"""
Unit tests for platform/api/signals.py

Tests webhook signal handlers.
"""

from unittest.mock import Mock, patch, MagicMock

import pytest

from smoothschedule.platform.api.signals import (
    trigger_webhook,
    handle_appointment_created,
    handle_appointment_updated,
    handle_appointment_cancelled,
    handle_customer_created,
    handle_customer_updated,
    register_webhook_signals,
)


class TestTriggerWebhook:
    """Tests for trigger_webhook function."""

    @patch('smoothschedule.platform.api.models.WebhookSubscription')
    @patch('smoothschedule.platform.api.webhooks.queue_webhook_delivery')
    def test_queues_delivery_for_matching_subscriptions(self, mock_queue, mock_model):
        """Should queue webhook delivery for all matching subscriptions."""
        # Setup mock subscriptions
        sub1 = Mock()
        sub2 = Mock()
        mock_model.objects.filter.return_value = [sub1, sub2]

        tenant = Mock()
        event_type = 'appointment.created'
        data = {'id': '123'}

        trigger_webhook(tenant, event_type, data)

        mock_model.objects.filter.assert_called_once_with(
            tenant=tenant,
            is_active=True,
            events__contains=[event_type]
        )
        assert mock_queue.call_count == 2
        mock_queue.assert_any_call(sub1, event_type, data)
        mock_queue.assert_any_call(sub2, event_type, data)

    @patch('smoothschedule.platform.api.models.WebhookSubscription')
    @patch('smoothschedule.platform.api.webhooks.queue_webhook_delivery')
    def test_no_delivery_when_no_subscriptions(self, mock_queue, mock_model):
        """Should not queue any deliveries when no subscriptions match."""
        mock_model.objects.filter.return_value = []

        tenant = Mock()

        trigger_webhook(tenant, 'some.event', {})

        mock_queue.assert_not_called()


class TestHandleAppointmentCreated:
    """Tests for handle_appointment_created signal handler."""

    @patch('smoothschedule.platform.api.signals.trigger_webhook')
    def test_triggers_webhook_on_create(self, mock_trigger):
        """Should trigger webhook when appointment is created."""
        tenant = Mock()
        instance = Mock()
        instance.tenant = tenant
        instance.id = 123
        instance.start = Mock()
        instance.start.isoformat.return_value = '2024-01-01T10:00:00'
        instance.end = Mock()
        instance.end.isoformat.return_value = '2024-01-01T11:00:00'
        instance.status = 'CONFIRMED'

        handle_appointment_created(Mock(), instance, created=True)

        mock_trigger.assert_called_once()
        call_args = mock_trigger.call_args
        assert call_args[0][0] == tenant
        assert call_args[0][1] == 'appointment.created'
        assert call_args[0][2]['id'] == '123'
        assert call_args[0][2]['status'] == 'CONFIRMED'

    @patch('smoothschedule.platform.api.signals.trigger_webhook')
    def test_does_not_trigger_on_update(self, mock_trigger):
        """Should not trigger webhook when appointment is updated (not created)."""
        instance = Mock()

        handle_appointment_created(Mock(), instance, created=False)

        mock_trigger.assert_not_called()

    @patch('smoothschedule.platform.api.signals.trigger_webhook')
    def test_does_not_trigger_without_tenant(self, mock_trigger):
        """Should not trigger webhook when instance has no tenant."""
        instance = Mock(spec=['id', 'status'])  # No tenant attribute

        handle_appointment_created(Mock(), instance, created=True)

        mock_trigger.assert_not_called()

    @patch('smoothschedule.platform.api.signals.trigger_webhook')
    def test_handles_none_tenant(self, mock_trigger):
        """Should not trigger webhook when tenant is None."""
        instance = Mock()
        instance.tenant = None

        handle_appointment_created(Mock(), instance, created=True)

        mock_trigger.assert_not_called()

    @patch('smoothschedule.platform.api.signals.trigger_webhook')
    def test_handles_exception_silently(self, mock_trigger):
        """Should catch and suppress exceptions."""
        mock_trigger.side_effect = Exception("Test error")

        instance = Mock()
        instance.tenant = Mock()

        # Should not raise
        handle_appointment_created(Mock(), instance, created=True)

    @patch('smoothschedule.platform.api.signals.trigger_webhook')
    def test_handles_none_start_time(self, mock_trigger):
        """Should handle None start time."""
        tenant = Mock()
        instance = Mock()
        instance.tenant = tenant
        instance.id = 123
        instance.start = None
        instance.end = None
        instance.status = 'PENDING'

        handle_appointment_created(Mock(), instance, created=True)

        mock_trigger.assert_called_once()
        data = mock_trigger.call_args[0][2]
        assert data['start_time'] is None
        assert data['end_time'] is None


class TestHandleAppointmentUpdated:
    """Tests for handle_appointment_updated signal handler."""

    @patch('smoothschedule.platform.api.signals.trigger_webhook')
    def test_triggers_webhook_on_update(self, mock_trigger):
        """Should trigger webhook when appointment is updated."""
        tenant = Mock()
        instance = Mock()
        instance.tenant = tenant
        instance.id = 456
        instance.start = Mock()
        instance.start.isoformat.return_value = '2024-01-01T10:00:00'
        instance.end = Mock()
        instance.end.isoformat.return_value = '2024-01-01T11:00:00'
        instance.status = 'CONFIRMED'

        handle_appointment_updated(Mock(), instance)

        mock_trigger.assert_called_once()
        assert mock_trigger.call_args[0][1] == 'appointment.updated'

    @patch('smoothschedule.platform.api.signals.trigger_webhook')
    def test_does_not_trigger_without_tenant(self, mock_trigger):
        """Should not trigger webhook when instance has no tenant."""
        instance = Mock(spec=['id', 'status'])

        handle_appointment_updated(Mock(), instance)

        mock_trigger.assert_not_called()

    @patch('smoothschedule.platform.api.signals.trigger_webhook')
    def test_handles_exception_silently(self, mock_trigger):
        """Should catch and suppress exceptions."""
        mock_trigger.side_effect = Exception("Test error")

        instance = Mock()
        instance.tenant = Mock()

        # Should not raise
        handle_appointment_updated(Mock(), instance)


class TestHandleAppointmentCancelled:
    """Tests for handle_appointment_cancelled signal handler."""

    @patch('smoothschedule.platform.api.signals.trigger_webhook')
    def test_triggers_webhook_on_cancellation(self, mock_trigger):
        """Should trigger webhook when appointment is cancelled."""
        tenant = Mock()
        instance = Mock()
        instance.tenant = tenant
        instance.id = 789
        instance.status = 'CANCELLED'
        instance.start = Mock()
        instance.start.isoformat.return_value = '2024-01-01T10:00:00'
        instance.end = Mock()
        instance.end.isoformat.return_value = '2024-01-01T11:00:00'

        handle_appointment_cancelled(Mock(), instance)

        mock_trigger.assert_called_once()
        assert mock_trigger.call_args[0][1] == 'appointment.cancelled'

    @patch('smoothschedule.platform.api.signals.trigger_webhook')
    def test_does_not_trigger_when_not_cancelled(self, mock_trigger):
        """Should not trigger webhook when status is not CANCELLED."""
        instance = Mock()
        instance.status = 'CONFIRMED'

        handle_appointment_cancelled(Mock(), instance)

        mock_trigger.assert_not_called()

    @patch('smoothschedule.platform.api.signals.trigger_webhook')
    def test_does_not_trigger_without_tenant(self, mock_trigger):
        """Should not trigger webhook when instance has no tenant."""
        instance = Mock(spec=['id', 'status'])
        instance.status = 'CANCELLED'

        handle_appointment_cancelled(Mock(), instance)

        mock_trigger.assert_not_called()

    @patch('smoothschedule.platform.api.signals.trigger_webhook')
    def test_handles_exception_silently(self, mock_trigger):
        """Should catch and suppress exceptions."""
        mock_trigger.side_effect = Exception("Test error")

        instance = Mock()
        instance.status = 'CANCELLED'
        instance.tenant = Mock()

        # Should not raise
        handle_appointment_cancelled(Mock(), instance)


class TestHandleCustomerCreated:
    """Tests for handle_customer_created signal handler."""

    @patch('smoothschedule.platform.api.signals.trigger_webhook')
    def test_triggers_webhook_for_customer_role(self, mock_trigger):
        """Should trigger webhook when customer is created."""
        tenant = Mock()
        instance = Mock()
        instance.tenant = tenant
        instance.role = 'CUSTOMER'
        instance.id = 111
        instance.email = 'customer@example.com'
        instance.get_full_name.return_value = 'John Doe'

        handle_customer_created(Mock(), instance, created=True)

        mock_trigger.assert_called_once()
        assert mock_trigger.call_args[0][1] == 'customer.created'
        data = mock_trigger.call_args[0][2]
        assert data['email'] == 'customer@example.com'
        assert data['name'] == 'John Doe'

    @patch('smoothschedule.platform.api.signals.trigger_webhook')
    def test_does_not_trigger_on_update(self, mock_trigger):
        """Should not trigger webhook when customer is updated."""
        instance = Mock()
        instance.role = 'CUSTOMER'

        handle_customer_created(Mock(), instance, created=False)

        mock_trigger.assert_not_called()

    @patch('smoothschedule.platform.api.signals.trigger_webhook')
    def test_does_not_trigger_for_non_customer_role(self, mock_trigger):
        """Should not trigger webhook for non-customer roles."""
        instance = Mock()
        instance.role = 'STAFF'

        handle_customer_created(Mock(), instance, created=True)

        mock_trigger.assert_not_called()

    @patch('smoothschedule.platform.api.signals.trigger_webhook')
    def test_does_not_trigger_without_tenant(self, mock_trigger):
        """Should not trigger webhook when no tenant."""
        instance = Mock(spec=['role', 'id', 'email'])
        instance.role = 'CUSTOMER'

        handle_customer_created(Mock(), instance, created=True)

        mock_trigger.assert_not_called()

    @patch('smoothschedule.platform.api.signals.trigger_webhook')
    def test_handles_missing_get_full_name(self, mock_trigger):
        """Should handle instance without get_full_name method."""
        tenant = Mock()
        instance = Mock(spec=['tenant', 'role', 'id', 'email'])
        instance.tenant = tenant
        instance.role = 'CUSTOMER'
        instance.id = 222
        instance.email = 'test@example.com'

        handle_customer_created(Mock(), instance, created=True)

        mock_trigger.assert_called_once()
        data = mock_trigger.call_args[0][2]
        assert data['name'] is None


class TestHandleCustomerUpdated:
    """Tests for handle_customer_updated signal handler."""

    @patch('smoothschedule.platform.api.signals.trigger_webhook')
    def test_triggers_webhook_for_customer_update(self, mock_trigger):
        """Should trigger webhook when customer is updated."""
        tenant = Mock()
        instance = Mock()
        instance.tenant = tenant
        instance.role = 'CUSTOMER'
        instance.id = 333
        instance.email = 'updated@example.com'
        instance.get_full_name.return_value = 'Jane Doe'

        handle_customer_updated(Mock(), instance)

        mock_trigger.assert_called_once()
        assert mock_trigger.call_args[0][1] == 'customer.updated'

    @patch('smoothschedule.platform.api.signals.trigger_webhook')
    def test_does_not_trigger_for_non_customer_role(self, mock_trigger):
        """Should not trigger webhook for non-customer roles."""
        instance = Mock()
        instance.role = 'MANAGER'

        handle_customer_updated(Mock(), instance)

        mock_trigger.assert_not_called()

    @patch('smoothschedule.platform.api.signals.trigger_webhook')
    def test_handles_exception_silently(self, mock_trigger):
        """Should catch and suppress exceptions."""
        mock_trigger.side_effect = Exception("Test error")

        instance = Mock()
        instance.role = 'CUSTOMER'
        instance.tenant = Mock()

        # Should not raise
        handle_customer_updated(Mock(), instance)


class TestRegisterWebhookSignals:
    """Tests for register_webhook_signals function."""

    @patch('smoothschedule.platform.api.signals.post_save')
    def test_connects_event_signals(self, mock_post_save):
        """Should connect signal handlers for Event model."""
        with patch.dict('sys.modules', {'smoothschedule.scheduling.schedule.models': MagicMock()}):
            register_webhook_signals()

        # Should have attempted to connect signals
        assert mock_post_save.connect.called

    @patch('smoothschedule.platform.api.signals.post_save')
    def test_handles_import_error_for_event(self, mock_post_save):
        """Should handle ImportError when Event model not available."""
        with patch('smoothschedule.platform.api.signals.post_save') as mock_ps:
            # Simulate import error by patching the import inside the function
            with patch.dict('sys.modules', {'smoothschedule.scheduling.schedule.models': None}):
                # This should not raise
                try:
                    register_webhook_signals()
                except ImportError:
                    pass  # Expected in some test environments

    @patch('smoothschedule.platform.api.signals.post_save')
    def test_handles_import_error_for_user(self, mock_post_save):
        """Should handle ImportError when User model not available."""
        with patch.dict('sys.modules', {'smoothschedule.identity.users.models': None}):
            # This should not raise
            try:
                register_webhook_signals()
            except ImportError:
                pass  # Expected in some test environments
