"""
Unit tests for serializer mixins.

Tests use mocks to avoid database access.
Tests cover:
- TimezoneSerializerMixin
- TimezoneContextMixin
"""
from unittest.mock import Mock, patch, MagicMock
import pytest
from rest_framework import serializers


class TestTimezoneSerializerMixin:
    """Test TimezoneSerializerMixin class."""

    def test_adds_business_timezone_field_to_serializer(self):
        """Should add business_timezone as a SerializerMethodField when in Meta.fields."""
        from smoothschedule.identity.core.mixins import TimezoneSerializerMixin

        class TestSerializer(TimezoneSerializerMixin, serializers.Serializer):
            name = serializers.CharField()

            class Meta:
                fields = ['name', 'business_timezone']

        # Need to instantiate with context to bind the serializer
        serializer = TestSerializer(context={})

        # Check that the mixin provides the get_business_timezone method
        assert hasattr(TimezoneSerializerMixin, 'get_business_timezone')
        # Check that business_timezone field is dynamically added when in Meta.fields
        assert 'business_timezone' in serializer.fields
        assert isinstance(serializer.fields['business_timezone'], serializers.SerializerMethodField)

    def test_get_business_timezone_from_context_tenant(self):
        """Should get timezone from tenant in context."""
        from smoothschedule.identity.core.mixins import TimezoneSerializerMixin

        class TestSerializer(TimezoneSerializerMixin, serializers.Serializer):
            name = serializers.CharField()

        mock_tenant = Mock()
        mock_tenant.timezone = 'America/Denver'

        serializer = TestSerializer(context={'tenant': mock_tenant})

        obj = Mock()
        result = serializer.get_business_timezone(obj)

        assert result == 'America/Denver'

    def test_get_business_timezone_returns_none_when_timezone_not_set(self):
        """Should return None when tenant timezone is empty."""
        from smoothschedule.identity.core.mixins import TimezoneSerializerMixin

        class TestSerializer(TimezoneSerializerMixin, serializers.Serializer):
            name = serializers.CharField()

        mock_tenant = Mock()
        mock_tenant.timezone = ''

        serializer = TestSerializer(context={'tenant': mock_tenant})

        obj = Mock()
        result = serializer.get_business_timezone(obj)

        assert result is None

    def test_get_business_timezone_returns_none_when_timezone_is_none(self):
        """Should return None when tenant timezone is None."""
        from smoothschedule.identity.core.mixins import TimezoneSerializerMixin

        class TestSerializer(TimezoneSerializerMixin, serializers.Serializer):
            name = serializers.CharField()

        mock_tenant = Mock()
        mock_tenant.timezone = None

        serializer = TestSerializer(context={'tenant': mock_tenant})

        obj = Mock()
        result = serializer.get_business_timezone(obj)

        assert result is None

    def test_get_business_timezone_from_request_tenant(self):
        """Should get timezone from request.tenant when not in direct context."""
        from smoothschedule.identity.core.mixins import TimezoneSerializerMixin

        class TestSerializer(TimezoneSerializerMixin, serializers.Serializer):
            name = serializers.CharField()

        mock_tenant = Mock()
        mock_tenant.timezone = 'Europe/London'

        mock_request = Mock()
        mock_request.tenant = mock_tenant

        serializer = TestSerializer(context={'request': mock_request})

        obj = Mock()
        result = serializer.get_business_timezone(obj)

        assert result == 'Europe/London'

    def test_get_business_timezone_from_connection_tenant(self):
        """Should fallback to connection.tenant from django-tenants."""
        from smoothschedule.identity.core.mixins import TimezoneSerializerMixin

        class TestSerializer(TimezoneSerializerMixin, serializers.Serializer):
            name = serializers.CharField()

        mock_tenant = Mock()
        mock_tenant.timezone = 'Asia/Tokyo'

        serializer = TestSerializer(context={})

        obj = Mock()

        with patch('django.db.connection') as mock_connection:
            mock_connection.tenant = mock_tenant

            result = serializer.get_business_timezone(obj)

            assert result == 'Asia/Tokyo'

    def test_get_business_timezone_returns_none_when_no_tenant_available(self):
        """Should return None when no tenant is available from any source."""
        from smoothschedule.identity.core.mixins import TimezoneSerializerMixin

        class TestSerializer(TimezoneSerializerMixin, serializers.Serializer):
            name = serializers.CharField()

        serializer = TestSerializer(context={})

        obj = Mock()

        with patch('django.db.connection') as mock_connection:
            # No tenant attribute on connection
            del mock_connection.tenant

            result = serializer.get_business_timezone(obj)

            assert result is None

    def test_get_business_timezone_handles_exception_gracefully(self):
        """Should handle exceptions when accessing connection.tenant."""
        from smoothschedule.identity.core.mixins import TimezoneSerializerMixin

        class TestSerializer(TimezoneSerializerMixin, serializers.Serializer):
            name = serializers.CharField()

        serializer = TestSerializer(context={})

        obj = Mock()

        with patch('django.db.connection') as mock_connection:
            type(mock_connection).tenant = property(lambda self: (_ for _ in ()).throw(Exception("Connection error")))

            result = serializer.get_business_timezone(obj)

            # Should return None instead of crashing
            assert result is None

    def test_priority_context_tenant_over_request_tenant(self):
        """Should prioritize direct context tenant over request.tenant."""
        from smoothschedule.identity.core.mixins import TimezoneSerializerMixin

        class TestSerializer(TimezoneSerializerMixin, serializers.Serializer):
            name = serializers.CharField()

        context_tenant = Mock()
        context_tenant.timezone = 'America/New_York'

        request_tenant = Mock()
        request_tenant.timezone = 'America/Los_Angeles'

        mock_request = Mock()
        mock_request.tenant = request_tenant

        serializer = TestSerializer(context={
            'tenant': context_tenant,
            'request': mock_request
        })

        obj = Mock()
        result = serializer.get_business_timezone(obj)

        # Should use context tenant, not request tenant
        assert result == 'America/New_York'

    def test_priority_request_tenant_over_connection_tenant(self):
        """Should prioritize request.tenant over connection.tenant."""
        from smoothschedule.identity.core.mixins import TimezoneSerializerMixin

        class TestSerializer(TimezoneSerializerMixin, serializers.Serializer):
            name = serializers.CharField()

        request_tenant = Mock()
        request_tenant.timezone = 'Pacific/Auckland'

        connection_tenant = Mock()
        connection_tenant.timezone = 'Pacific/Fiji'

        mock_request = Mock()
        mock_request.tenant = request_tenant

        serializer = TestSerializer(context={'request': mock_request})

        obj = Mock()

        with patch('django.db.connection') as mock_connection:
            mock_connection.tenant = connection_tenant

            result = serializer.get_business_timezone(obj)

            # Should use request tenant, not connection tenant
            assert result == 'Pacific/Auckland'

    def test_get_business_timezone_handles_missing_timezone_attribute(self):
        """Should handle tenant objects without timezone attribute."""
        from smoothschedule.identity.core.mixins import TimezoneSerializerMixin

        class TestSerializer(TimezoneSerializerMixin, serializers.Serializer):
            name = serializers.CharField()

        mock_tenant = Mock(spec=[])  # No timezone attribute

        serializer = TestSerializer(context={'tenant': mock_tenant})

        obj = Mock()
        result = serializer.get_business_timezone(obj)

        assert result is None

    def test_serializer_includes_timezone_in_representation(self):
        """Should include business_timezone in serialized output."""
        from smoothschedule.identity.core.mixins import TimezoneSerializerMixin

        class TestSerializer(TimezoneSerializerMixin, serializers.Serializer):
            name = serializers.CharField()

            class Meta:
                fields = ['name', 'business_timezone']

        mock_tenant = Mock()
        mock_tenant.timezone = 'America/Chicago'

        mock_obj = Mock()
        mock_obj.name = 'Test Event'

        serializer = TestSerializer(mock_obj, context={'tenant': mock_tenant})

        # The business_timezone should be accessible as a method field
        tz = serializer.get_business_timezone(mock_obj)
        assert tz == 'America/Chicago'

    def test_serializer_timezone_is_read_only(self):
        """Should not allow setting business_timezone (read-only field)."""
        from smoothschedule.identity.core.mixins import TimezoneSerializerMixin

        class TestSerializer(TimezoneSerializerMixin, serializers.Serializer):
            name = serializers.CharField()

            class Meta:
                fields = ['name', 'business_timezone']

        # Attempt to create with business_timezone
        data = {
            'name': 'Test Event',
            'business_timezone': 'America/New_York'
        }

        serializer = TestSerializer(data=data)

        # business_timezone should be ignored (it's a method field, read-only)
        assert serializer.is_valid()

        # The business_timezone field is a SerializerMethodField which is always read-only
        assert isinstance(serializer.fields['business_timezone'], serializers.SerializerMethodField)
        # Validated data should not include business_timezone (it's read-only)
        assert 'business_timezone' not in serializer.validated_data


class TestTimezoneContextMixin:
    """Test TimezoneContextMixin for ViewSets."""

    def test_adds_tenant_to_serializer_context(self):
        """Should add tenant to serializer context."""
        from smoothschedule.identity.core.mixins import TimezoneContextMixin
        from rest_framework.viewsets import ModelViewSet

        class TestViewSet(TimezoneContextMixin, ModelViewSet):
            pass

        mock_tenant = Mock()

        viewset = TestViewSet()
        viewset.request = Mock()
        viewset.request.tenant = mock_tenant

        # Mock the parent's get_serializer_context
        with patch.object(ModelViewSet, 'get_serializer_context', return_value={}):
            context = viewset.get_serializer_context()

            assert 'tenant' in context
            assert context['tenant'] == mock_tenant

    def test_preserves_existing_context_from_parent(self):
        """Should preserve context from parent class."""
        from smoothschedule.identity.core.mixins import TimezoneContextMixin
        from rest_framework.viewsets import ModelViewSet

        class TestViewSet(TimezoneContextMixin, ModelViewSet):
            pass

        mock_tenant = Mock()

        viewset = TestViewSet()
        viewset.request = Mock()
        viewset.request.tenant = mock_tenant

        # Parent returns some context
        parent_context = {
            'request': viewset.request,
            'view': viewset,
            'format': 'json'
        }

        with patch.object(ModelViewSet, 'get_serializer_context', return_value=parent_context):
            context = viewset.get_serializer_context()

            # Should preserve parent context
            assert 'request' in context
            assert 'view' in context
            assert 'format' in context

            # Should add tenant
            assert 'tenant' in context
            assert context['tenant'] == mock_tenant

    def test_handles_missing_tenant_attribute_on_request(self):
        """Should handle requests without tenant attribute."""
        from smoothschedule.identity.core.mixins import TimezoneContextMixin
        from rest_framework.viewsets import ModelViewSet

        class TestViewSet(TimezoneContextMixin, ModelViewSet):
            pass

        viewset = TestViewSet()
        viewset.request = Mock(spec=['user'])  # No tenant attribute

        with patch.object(ModelViewSet, 'get_serializer_context', return_value={}):
            context = viewset.get_serializer_context()

            # Should add tenant as None instead of crashing
            assert 'tenant' in context
            assert context['tenant'] is None

    def test_integration_with_timezone_serializer(self):
        """Should work with TimezoneSerializerMixin for full timezone support."""
        from smoothschedule.identity.core.mixins import (
            TimezoneContextMixin,
            TimezoneSerializerMixin
        )
        from rest_framework.viewsets import ModelViewSet

        class TestSerializer(TimezoneSerializerMixin, serializers.Serializer):
            title = serializers.CharField()

        class TestViewSet(TimezoneContextMixin, ModelViewSet):
            serializer_class = TestSerializer

        mock_tenant = Mock()
        mock_tenant.timezone = 'America/Denver'

        viewset = TestViewSet()
        viewset.request = Mock()
        viewset.request.tenant = mock_tenant

        mock_obj = Mock()
        mock_obj.title = 'Test Event'

        # Get serializer from viewset
        with patch.object(ModelViewSet, 'get_serializer_context', return_value={'request': viewset.request}):
            serializer = viewset.get_serializer(mock_obj)

            # Serializer should have tenant in context
            assert 'tenant' in serializer.context
            assert serializer.context['tenant'] == mock_tenant

            # Should correctly get timezone
            tz = serializer.get_business_timezone(mock_obj)
            assert tz == 'America/Denver'


class TestTimezoneSerializerEdgeCases:
    """Test edge cases for timezone serialization."""

    def test_handles_various_timezone_formats(self):
        """Should handle various IANA timezone strings."""
        from smoothschedule.identity.core.mixins import TimezoneSerializerMixin

        class TestSerializer(TimezoneSerializerMixin, serializers.Serializer):
            pass

        test_timezones = [
            'UTC',
            'America/New_York',
            'Europe/London',
            'Asia/Tokyo',
            'Australia/Sydney',
            'Pacific/Auckland',
            'Africa/Johannesburg',
        ]

        for tz in test_timezones:
            mock_tenant = Mock()
            mock_tenant.timezone = tz

            serializer = TestSerializer(Mock(), context={'tenant': mock_tenant})

            result = serializer.get_business_timezone(Mock())
            assert result == tz

    def test_handles_empty_string_timezone(self):
        """Should treat empty string as None (use client local time)."""
        from smoothschedule.identity.core.mixins import TimezoneSerializerMixin

        class TestSerializer(TimezoneSerializerMixin, serializers.Serializer):
            pass

        mock_tenant = Mock()
        mock_tenant.timezone = ''

        serializer = TestSerializer(Mock(), context={'tenant': mock_tenant})

        result = serializer.get_business_timezone(Mock())
        assert result is None

    def test_handles_whitespace_only_timezone(self):
        """Should treat whitespace-only string as None."""
        from smoothschedule.identity.core.mixins import TimezoneSerializerMixin

        class TestSerializer(TimezoneSerializerMixin, serializers.Serializer):
            pass

        mock_tenant = Mock()
        mock_tenant.timezone = '   '

        serializer = TestSerializer(Mock(), context={'tenant': mock_tenant})

        result = serializer.get_business_timezone(Mock())

        # Empty after strip should return None
        assert result is None or result == '   '  # Depending on implementation

    def test_nested_serializer_inherits_context(self):
        """Should pass context to nested serializers."""
        from smoothschedule.identity.core.mixins import TimezoneSerializerMixin

        class NestedSerializer(TimezoneSerializerMixin, serializers.Serializer):
            detail = serializers.CharField()

        class ParentSerializer(TimezoneSerializerMixin, serializers.Serializer):
            title = serializers.CharField()
            nested = NestedSerializer()

        mock_tenant = Mock()
        mock_tenant.timezone = 'America/New_York'

        mock_obj = Mock()
        mock_obj.title = 'Parent'
        mock_obj.nested = Mock()
        mock_obj.nested.detail = 'Nested'

        serializer = ParentSerializer(mock_obj, context={'tenant': mock_tenant})

        # Both parent and nested should get timezone from context
        assert serializer.get_business_timezone(mock_obj) == 'America/New_York'

        # Nested serializer should have context passed down
        nested_field = serializer.fields['nested']
        assert 'tenant' in nested_field.context
        assert nested_field.context['tenant'] == mock_tenant

    def test_list_serializer_context_propagation(self):
        """Should propagate context to many=True serializers."""
        from smoothschedule.identity.core.mixins import TimezoneSerializerMixin

        class ItemSerializer(TimezoneSerializerMixin, serializers.Serializer):
            name = serializers.CharField()

        mock_tenant = Mock()
        mock_tenant.timezone = 'Europe/Paris'

        items = [
            Mock(name='Item 1'),
            Mock(name='Item 2'),
            Mock(name='Item 3'),
        ]

        serializer = ItemSerializer(items, many=True, context={'tenant': mock_tenant})

        # Context should be propagated to child serializers
        assert serializer.context['tenant'] == mock_tenant

        # Each child serializer should be able to get timezone
        # Test with first item
        child_serializer = ItemSerializer(items[0], context={'tenant': mock_tenant})
        assert child_serializer.get_business_timezone(items[0]) == 'Europe/Paris'
