import pytest
from datetime import datetime
from django.utils import timezone
from unittest.mock import Mock

from smoothschedule.communication.messaging.serializers import (
    BroadcastMessageListSerializer,
    BroadcastMessageDetailSerializer,
    BroadcastMessageCreateSerializer,
)
from smoothschedule.communication.messaging.models import BroadcastMessage


class TestBroadcastMessageListSerializer:
    """Test BroadcastMessageListSerializer returns all required fields."""

    def test_serializer_includes_all_required_fields(self):
        """Serializer should include all fields needed by frontend."""
        # Arrange - create a mock message
        sender = Mock()
        sender.id = 1
        sender.get_full_name.return_value = "John Doe"
        sender.username = "johndoe"

        message = Mock(spec=BroadcastMessage)
        message.id = 123
        message.subject = 'Test Subject'
        message.body = 'Test message body content'
        message.status = BroadcastMessage.Status.SENT
        message.delivery_method = BroadcastMessage.DeliveryMethod.EMAIL
        message.sender = sender
        message.target_owners = True
        message.target_managers = False
        message.target_staff = True
        message.target_customers = False
        message.individual_recipients.all.return_value = []
        message.individual_recipients.exists.return_value = False
        message.total_recipients = 10
        message.delivered_count = 8
        message.read_count = 5
        message.created_at = timezone.now()
        message.sent_at = timezone.now()
        message.scheduled_at = None

        # Mock the get_target_description method
        message.get_target_description.return_value = 'Owners, Staff'

        # Act
        serializer = BroadcastMessageListSerializer(message)
        data = serializer.data

        # Assert - check all required fields are present
        assert 'id' in data
        assert 'subject' in data
        assert 'body' in data  # Frontend needs this for preview
        assert 'status' in data
        assert 'delivery_method' in data
        assert 'created_by' in data  # Frontend needs sender ID
        assert 'created_by_name' in data  # Frontend needs sender name
        assert 'target_roles' in data  # Frontend needs array of roles
        assert 'target_users' in data  # Frontend needs array of user IDs
        assert 'total_recipients' in data
        assert 'delivered_count' in data
        assert 'read_count' in data
        assert 'created_at' in data
        assert 'sent_at' in data

    def test_target_roles_returns_array_of_selected_roles(self):
        """target_roles should return array of selected role strings."""
        # Arrange
        sender = Mock()
        sender.id = 1
        sender.get_full_name.return_value = "John Doe"

        message = Mock(spec=BroadcastMessage)
        message.id = 456
        message.subject = 'Test'
        message.body = 'Body'
        message.status = BroadcastMessage.Status.SENT
        message.delivery_method = BroadcastMessage.DeliveryMethod.IN_APP
        message.sender = sender
        message.target_owners = True
        message.target_managers = True
        message.target_staff = False
        message.target_customers = False
        message.individual_recipients.all.return_value = []
        message.individual_recipients.exists.return_value = False
        message.total_recipients = 5
        message.delivered_count = 5
        message.read_count = 2
        message.created_at = timezone.now()
        message.sent_at = timezone.now()
        message.scheduled_at = None
        message.get_target_description.return_value = 'Owners, Managers'

        # Act
        serializer = BroadcastMessageListSerializer(message)
        data = serializer.data

        # Assert
        assert isinstance(data['target_roles'], list)
        assert 'owner' in data['target_roles']
        assert 'manager' in data['target_roles']
        assert 'staff' not in data['target_roles']
        assert 'customer' not in data['target_roles']

    def test_target_users_returns_array_of_individual_recipient_ids(self):
        """target_users should return array of individual recipient IDs."""
        # Arrange
        sender = Mock()
        sender.id = 1
        sender.get_full_name.return_value = "John Doe"

        user1 = Mock()
        user1.id = 10

        user2 = Mock()
        user2.id = 20

        message = Mock(spec=BroadcastMessage)
        message.id = 456
        message.subject = 'Test'
        message.body = 'Body'
        message.status = BroadcastMessage.Status.SENT
        message.delivery_method = BroadcastMessage.DeliveryMethod.SMS
        message.sender = sender
        message.target_owners = False
        message.target_managers = False
        message.target_staff = False
        message.target_customers = False
        message.individual_recipients.all.return_value = [user1, user2]
        message.individual_recipients.exists.return_value = True
        message.individual_recipients.count.return_value = 2
        message.total_recipients = 2
        message.delivered_count = 2
        message.read_count = 1
        message.created_at = timezone.now()
        message.sent_at = timezone.now()
        message.scheduled_at = None
        message.get_target_description.return_value = '2 Individual(s)'

        # Act
        serializer = BroadcastMessageListSerializer(message)
        data = serializer.data

        # Assert
        assert isinstance(data['target_users'], list)
        assert '10' in data['target_users']
        assert '20' in data['target_users']
        assert len(data['target_users']) == 2

    def test_delivery_method_format_matches_frontend_expectations(self):
        """delivery_method should be uppercase with underscores (IN_APP, not in_app)."""
        # Arrange
        sender = Mock()
        sender.id = 1
        sender.get_full_name.return_value = "John Doe"

        message = Mock(spec=BroadcastMessage)
        message.id = 456
        message.subject = 'Test'
        message.body = 'Body'
        message.status = BroadcastMessage.Status.SENT
        message.delivery_method = 'in_app'  # Django stores as lowercase
        message.sender = sender
        message.target_owners = True
        message.target_managers = False
        message.target_staff = False
        message.target_customers = False
        message.individual_recipients.all.return_value = []
        message.individual_recipients.exists.return_value = False
        message.total_recipients = 5
        message.delivered_count = 5
        message.read_count = 2
        message.created_at = timezone.now()
        message.sent_at = timezone.now()
        message.scheduled_at = None
        message.get_target_description.return_value = 'Owners'

        # Act
        serializer = BroadcastMessageListSerializer(message)
        data = serializer.data

        # Assert
        assert data['delivery_method'] == 'IN_APP'  # Frontend expects uppercase

    def test_created_by_contains_sender_id(self):
        """created_by should contain the sender's ID as string."""
        # Arrange
        sender = Mock()
        sender.id = 42
        sender.get_full_name.return_value = "Jane Smith"

        message = Mock(spec=BroadcastMessage)
        message.id = 456
        message.subject = 'Test'
        message.body = 'Body'
        message.status = BroadcastMessage.Status.SENT
        message.delivery_method = BroadcastMessage.DeliveryMethod.EMAIL
        message.sender = sender
        message.target_owners = False
        message.target_managers = True
        message.target_staff = False
        message.target_customers = False
        message.individual_recipients.all.return_value = []
        message.individual_recipients.exists.return_value = False
        message.total_recipients = 3
        message.delivered_count = 3
        message.read_count = 1
        message.created_at = timezone.now()
        message.sent_at = timezone.now()
        message.scheduled_at = None
        message.get_target_description.return_value = 'Managers'

        # Act
        serializer = BroadcastMessageListSerializer(message)
        data = serializer.data

        # Assert
        assert data['created_by'] == '42'
        assert data['created_by_name'] == 'Jane Smith'


class TestBroadcastMessageCreateSerializer:
    """Test BroadcastMessageCreateSerializer accepts frontend data format."""

    def test_accepts_target_roles_array(self):
        """Serializer should accept target_roles as array and convert to boolean fields."""
        # Arrange
        data = {
            'subject': 'Test Message',
            'body': 'Test body',
            'target_roles': ['owner', 'staff'],
            'target_users': [],
            'delivery_method': 'IN_APP'
        }

        # Act
        serializer = BroadcastMessageCreateSerializer(data=data)

        # Assert
        assert serializer.is_valid(), serializer.errors
        validated = serializer.validated_data
        assert validated['target_owners'] is True
        assert validated['target_managers'] is False
        assert validated['target_staff'] is True
        assert validated['target_customers'] is False

    def test_accepts_target_users_array(self):
        """Serializer should accept target_users as array of user IDs."""
        # Arrange
        data = {
            'subject': 'Test Message',
            'body': 'Test body',
            'target_roles': [],
            'target_users': ['10', '20', '30'],
            'delivery_method': 'EMAIL'
        }

        # Act
        serializer = BroadcastMessageCreateSerializer(data=data)

        # Assert
        assert serializer.is_valid(), serializer.errors
        validated = serializer.validated_data
        assert validated['individual_recipient_ids'] == [10, 20, 30]

    def test_accepts_delivery_method_uppercase(self):
        """Serializer should accept uppercase delivery method (IN_APP, EMAIL, etc.)."""
        # Arrange - test all valid uppercase formats
        test_cases = [
            ('IN_APP', 'in_app'),
            ('EMAIL', 'email'),
            ('SMS', 'sms'),
            ('ALL', 'all'),
        ]

        for input_method, expected_method in test_cases:
            data = {
                'subject': 'Test',
                'body': 'Test body',
                'target_roles': ['owner'],
                'target_users': [],
                'delivery_method': input_method
            }

            # Act
            serializer = BroadcastMessageCreateSerializer(data=data)

            # Assert
            assert serializer.is_valid(), f"Failed for {input_method}: {serializer.errors}"
            assert serializer.validated_data['delivery_method'] == expected_method

    def test_rejects_invalid_target_roles(self):
        """Serializer should reject invalid role names."""
        # Arrange
        data = {
            'subject': 'Test',
            'body': 'Test body',
            'target_roles': ['invalid_role'],
            'target_users': [],
            'delivery_method': 'IN_APP'
        }

        # Act
        serializer = BroadcastMessageCreateSerializer(data=data)

        # Assert
        assert not serializer.is_valid()
        assert 'target_roles' in serializer.errors

    def test_requires_at_least_one_target(self):
        """Serializer should require at least one target (role or user)."""
        # Arrange
        data = {
            'subject': 'Test',
            'body': 'Test body',
            'target_roles': [],
            'target_users': [],
            'delivery_method': 'IN_APP'
        }

        # Act
        serializer = BroadcastMessageCreateSerializer(data=data)

        # Assert
        assert not serializer.is_valid()
        assert 'non_field_errors' in serializer.errors or 'target_roles' in serializer.errors

    def test_rejects_invalid_delivery_method(self):
        """Serializer should reject invalid delivery method."""
        data = {
            'subject': 'Test',
            'body': 'Test body',
            'target_roles': ['owner'],
            'target_users': [],
            'delivery_method': 'INVALID_METHOD'
        }

        serializer = BroadcastMessageCreateSerializer(data=data)

        assert not serializer.is_valid()
        assert 'delivery_method' in serializer.errors

    def test_rejects_invalid_user_id_format(self):
        """Serializer should reject non-integer user IDs."""
        data = {
            'subject': 'Test',
            'body': 'Test body',
            'target_roles': [],
            'target_users': ['not_an_integer'],
            'delivery_method': 'IN_APP'
        }

        serializer = BroadcastMessageCreateSerializer(data=data)

        assert not serializer.is_valid()
        assert 'target_users' in serializer.errors


class TestBroadcastMessageCreateSerializerCreate:
    """Test BroadcastMessageCreateSerializer.create method."""

    def test_create_sets_draft_status_when_send_immediately_true(self):
        """Create should set DRAFT status when send_immediately is True."""
        from smoothschedule.communication.messaging.serializers import BroadcastMessageCreateSerializer
        from unittest.mock import patch

        validated_data = {
            'subject': 'Test Message',
            'body': 'Test body',
            'target_owners': True,
            'target_managers': False,
            'target_staff': False,
            'target_customers': False,
            'delivery_method': 'in_app',
            'individual_recipient_ids': [],
            'send_immediately': True,
        }

        with patch('smoothschedule.communication.messaging.serializers.BroadcastMessage') as MockMessage:
            mock_message = Mock()
            mock_message.individual_recipients = Mock()
            MockMessage.objects.create.return_value = mock_message
            MockMessage.Status.DRAFT = 'draft'
            MockMessage.Status.SCHEDULED = 'scheduled'

            serializer = BroadcastMessageCreateSerializer()
            result = serializer.create(validated_data)

        assert validated_data['status'] == 'draft'

    def test_create_sets_scheduled_status_when_scheduled(self):
        """Create should set SCHEDULED status when scheduled_at is set and send_immediately is False."""
        from smoothschedule.communication.messaging.serializers import BroadcastMessageCreateSerializer
        from unittest.mock import patch
        from datetime import datetime, timedelta

        scheduled_time = timezone.now() + timedelta(hours=2)
        validated_data = {
            'subject': 'Scheduled Message',
            'body': 'Test body',
            'target_owners': True,
            'target_managers': False,
            'target_staff': False,
            'target_customers': False,
            'delivery_method': 'email',
            'individual_recipient_ids': [],
            'send_immediately': False,
            'scheduled_at': scheduled_time,
        }

        with patch('smoothschedule.communication.messaging.serializers.BroadcastMessage') as MockMessage:
            mock_message = Mock()
            mock_message.individual_recipients = Mock()
            MockMessage.objects.create.return_value = mock_message
            MockMessage.Status.DRAFT = 'draft'
            MockMessage.Status.SCHEDULED = 'scheduled'

            serializer = BroadcastMessageCreateSerializer()
            result = serializer.create(validated_data)

        assert validated_data['status'] == 'scheduled'

    def test_create_adds_individual_recipients(self):
        """Create should add individual recipients to the message."""
        from smoothschedule.communication.messaging.serializers import BroadcastMessageCreateSerializer
        from unittest.mock import patch

        validated_data = {
            'subject': 'Test Message',
            'body': 'Test body',
            'target_owners': False,
            'target_managers': False,
            'target_staff': False,
            'target_customers': False,
            'delivery_method': 'in_app',
            'individual_recipient_ids': [10, 20, 30],
            'send_immediately': True,
        }

        with patch('smoothschedule.communication.messaging.serializers.BroadcastMessage') as MockMessage:
            mock_message = Mock()
            mock_recipients = Mock()
            mock_message.individual_recipients = mock_recipients
            MockMessage.objects.create.return_value = mock_message
            MockMessage.Status.DRAFT = 'draft'

            # User is imported locally in create, so patch where it's actually used
            with patch('smoothschedule.identity.users.models.User') as MockUser:
                mock_users = [Mock(id=10), Mock(id=20), Mock(id=30)]
                MockUser.objects.filter.return_value = mock_users

                serializer = BroadcastMessageCreateSerializer()
                result = serializer.create(validated_data)

        # Verify the message was created and recipients were set
        MockMessage.objects.create.assert_called_once()
        mock_recipients.set.assert_called_once()


class TestMessageRecipientSerializer:
    """Test MessageRecipientSerializer."""

    def test_get_user_name_returns_full_name(self):
        """get_user_name should return user's full name."""
        from smoothschedule.communication.messaging.serializers import MessageRecipientSerializer

        mock_obj = Mock()
        mock_obj.user.get_full_name.return_value = 'John Doe'
        mock_obj.user.username = 'johndoe'

        serializer = MessageRecipientSerializer()
        result = serializer.get_user_name(mock_obj)

        assert result == 'John Doe'

    def test_get_user_name_falls_back_to_username(self):
        """get_user_name should fall back to username if no full name."""
        from smoothschedule.communication.messaging.serializers import MessageRecipientSerializer

        mock_obj = Mock()
        mock_obj.user.get_full_name.return_value = ''
        mock_obj.user.username = 'johndoe'

        serializer = MessageRecipientSerializer()
        result = serializer.get_user_name(mock_obj)

        assert result == 'johndoe'


class TestBroadcastMessageListSerializerNullSender:
    """Test BroadcastMessageListSerializer with null sender."""

    def test_get_created_by_returns_none_when_no_sender(self):
        """get_created_by should return None when sender is None."""
        from smoothschedule.communication.messaging.serializers import BroadcastMessageListSerializer

        message = Mock()
        message.sender = None

        serializer = BroadcastMessageListSerializer()
        result = serializer.get_created_by(message)

        assert result is None

    def test_get_created_by_name_returns_unknown_when_no_sender(self):
        """get_created_by_name should return 'Unknown' when sender is None."""
        from smoothschedule.communication.messaging.serializers import BroadcastMessageListSerializer

        message = Mock()
        message.sender = None

        serializer = BroadcastMessageListSerializer()
        result = serializer.get_created_by_name(message)

        assert result == 'Unknown'

    def test_get_target_roles_includes_customer(self):
        """get_target_roles should include 'customer' when target_customers is True."""
        from smoothschedule.communication.messaging.serializers import BroadcastMessageListSerializer

        message = Mock()
        message.target_owners = False
        message.target_managers = False
        message.target_staff = False
        message.target_customers = True

        serializer = BroadcastMessageListSerializer()
        result = serializer.get_target_roles(message)

        assert 'customer' in result


class TestBroadcastMessageDetailSerializerMethods:
    """Test BroadcastMessageDetailSerializer method fields."""

    def test_get_sender_name_returns_full_name(self):
        """get_sender_name should return sender's full name."""
        from smoothschedule.communication.messaging.serializers import BroadcastMessageDetailSerializer

        message = Mock()
        message.sender = Mock()
        message.sender.get_full_name.return_value = 'Jane Doe'
        message.sender.username = 'janedoe'

        serializer = BroadcastMessageDetailSerializer()
        result = serializer.get_sender_name(message)

        assert result == 'Jane Doe'

    def test_get_sender_name_falls_back_to_username(self):
        """get_sender_name should fall back to username if no full name."""
        from smoothschedule.communication.messaging.serializers import BroadcastMessageDetailSerializer

        message = Mock()
        message.sender = Mock()
        message.sender.get_full_name.return_value = ''
        message.sender.username = 'janedoe'

        serializer = BroadcastMessageDetailSerializer()
        result = serializer.get_sender_name(message)

        assert result == 'janedoe'

    def test_get_sender_name_returns_unknown_when_no_sender(self):
        """get_sender_name should return 'Unknown' when sender is None."""
        from smoothschedule.communication.messaging.serializers import BroadcastMessageDetailSerializer

        message = Mock()
        message.sender = None

        serializer = BroadcastMessageDetailSerializer()
        result = serializer.get_sender_name(message)

        assert result == 'Unknown'

    def test_get_target_description_delegates_to_model(self):
        """get_target_description should call model's get_target_description."""
        from smoothschedule.communication.messaging.serializers import BroadcastMessageDetailSerializer

        message = Mock()
        message.get_target_description.return_value = 'All Staff, Managers'

        serializer = BroadcastMessageDetailSerializer()
        result = serializer.get_target_description(message)

        assert result == 'All Staff, Managers'
        message.get_target_description.assert_called_once()


class TestBroadcastMessageDetailSerializerValidation:
    """Test BroadcastMessageDetailSerializer validation."""

    def test_validate_passes_with_target_roles(self):
        """Validation should pass when target roles are selected."""
        from smoothschedule.communication.messaging.serializers import BroadcastMessageDetailSerializer

        data = {
            'subject': 'Test',
            'body': 'Body',
            'target_owners': True,
            'target_managers': False,
            'target_staff': False,
            'target_customers': False,
        }

        serializer = BroadcastMessageDetailSerializer()
        result = serializer.validate(data)

        assert result == data

    def test_validate_passes_with_individual_recipients(self):
        """Validation should pass when individual recipients are provided."""
        from smoothschedule.communication.messaging.serializers import BroadcastMessageDetailSerializer

        data = {
            'subject': 'Test',
            'body': 'Body',
            'target_owners': False,
            'target_managers': False,
            'target_staff': False,
            'target_customers': False,
            'individual_recipient_ids': [1, 2, 3],
        }

        serializer = BroadcastMessageDetailSerializer()
        result = serializer.validate(data)

        assert result == data

    def test_validate_fails_without_targets(self):
        """Validation should fail when no targets are selected."""
        from smoothschedule.communication.messaging.serializers import BroadcastMessageDetailSerializer
        from rest_framework.exceptions import ValidationError

        data = {
            'subject': 'Test',
            'body': 'Body',
            'target_owners': False,
            'target_managers': False,
            'target_staff': False,
            'target_customers': False,
            'individual_recipient_ids': [],
        }

        serializer = BroadcastMessageDetailSerializer()

        with pytest.raises(ValidationError) as exc_info:
            serializer.validate(data)

        assert 'At least one target' in str(exc_info.value)


class TestInboxMessageSerializer:
    """Test InboxMessageSerializer methods."""

    def test_get_sender_name_returns_full_name(self):
        """get_sender_name should return sender's full name."""
        from smoothschedule.communication.messaging.serializers import InboxMessageSerializer

        message = Mock()
        message.sender = Mock()
        message.sender.get_full_name.return_value = 'John Smith'
        message.sender.username = 'jsmith'

        serializer = InboxMessageSerializer()
        result = serializer.get_sender_name(message)

        assert result == 'John Smith'

    def test_get_sender_name_falls_back_to_username(self):
        """get_sender_name should fall back to username."""
        from smoothschedule.communication.messaging.serializers import InboxMessageSerializer

        message = Mock()
        message.sender = Mock()
        message.sender.get_full_name.return_value = ''
        message.sender.username = 'jsmith'

        serializer = InboxMessageSerializer()
        result = serializer.get_sender_name(message)

        assert result == 'jsmith'

    def test_get_sender_name_returns_system_when_no_sender(self):
        """get_sender_name should return 'System' when sender is None."""
        from smoothschedule.communication.messaging.serializers import InboxMessageSerializer

        message = Mock()
        message.sender = None

        serializer = InboxMessageSerializer()
        result = serializer.get_sender_name(message)

        assert result == 'System'

    def test_get_is_read_returns_true_when_recipient_has_read(self):
        """get_is_read should return True when recipient has read."""
        from smoothschedule.communication.messaging.serializers import InboxMessageSerializer

        mock_user = Mock()
        mock_request = Mock()
        mock_request.user = mock_user

        mock_recipient = Mock()
        mock_recipient.is_read = True

        message = Mock()
        message.recipients.filter.return_value.first.return_value = mock_recipient

        serializer = InboxMessageSerializer(context={'request': mock_request})
        result = serializer.get_is_read(message)

        assert result is True

    def test_get_is_read_returns_false_when_no_recipient(self):
        """get_is_read should return False when no recipient record."""
        from smoothschedule.communication.messaging.serializers import InboxMessageSerializer

        mock_user = Mock()
        mock_request = Mock()
        mock_request.user = mock_user

        message = Mock()
        message.recipients.filter.return_value.first.return_value = None

        serializer = InboxMessageSerializer(context={'request': mock_request})
        result = serializer.get_is_read(message)

        assert result is False

    def test_get_is_read_returns_false_when_no_request(self):
        """get_is_read should return False when no request context."""
        from smoothschedule.communication.messaging.serializers import InboxMessageSerializer

        message = Mock()

        serializer = InboxMessageSerializer(context={})
        result = serializer.get_is_read(message)

        assert result is False

    def test_get_read_at_returns_timestamp_when_read(self):
        """get_read_at should return timestamp when recipient has read."""
        from smoothschedule.communication.messaging.serializers import InboxMessageSerializer

        mock_user = Mock()
        mock_request = Mock()
        mock_request.user = mock_user

        read_time = timezone.now()
        mock_recipient = Mock()
        mock_recipient.read_at = read_time

        message = Mock()
        message.recipients.filter.return_value.first.return_value = mock_recipient

        serializer = InboxMessageSerializer(context={'request': mock_request})
        result = serializer.get_read_at(message)

        assert result == read_time

    def test_get_read_at_returns_none_when_no_recipient(self):
        """get_read_at should return None when no recipient record."""
        from smoothschedule.communication.messaging.serializers import InboxMessageSerializer

        mock_user = Mock()
        mock_request = Mock()
        mock_request.user = mock_user

        message = Mock()
        message.recipients.filter.return_value.first.return_value = None

        serializer = InboxMessageSerializer(context={'request': mock_request})
        result = serializer.get_read_at(message)

        assert result is None

    def test_get_read_at_returns_none_when_no_request(self):
        """get_read_at should return None when no request context."""
        from smoothschedule.communication.messaging.serializers import InboxMessageSerializer

        message = Mock()

        serializer = InboxMessageSerializer(context={})
        result = serializer.get_read_at(message)

        assert result is None
