"""
Unit tests for platform admin serializers.

Tests serializer field configurations, validation logic, and custom methods
using mocks to avoid database hits.
"""
from datetime import datetime, timedelta
from decimal import Decimal
from unittest.mock import Mock, MagicMock, patch
import pytest
from django.utils import timezone
from rest_framework import serializers
from rest_framework.test import APIRequestFactory

from ..serializers import (
    PlatformSettingsSerializer,
    StripeKeysUpdateSerializer,
    OAuthSettingsSerializer,
    OAuthSettingsResponseSerializer,
    SubscriptionPlanSerializer,
    SubscriptionPlanCreateSerializer,
    TenantSerializer,
    TenantUpdateSerializer,
    TenantCreateSerializer,
    PlatformUserSerializer,
    PlatformMetricsSerializer,
    TenantInvitationSerializer,
    TenantInvitationCreateSerializer,
    TenantInvitationAcceptSerializer,
    TenantInvitationDetailSerializer,
    AssignedUserSerializer,
    PlatformEmailAddressListSerializer,
    PlatformEmailAddressSerializer,
    PlatformEmailAddressCreateSerializer,
    PlatformEmailAddressUpdateSerializer,
)


class TestPlatformSettingsSerializer:
    """Tests for PlatformSettingsSerializer."""

    def test_all_fields_are_read_only(self):
        """Verify all fields are read-only."""
        serializer = PlatformSettingsSerializer()

        # Check that regular fields are read-only
        assert serializer.fields['stripe_account_id'].read_only
        assert serializer.fields['stripe_account_name'].read_only
        assert serializer.fields['stripe_keys_validated_at'].read_only
        assert serializer.fields['stripe_validation_error'].read_only
        assert serializer.fields['email_check_interval_minutes'].read_only
        assert serializer.fields['updated_at'].read_only

    def test_get_stripe_secret_key_masked(self):
        """Test masking of Stripe secret key."""
        mock_obj = Mock()
        mock_obj.mask_key.return_value = 'sk_test_1234...xyz1'
        mock_obj.get_stripe_secret_key.return_value = 'sk_test_1234567890abcdefghijklmnopqrstuvwxyz1'

        serializer = PlatformSettingsSerializer()
        result = serializer.get_stripe_secret_key_masked(mock_obj)

        mock_obj.get_stripe_secret_key.assert_called_once()
        mock_obj.mask_key.assert_called_once_with('sk_test_1234567890abcdefghijklmnopqrstuvwxyz1')
        assert result == 'sk_test_1234...xyz1'

    def test_get_stripe_publishable_key_masked(self):
        """Test masking of Stripe publishable key."""
        mock_obj = Mock()
        mock_obj.mask_key.return_value = 'pk_test_1234...xyz2'
        mock_obj.get_stripe_publishable_key.return_value = 'pk_test_1234567890abcdefghijklmnopqrstuvwxyz2'

        serializer = PlatformSettingsSerializer()
        result = serializer.get_stripe_publishable_key_masked(mock_obj)

        mock_obj.get_stripe_publishable_key.assert_called_once()
        mock_obj.mask_key.assert_called_once_with('pk_test_1234567890abcdefghijklmnopqrstuvwxyz2')
        assert result == 'pk_test_1234...xyz2'

    def test_get_stripe_webhook_secret_masked(self):
        """Test masking of Stripe webhook secret."""
        mock_obj = Mock()
        mock_obj.mask_key.return_value = 'whsec_1234...xyz3'
        mock_obj.get_stripe_webhook_secret.return_value = 'whsec_1234567890abcdefghijklmnopqrstuvwxyz3'

        serializer = PlatformSettingsSerializer()
        result = serializer.get_stripe_webhook_secret_masked(mock_obj)

        mock_obj.get_stripe_webhook_secret.assert_called_once()
        mock_obj.mask_key.assert_called_once_with('whsec_1234567890abcdefghijklmnopqrstuvwxyz3')
        assert result == 'whsec_1234...xyz3'

    def test_get_has_stripe_keys(self):
        """Test has_stripe_keys method field."""
        mock_obj = Mock()
        mock_obj.has_stripe_keys.return_value = True

        serializer = PlatformSettingsSerializer()
        result = serializer.get_has_stripe_keys(mock_obj)

        mock_obj.has_stripe_keys.assert_called_once()
        assert result is True

    def test_get_stripe_keys_from_env(self):
        """Test stripe_keys_from_env method field."""
        mock_obj = Mock()
        mock_obj.stripe_keys_from_env.return_value = False

        serializer = PlatformSettingsSerializer()
        result = serializer.get_stripe_keys_from_env(mock_obj)

        mock_obj.stripe_keys_from_env.assert_called_once()
        assert result is False


class TestStripeKeysUpdateSerializer:
    """Tests for StripeKeysUpdateSerializer."""

    def test_all_fields_optional(self):
        """Verify all fields are optional."""
        serializer = StripeKeysUpdateSerializer()

        assert not serializer.fields['stripe_secret_key'].required
        assert not serializer.fields['stripe_publishable_key'].required
        assert not serializer.fields['stripe_webhook_secret'].required

    def test_all_fields_allow_blank(self):
        """Verify all fields allow blank strings."""
        serializer = StripeKeysUpdateSerializer()

        assert serializer.fields['stripe_secret_key'].allow_blank
        assert serializer.fields['stripe_publishable_key'].allow_blank
        assert serializer.fields['stripe_webhook_secret'].allow_blank

    def test_valid_data_with_all_fields(self):
        """Test serializer with all fields provided."""
        data = {
            'stripe_secret_key': 'sk_test_12345',
            'stripe_publishable_key': 'pk_test_12345',
            'stripe_webhook_secret': 'whsec_12345'
        }
        serializer = StripeKeysUpdateSerializer(data=data)

        assert serializer.is_valid()
        assert serializer.validated_data == data

    def test_valid_data_with_partial_fields(self):
        """Test serializer with only some fields."""
        data = {'stripe_secret_key': 'sk_test_12345'}
        serializer = StripeKeysUpdateSerializer(data=data)

        assert serializer.is_valid()
        assert serializer.validated_data == data

    def test_valid_data_with_empty_dict(self):
        """Test serializer accepts empty data."""
        data = {}
        serializer = StripeKeysUpdateSerializer(data=data)

        assert serializer.is_valid()


class TestOAuthSettingsSerializer:
    """Tests for OAuthSettingsSerializer."""

    def test_boolean_fields_have_defaults(self):
        """Verify boolean fields have default values."""
        serializer = OAuthSettingsSerializer()

        assert serializer.fields['oauth_allow_registration'].default is True
        assert serializer.fields['oauth_google_enabled'].default is False
        assert serializer.fields['oauth_apple_enabled'].default is False
        assert serializer.fields['oauth_facebook_enabled'].default is False
        assert serializer.fields['oauth_linkedin_enabled'].default is False
        assert serializer.fields['oauth_microsoft_enabled'].default is False
        assert serializer.fields['oauth_twitter_enabled'].default is False
        assert serializer.fields['oauth_twitch_enabled'].default is False

    def test_string_fields_optional_and_allow_blank(self):
        """Verify string fields are optional and allow blank."""
        serializer = OAuthSettingsSerializer()

        assert not serializer.fields['oauth_google_client_id'].required
        assert serializer.fields['oauth_google_client_id'].allow_blank
        assert not serializer.fields['oauth_google_client_secret'].required
        assert serializer.fields['oauth_google_client_secret'].allow_blank

    def test_valid_full_oauth_configuration(self):
        """Test complete OAuth configuration."""
        data = {
            'oauth_allow_registration': True,
            'oauth_google_enabled': True,
            'oauth_google_client_id': 'google_client_123',
            'oauth_google_client_secret': 'google_secret_456',
            'oauth_apple_enabled': True,
            'oauth_apple_client_id': 'apple_client_123',
            'oauth_apple_client_secret': 'apple_secret_456',
            'oauth_apple_team_id': 'team_789',
            'oauth_apple_key_id': 'key_012',
        }
        serializer = OAuthSettingsSerializer(data=data)

        assert serializer.is_valid()
        assert serializer.validated_data['oauth_google_enabled'] is True
        assert serializer.validated_data['oauth_google_client_id'] == 'google_client_123'


class TestOAuthSettingsResponseSerializer:
    """Tests for OAuthSettingsResponseSerializer."""

    def test_has_all_provider_fields(self):
        """Verify serializer has fields for all OAuth providers."""
        serializer = OAuthSettingsResponseSerializer()

        assert 'oauth_allow_registration' in serializer.fields
        assert 'google' in serializer.fields
        assert 'apple' in serializer.fields
        assert 'facebook' in serializer.fields
        assert 'linkedin' in serializer.fields
        assert 'microsoft' in serializer.fields
        assert 'twitter' in serializer.fields
        assert 'twitch' in serializer.fields

    def test_provider_fields_are_dict_fields(self):
        """Verify provider fields are DictFields."""
        serializer = OAuthSettingsResponseSerializer()

        assert isinstance(serializer.fields['google'], serializers.DictField)
        assert isinstance(serializer.fields['apple'], serializers.DictField)


class TestSubscriptionPlanSerializer:
    """Tests for SubscriptionPlanSerializer."""

    def test_read_only_fields(self):
        """Verify id, created_at, and updated_at are read-only."""
        serializer = SubscriptionPlanSerializer()

        assert 'id' in serializer.Meta.read_only_fields
        assert 'created_at' in serializer.Meta.read_only_fields
        assert 'updated_at' in serializer.Meta.read_only_fields

    def test_includes_all_required_fields(self):
        """Verify all expected fields are present."""
        serializer = SubscriptionPlanSerializer()

        expected_fields = [
            'id', 'name', 'description', 'plan_type',
            'stripe_product_id', 'stripe_price_id',
            'price_monthly', 'price_yearly',
            'features', 'limits', 'permissions',
            'transaction_fee_percent', 'transaction_fee_fixed',
            'sms_enabled', 'sms_price_per_message_cents',
            'masked_calling_enabled', 'masked_calling_price_per_minute_cents',
            'proxy_number_enabled', 'proxy_number_monthly_fee_cents',
            'contracts_enabled',
            'default_auto_reload_enabled', 'default_auto_reload_threshold_cents',
            'default_auto_reload_amount_cents',
            'is_active', 'is_public', 'is_most_popular', 'show_price',
            'created_at', 'updated_at'
        ]

        for field in expected_fields:
            assert field in serializer.Meta.fields


class TestSubscriptionPlanCreateSerializer:
    """Tests for SubscriptionPlanCreateSerializer."""

    def test_create_stripe_product_is_write_only(self):
        """Verify create_stripe_product is write-only."""
        serializer = SubscriptionPlanCreateSerializer()

        assert serializer.fields['create_stripe_product'].write_only

    def test_create_stripe_product_defaults_to_false(self):
        """Verify create_stripe_product defaults to False."""
        serializer = SubscriptionPlanCreateSerializer()

        assert serializer.fields['create_stripe_product'].default is False

    def test_create_without_stripe_integration(self):
        """Test creating plan without Stripe integration."""
        data = {
            'name': 'Test Plan',
            'description': 'A test plan',
            'plan_type': 'base',
            'price_monthly': Decimal('29.99'),
            'create_stripe_product': False,
        }

        serializer = SubscriptionPlanCreateSerializer(data=data)
        assert serializer.is_valid()

        # Mock the parent create method
        with patch.object(serializers.ModelSerializer, 'create') as mock_create:
            mock_create.return_value = Mock(id=1, name='Test Plan')
            result = serializer.create(serializer.validated_data)

            # create_stripe_product should be removed from validated_data
            call_args = mock_create.call_args[0][0]
            assert 'create_stripe_product' not in call_args

    def test_create_with_stripe_integration(self):
        """Test creating plan with Stripe integration."""
        data = {
            'name': 'Test Plan',
            'description': 'A test plan',
            'plan_type': 'base',
            'price_monthly': Decimal('29.99'),
            'create_stripe_product': True,
        }

        serializer = SubscriptionPlanCreateSerializer(data=data)
        assert serializer.is_valid()

        # Mock imports that happen inside create() - patch at source, not destination
        with patch('stripe.Product') as mock_product_class:
            with patch('stripe.Price') as mock_price_class:
                with patch('django.conf.settings') as mock_settings:
                    mock_settings.STRIPE_SECRET_KEY = 'sk_test_12345'

                    mock_product = Mock()
                    mock_product.id = 'prod_12345'
                    mock_product_class.create.return_value = mock_product

                    mock_price = Mock()
                    mock_price.id = 'price_12345'
                    mock_price_class.create.return_value = mock_price

                    with patch.object(serializers.ModelSerializer, 'create') as mock_create:
                        mock_create.return_value = Mock(id=1, name='Test Plan')
                        result = serializer.create(serializer.validated_data)

                        # Verify Stripe API calls
                        mock_product_class.create.assert_called_once_with(
                            name='Test Plan',
                            description='A test plan',
                            metadata={'plan_type': 'base'}
                        )
                        mock_price_class.create.assert_called_once_with(
                            product='prod_12345',
                            unit_amount=2999,  # $29.99 * 100
                            currency='usd',
                            recurring={'interval': 'month'}
                        )

    def test_create_stripe_product_without_price(self):
        """Test that Stripe product is not created if price_monthly is missing."""
        data = {
            'name': 'Test Plan',
            'create_stripe_product': True,
            # No price_monthly
        }

        serializer = SubscriptionPlanCreateSerializer(data=data)
        assert serializer.is_valid()

        with patch.object(serializers.ModelSerializer, 'create') as mock_create:
            mock_create.return_value = Mock(id=1, name='Test Plan')
            result = serializer.create(serializer.validated_data)

            # Verify plan was created
            assert result is not None

    def test_create_handles_stripe_error(self):
        """Test that Stripe errors are properly handled."""
        data = {
            'name': 'Test Plan',
            'price_monthly': Decimal('29.99'),
            'create_stripe_product': True,
        }

        serializer = SubscriptionPlanCreateSerializer(data=data)
        assert serializer.is_valid()

        # Create a StripeError exception class
        class StripeError(Exception):
            pass

        # Mock stripe module to raise error - patch at source
        with patch('stripe.Product') as mock_product_class:
            with patch('stripe.error') as mock_error:
                with patch('django.conf.settings') as mock_settings:
                    mock_settings.STRIPE_SECRET_KEY = 'sk_test_12345'
                    mock_error.StripeError = StripeError
                    mock_product_class.create.side_effect = StripeError('Stripe API error')

                    with pytest.raises(serializers.ValidationError) as exc_info:
                        serializer.create(serializer.validated_data)

                    assert 'stripe' in exc_info.value.detail


class TestTenantSerializer:
    """Tests for TenantSerializer."""

    def test_all_fields_read_only(self):
        """Verify all fields are read-only."""
        serializer = TenantSerializer()

        assert set(serializer.Meta.fields) == set(serializer.Meta.read_only_fields)

    def test_get_subdomain_with_primary_domain(self):
        """Test subdomain extraction from primary domain."""
        mock_domain = Mock()
        mock_domain.domain = 'business1.lvh.me'

        mock_tenant = Mock()
        mock_tenant.domains.filter.return_value.first.return_value = mock_domain
        mock_tenant.schema_name = 'fallback'

        serializer = TenantSerializer()
        result = serializer.get_subdomain(mock_tenant)

        assert result == 'business1'

    def test_get_subdomain_without_primary_domain(self):
        """Test subdomain falls back to schema_name."""
        mock_tenant = Mock()
        mock_tenant.domains.filter.return_value.first.return_value = None
        mock_tenant.schema_name = 'mybusiness'

        serializer = TenantSerializer()
        result = serializer.get_subdomain(mock_tenant)

        assert result == 'mybusiness'

    def test_get_user_count_returns_zero(self):
        """Test user_count returns 0 (optimization placeholder)."""
        mock_tenant = Mock()

        serializer = TenantSerializer()
        result = serializer.get_user_count(mock_tenant)

        assert result == 0

    @patch('smoothschedule.platform.admin.serializers.User')
    def test_get_owner_with_valid_owner(self, mock_user_model):
        """Test get_owner returns owner details."""
        mock_owner = Mock()
        mock_owner.id = 123
        mock_owner.username = 'owner@example.com'
        mock_owner.full_name = 'John Doe'
        mock_owner.email = 'owner@example.com'
        mock_owner.role = 'TENANT_OWNER'
        mock_owner.email_verified = True

        mock_user_model.objects.filter.return_value.first.return_value = mock_owner
        mock_user_model.Role.TENANT_OWNER = 'TENANT_OWNER'

        mock_tenant = Mock()

        serializer = TenantSerializer()
        result = serializer.get_owner(mock_tenant)

        assert result is not None
        assert result['id'] == 123
        assert result['email'] == 'owner@example.com'
        assert result['full_name'] == 'John Doe'
        assert result['email_verified'] is True

    @patch('smoothschedule.platform.admin.serializers.User')
    def test_get_owner_without_owner(self, mock_user_model):
        """Test get_owner returns None when no owner exists."""
        mock_user_model.objects.filter.return_value.first.return_value = None
        mock_user_model.Role.TENANT_OWNER = 'TENANT_OWNER'

        mock_tenant = Mock()

        serializer = TenantSerializer()
        result = serializer.get_owner(mock_tenant)

        assert result is None

    @patch('smoothschedule.platform.admin.serializers.User')
    def test_get_owner_handles_exception(self, mock_user_model):
        """Test get_owner returns None on exception."""
        mock_user_model.objects.filter.side_effect = Exception('Database error')
        mock_user_model.Role.TENANT_OWNER = 'TENANT_OWNER'

        mock_tenant = Mock()

        serializer = TenantSerializer()
        result = serializer.get_owner(mock_tenant)

        assert result is None


class TestTenantUpdateSerializer:
    """Tests for TenantUpdateSerializer."""

    def test_id_is_read_only(self):
        """Verify id is read-only."""
        serializer = TenantUpdateSerializer()

        assert 'id' in serializer.Meta.read_only_fields

    def test_includes_expected_fields(self):
        """Verify expected fields are included.

        Note: Permission fields are now managed via billing system,
        not directly on the Tenant model.
        """
        serializer = TenantUpdateSerializer()

        expected_fields = ['id', 'name', 'is_active', 'contact_email', 'phone']

        for field in expected_fields:
            assert field in serializer.Meta.fields

    def test_update_saves_in_public_schema(self):
        """Test update method saves in public schema."""
        mock_instance = Mock()
        mock_instance.save = Mock()

        validated_data = {
            'name': 'Updated Name',
            'is_active': False,
            'contact_email': 'test@example.com',
        }

        serializer = TenantUpdateSerializer()

        # Mock schema_context which is imported inside the update method
        with patch('django_tenants.utils.schema_context') as mock_schema_context:
            result = serializer.update(mock_instance, validated_data)

            # Verify attributes were set
            assert mock_instance.name == 'Updated Name'
            assert mock_instance.is_active is False
            assert mock_instance.contact_email == 'test@example.com'

            # Verify schema_context was called with 'public'
            mock_schema_context.assert_called_once_with('public')

            # Verify save was called
            mock_instance.save.assert_called_once()


class TestTenantCreateSerializer:
    """Tests for TenantCreateSerializer."""

    def test_required_fields(self):
        """Verify name and subdomain are required."""
        serializer = TenantCreateSerializer()

        assert not serializer.fields['name'].required or serializer.fields['name'].required
        assert not serializer.fields['subdomain'].required or serializer.fields['subdomain'].required

    def test_optional_fields_have_defaults(self):
        """Verify optional fields have sensible defaults.

        Note: subscription_tier, max_users, max_resources, etc. are now
        managed via the billing system, not serializer fields.
        """
        serializer = TenantCreateSerializer()

        assert serializer.fields['is_active'].default is True

    def test_owner_password_is_write_only(self):
        """Verify owner_password is write-only."""
        serializer = TenantCreateSerializer()

        assert serializer.fields['owner_password'].write_only

    @patch('smoothschedule.platform.admin.serializers.Tenant')
    @patch('smoothschedule.platform.admin.serializers.Domain')
    def test_validate_subdomain_lowercase_conversion(self, mock_domain, mock_tenant):
        """Test subdomain is converted to lowercase."""
        mock_tenant.objects.filter.return_value.exists.return_value = False
        mock_domain.objects.filter.return_value.exists.return_value = False

        serializer = TenantCreateSerializer()
        result = serializer.validate_subdomain('MyBusiness')
        assert result == 'mybusiness'

    def test_validate_subdomain_invalid_format(self):
        """Test subdomain validation rejects invalid formats."""
        serializer = TenantCreateSerializer()

        invalid_subdomains = [
            '123business',  # Can't start with number
            'business_name',  # No underscores
            'business.name',  # No dots
            'BUSINESS',  # Only lowercase after conversion, but starts correctly
        ]

        for subdomain in ['123business', 'business_name', 'business.name']:
            with pytest.raises(serializers.ValidationError):
                serializer.validate_subdomain(subdomain)

    @patch('smoothschedule.platform.admin.serializers.Tenant')
    def test_validate_subdomain_already_exists_as_schema(self, mock_tenant_model):
        """Test subdomain validation rejects existing schema_name."""
        mock_tenant_model.objects.filter.return_value.exists.return_value = True

        serializer = TenantCreateSerializer()

        with pytest.raises(serializers.ValidationError) as exc_info:
            serializer.validate_subdomain('existing')

        assert 'already taken' in str(exc_info.value)

    @patch('smoothschedule.platform.admin.serializers.Tenant')
    @patch('smoothschedule.platform.admin.serializers.Domain')
    def test_validate_subdomain_already_exists_as_domain(self, mock_domain_model, mock_tenant_model):
        """Test subdomain validation rejects existing domain."""
        mock_tenant_model.objects.filter.return_value.exists.return_value = False
        mock_domain_model.objects.filter.return_value.exists.return_value = True

        serializer = TenantCreateSerializer()

        with pytest.raises(serializers.ValidationError) as exc_info:
            serializer.validate_subdomain('existing')

        assert 'already taken' in str(exc_info.value)

    @patch('smoothschedule.platform.admin.serializers.Tenant')
    @patch('smoothschedule.platform.admin.serializers.Domain')
    def test_validate_subdomain_reserved(self, mock_domain_model, mock_tenant_model):
        """Test subdomain validation rejects reserved names."""
        mock_tenant_model.objects.filter.return_value.exists.return_value = False
        mock_domain_model.objects.filter.return_value.exists.return_value = False

        serializer = TenantCreateSerializer()

        reserved_names = ['www', 'api', 'admin', 'platform', 'public']

        for name in reserved_names:
            with pytest.raises(serializers.ValidationError) as exc_info:
                serializer.validate_subdomain(name)
            assert 'reserved' in str(exc_info.value)

    @patch('smoothschedule.platform.admin.serializers.User')
    def test_validate_requires_owner_name_with_email(self, mock_user_model):
        """Test validation requires owner_name when owner_email is provided."""
        mock_user_model.objects.filter.return_value.exists.return_value = False

        serializer = TenantCreateSerializer()

        attrs = {
            'name': 'My Business',
            'subdomain': 'mybiz',
            'owner_email': 'owner@example.com',
            'owner_password': 'secure123',
            # Missing owner_name
        }

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

        assert 'owner_name' in exc_info.value.detail

    @patch('smoothschedule.platform.admin.serializers.User')
    def test_validate_requires_owner_password_with_email(self, mock_user_model):
        """Test validation requires owner_password when owner_email is provided."""
        mock_user_model.objects.filter.return_value.exists.return_value = False

        serializer = TenantCreateSerializer()

        attrs = {
            'name': 'My Business',
            'subdomain': 'mybiz',
            'owner_email': 'owner@example.com',
            'owner_name': 'John Doe',
            # Missing owner_password
        }

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

        assert 'owner_password' in exc_info.value.detail

    @patch('smoothschedule.platform.admin.serializers.User')
    def test_validate_rejects_existing_owner_email(self, mock_user_model):
        """Test validation rejects email that already exists."""
        mock_user_model.objects.filter.return_value.exists.return_value = True

        serializer = TenantCreateSerializer()

        attrs = {
            'name': 'My Business',
            'subdomain': 'mybiz',
            'owner_email': 'existing@example.com',
            'owner_name': 'John Doe',
            'owner_password': 'secure123',
        }

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

        assert 'owner_email' in exc_info.value.detail

    def test_create_tenant_without_owner(self):
        """Test creating tenant without owner user."""
        validated_data = {
            'name': 'My Business',
            'subdomain': 'mybiz',
            'subscription_tier': 'STARTER',
            'is_active': True,
            'max_users': 10,
            'max_resources': 20,
            'contact_email': 'contact@example.com',
            'phone': '555-1234',
            'can_manage_oauth_credentials': False,
        }

        serializer = TenantCreateSerializer()

        # Mock models that are imported at top of serializers.py - patch in serializers namespace
        with patch('django.db.transaction'):
            with patch('django_tenants.utils.schema_context') as mock_schema_context:
                with patch('smoothschedule.platform.admin.serializers.Tenant') as mock_tenant_model:
                    with patch('smoothschedule.platform.admin.serializers.Domain') as mock_domain_model:
                        mock_tenant = Mock()
                        mock_tenant.id = 1
                        mock_tenant_model.objects.create.return_value = mock_tenant

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

                        # Verify tenant was created with correct data
                        mock_tenant_model.objects.create.assert_called_once()
                        create_call = mock_tenant_model.objects.create.call_args[1]
                        assert create_call['schema_name'] == 'mybiz'
                        assert create_call['name'] == 'My Business'

                        # Verify domain was created
                        mock_domain_model.objects.create.assert_called_once()
                        domain_call = mock_domain_model.objects.create.call_args[1]
                        assert domain_call['domain'] == 'mybiz.lvh.me'
                        assert domain_call['is_primary'] is True

    def test_create_tenant_with_owner(self):
        """Test creating tenant with owner user."""
        validated_data = {
            'name': 'My Business',
            'subdomain': 'mybiz',
            'subscription_tier': 'STARTER',
            'is_active': True,
            'max_users': 10,
            'max_resources': 20,
            'contact_email': 'contact@example.com',
            'phone': '555-1234',
            'can_manage_oauth_credentials': False,
            'owner_email': 'owner@example.com',
            'owner_name': 'John Doe',
            'owner_password': 'secure123',
        }

        serializer = TenantCreateSerializer()

        # Mock models that are imported at top of serializers.py - patch in serializers namespace
        with patch('django.db.transaction'):
            with patch('django_tenants.utils.schema_context'):
                with patch('smoothschedule.platform.admin.serializers.Tenant') as mock_tenant_model:
                    with patch('smoothschedule.platform.admin.serializers.Domain'):
                        with patch('smoothschedule.platform.admin.serializers.User') as mock_user_model:
                            mock_tenant = Mock()
                            mock_tenant.id = 1
                            mock_tenant_model.objects.create.return_value = mock_tenant

                            mock_owner = Mock()
                            mock_owner.id = 99
                            mock_user_model.objects.create_user.return_value = mock_owner
                            mock_user_model.Role.TENANT_OWNER = 'TENANT_OWNER'

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

                            # Verify owner was created
                            mock_user_model.objects.create_user.assert_called_once()
                            owner_call = mock_user_model.objects.create_user.call_args[1]
                            assert owner_call['username'] == 'owner@example.com'
                            assert owner_call['email'] == 'owner@example.com'
                            assert owner_call['password'] == 'secure123'
                            assert owner_call['first_name'] == 'John'
                            assert owner_call['last_name'] == 'Doe'
                            assert owner_call['role'] == 'TENANT_OWNER'

    def test_create_tenant_splits_owner_name_correctly(self):
        """Test owner name is split into first and last name."""
        # Test with multi-word last name
        validated_data = {
            'name': 'My Business',
            'subdomain': 'mybiz',
            'owner_email': 'owner@example.com',
            'owner_name': 'John Michael Doe',
            'owner_password': 'secure123',
        }

        serializer = TenantCreateSerializer()

        # Mock models that are imported at top of serializers.py - patch in serializers namespace
        with patch('django.db.transaction'):
            with patch('django_tenants.utils.schema_context'):
                with patch('smoothschedule.platform.admin.serializers.Tenant') as mock_tenant_model:
                    with patch('smoothschedule.platform.admin.serializers.Domain'):
                        with patch('smoothschedule.platform.admin.serializers.User') as mock_user_model:
                            mock_tenant = Mock()
                            mock_tenant_model.objects.create.return_value = mock_tenant
                            mock_user_model.objects.create_user.return_value = Mock()
                            mock_user_model.Role.TENANT_OWNER = 'TENANT_OWNER'

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

                            owner_call = mock_user_model.objects.create_user.call_args[1]
                            assert owner_call['first_name'] == 'John'
                            assert owner_call['last_name'] == 'Michael Doe'


class TestPlatformUserSerializer:
    """Tests for PlatformUserSerializer."""

    def test_all_fields_read_only(self):
        """Verify all fields are read-only."""
        serializer = PlatformUserSerializer()

        assert set(serializer.Meta.fields) == set(serializer.Meta.read_only_fields)

    def test_get_role_lowercase(self):
        """Test role is converted to lowercase."""
        mock_user = Mock()
        mock_user.role = 'TENANT_OWNER'

        serializer = PlatformUserSerializer()
        result = serializer.get_role(mock_user)

        assert result == 'tenant_owner'

    def test_get_full_name(self):
        """Test get_full_name calls user's full_name property."""
        mock_user = Mock()
        mock_user.full_name = 'John Doe'

        serializer = PlatformUserSerializer()
        result = serializer.get_full_name(mock_user)

        assert result == 'John Doe'

    def test_get_business_with_tenant(self):
        """Test get_business returns tenant ID."""
        mock_tenant = Mock()
        mock_tenant.id = 123

        mock_user = Mock()
        mock_user.tenant = mock_tenant

        serializer = PlatformUserSerializer()
        result = serializer.get_business(mock_user)

        assert result == 123

    def test_get_business_without_tenant(self):
        """Test get_business returns None when no tenant."""
        mock_user = Mock()
        mock_user.tenant = None

        serializer = PlatformUserSerializer()
        result = serializer.get_business(mock_user)

        assert result is None

    def test_get_business_name_with_tenant(self):
        """Test get_business_name returns tenant name."""
        mock_tenant = Mock()
        mock_tenant.name = 'My Business'

        mock_user = Mock()
        mock_user.tenant = mock_tenant

        serializer = PlatformUserSerializer()
        result = serializer.get_business_name(mock_user)

        assert result == 'My Business'

    def test_get_business_subdomain_with_primary_domain(self):
        """Test get_business_subdomain extracts subdomain from primary domain."""
        mock_domain = Mock()
        mock_domain.domain = 'mybiz.lvh.me'

        mock_tenant = Mock()
        mock_tenant.domains.filter.return_value.first.return_value = mock_domain
        mock_tenant.schema_name = 'fallback'

        mock_user = Mock()
        mock_user.tenant = mock_tenant

        serializer = PlatformUserSerializer()
        result = serializer.get_business_subdomain(mock_user)

        assert result == 'mybiz'


class TestPlatformMetricsSerializer:
    """Tests for PlatformMetricsSerializer."""

    def test_has_all_metric_fields(self):
        """Verify all metric fields are present."""
        serializer = PlatformMetricsSerializer()

        assert 'total_tenants' in serializer.fields
        assert 'active_tenants' in serializer.fields
        assert 'total_users' in serializer.fields
        assert 'mrr' in serializer.fields
        assert 'growth_rate' in serializer.fields

    def test_mrr_is_decimal_field(self):
        """Verify MRR is a DecimalField with correct precision."""
        serializer = PlatformMetricsSerializer()

        mrr_field = serializer.fields['mrr']
        assert isinstance(mrr_field, serializers.DecimalField)
        assert mrr_field.max_digits == 10
        assert mrr_field.decimal_places == 2


class TestTenantInvitationSerializer:
    """Tests for TenantInvitationSerializer."""

    def test_read_only_fields(self):
        """Verify read-only fields are configured correctly."""
        serializer = TenantInvitationSerializer()

        read_only = serializer.Meta.read_only_fields

        assert 'id' in read_only
        assert 'token' in read_only
        assert 'status' in read_only
        assert 'created_at' in read_only
        assert 'expires_at' in read_only
        assert 'accepted_at' in read_only
        assert 'invited_by_email' in read_only

    def test_invited_by_is_write_only(self):
        """Verify invited_by is write-only."""
        serializer = TenantInvitationSerializer()

        assert serializer.Meta.extra_kwargs['invited_by']['write_only']

    def test_validate_permissions_valid_dict(self):
        """Test permissions validation accepts valid dictionary."""
        serializer = TenantInvitationSerializer()

        valid_permissions = {
            'can_accept_payments': True,
            'can_use_custom_domain': False,
        }

        result = serializer.validate_permissions(valid_permissions)
        assert result == valid_permissions

    def test_validate_permissions_rejects_non_dict(self):
        """Test permissions validation rejects non-dictionary."""
        serializer = TenantInvitationSerializer()

        with pytest.raises(serializers.ValidationError) as exc_info:
            serializer.validate_permissions(['not', 'a', 'dict'])

        assert 'must be a dictionary' in str(exc_info.value)

    def test_validate_permissions_rejects_non_boolean_values(self):
        """Test permissions validation rejects non-boolean values."""
        serializer = TenantInvitationSerializer()

        invalid_permissions = {
            'can_accept_payments': 'yes',  # Should be boolean
        }

        with pytest.raises(serializers.ValidationError) as exc_info:
            serializer.validate_permissions(invalid_permissions)

        assert 'must be a boolean' in str(exc_info.value)

    def test_validate_limits_valid_dict(self):
        """Test limits validation accepts valid dictionary."""
        serializer = TenantInvitationSerializer()

        valid_limits = {
            'can_add_video_conferencing': True,
            'max_event_types': 10,
            'max_calendars_connected': None,
        }

        result = serializer.validate_limits(valid_limits)
        assert result == valid_limits

    def test_validate_limits_rejects_non_dict(self):
        """Test limits validation rejects non-dictionary."""
        serializer = TenantInvitationSerializer()

        with pytest.raises(serializers.ValidationError) as exc_info:
            serializer.validate_limits('not a dict')

        assert 'must be a dictionary' in str(exc_info.value)

    def test_validate_limits_boolean_keys(self):
        """Test limits validation checks boolean field types."""
        serializer = TenantInvitationSerializer()

        invalid_limits = {
            'can_add_video_conferencing': 'yes',  # Should be boolean
        }

        with pytest.raises(serializers.ValidationError) as exc_info:
            serializer.validate_limits(invalid_limits)

        assert 'must be a boolean' in str(exc_info.value)

    def test_validate_limits_integer_keys(self):
        """Test limits validation checks integer field types."""
        serializer = TenantInvitationSerializer()

        invalid_limits = {
            'max_event_types': 'ten',  # Should be integer or null
        }

        with pytest.raises(serializers.ValidationError) as exc_info:
            serializer.validate_limits(invalid_limits)

        assert 'must be an integer' in str(exc_info.value)


class TestTenantInvitationCreateSerializer:
    """Tests for TenantInvitationCreateSerializer."""

    def test_validate_limits_same_as_parent(self):
        """Test limits validation works the same as parent serializer."""
        serializer = TenantInvitationCreateSerializer()

        valid_limits = {
            'can_download_logs': False,
            'max_event_types': 5,
        }

        result = serializer.validate_limits(valid_limits)
        assert result == valid_limits

    def test_create_sets_invited_by_from_context(self):
        """Test create method sets invited_by from request context."""
        mock_user = Mock()
        mock_user.id = 789

        mock_request = Mock()
        mock_request.user = mock_user

        validated_data = {
            'email': 'invitee@example.com',
            'suggested_business_name': 'New Business',
        }

        serializer = TenantInvitationCreateSerializer(
            data=validated_data,
            context={'request': mock_request}
        )

        # Mock the parent create method
        with patch.object(serializers.ModelSerializer, 'create') as mock_create:
            mock_create.return_value = Mock()
            serializer.is_valid(raise_exception=True)
            result = serializer.create(serializer.validated_data)

            # Verify invited_by was added to validated_data
            call_args = mock_create.call_args[0][0]
            assert call_args['invited_by'] == mock_user


class TestTenantInvitationAcceptSerializer:
    """Tests for TenantInvitationAcceptSerializer."""

    def test_password_is_write_only(self):
        """Verify password is write-only."""
        serializer = TenantInvitationAcceptSerializer()

        assert serializer.fields['password'].write_only

    def test_validate_subdomain_valid_format(self):
        """Test subdomain validation accepts valid format."""
        # Mock the models that are imported inside validate_subdomain - use actual paths
        with patch('smoothschedule.identity.core.models.Tenant') as mock_tenant_class:
            with patch('smoothschedule.identity.core.models.Domain') as mock_domain_class:
                mock_tenant_class.objects.filter.return_value.exists.return_value = False
                mock_domain_class.objects.filter.return_value.exists.return_value = False

                serializer = TenantInvitationAcceptSerializer()
                result = serializer.validate_subdomain('mybusiness')

                assert result == 'mybusiness'

    def test_validate_subdomain_converts_to_lowercase(self):
        """Test subdomain is converted to lowercase."""
        # Mock the models that are imported inside validate_subdomain - use actual paths
        with patch('smoothschedule.identity.core.models.Tenant') as mock_tenant_class:
            with patch('smoothschedule.identity.core.models.Domain') as mock_domain_class:
                mock_tenant_class.objects.filter.return_value.exists.return_value = False
                mock_domain_class.objects.filter.return_value.exists.return_value = False

                serializer = TenantInvitationAcceptSerializer()
                result = serializer.validate_subdomain('MyBusiness')

                assert result == 'mybusiness'

    @patch('smoothschedule.platform.admin.serializers.User')
    def test_validate_email_rejects_existing(self, mock_user_model):
        """Test email validation rejects existing email."""
        mock_user_model.objects.filter.return_value.exists.return_value = True

        serializer = TenantInvitationAcceptSerializer()

        with pytest.raises(serializers.ValidationError) as exc_info:
            serializer.validate_email('existing@example.com')

        assert 'already exists' in str(exc_info.value)

    @patch('smoothschedule.platform.admin.serializers.User')
    def test_validate_email_accepts_new(self, mock_user_model):
        """Test email validation accepts new email."""
        mock_user_model.objects.filter.return_value.exists.return_value = False

        serializer = TenantInvitationAcceptSerializer()
        result = serializer.validate_email('new@example.com')

        assert result == 'new@example.com'


class TestTenantInvitationDetailSerializer:
    """Tests for TenantInvitationDetailSerializer."""

    def test_inherits_from_tenant_invitation_serializer(self):
        """Verify it inherits from TenantInvitationSerializer."""
        serializer = TenantInvitationDetailSerializer()

        assert isinstance(serializer, TenantInvitationSerializer)

    def test_all_fields_read_only(self):
        """Verify all fields are read-only for public viewing."""
        serializer = TenantInvitationDetailSerializer()

        read_only = serializer.Meta.read_only_fields

        # Key fields should be read-only
        assert 'email' in read_only
        assert 'token' in read_only
        assert 'status' in read_only
        assert 'suggested_business_name' in read_only
        assert 'permissions' in read_only


class TestAssignedUserSerializer:
    """Tests for AssignedUserSerializer."""

    def test_all_fields_read_only(self):
        """Verify all base fields are read-only."""
        serializer = AssignedUserSerializer()

        assert serializer.fields['id'].read_only
        assert serializer.fields['email'].read_only
        assert serializer.fields['first_name'].read_only
        assert serializer.fields['last_name'].read_only

    def test_get_full_name_uses_get_full_name(self):
        """Test full_name uses obj.get_full_name()."""
        mock_user = Mock()
        mock_user.get_full_name.return_value = 'Jane Smith'
        mock_user.email = 'jane@example.com'

        serializer = AssignedUserSerializer()
        result = serializer.get_full_name(mock_user)

        assert result == 'Jane Smith'

    def test_get_full_name_falls_back_to_email(self):
        """Test full_name falls back to email if no name."""
        mock_user = Mock()
        mock_user.get_full_name.return_value = ''
        mock_user.email = 'user@example.com'

        serializer = AssignedUserSerializer()
        result = serializer.get_full_name(mock_user)

        assert result == 'user@example.com'


class TestPlatformEmailAddressListSerializer:
    """Tests for PlatformEmailAddressListSerializer."""

    def test_read_only_fields(self):
        """Verify read-only fields are configured correctly."""
        serializer = PlatformEmailAddressListSerializer()

        read_only = serializer.Meta.read_only_fields

        assert 'email_address' in read_only
        assert 'effective_sender_name' in read_only
        assert 'mail_server_synced' in read_only
        assert 'last_check_at' in read_only
        assert 'emails_processed_count' in read_only

    def test_email_address_is_read_only_field(self):
        """Verify email_address uses ReadOnlyField."""
        serializer = PlatformEmailAddressListSerializer()

        assert isinstance(serializer.fields['email_address'], serializers.ReadOnlyField)

    def test_assigned_user_is_nested_serializer(self):
        """Verify assigned_user uses AssignedUserSerializer."""
        serializer = PlatformEmailAddressListSerializer()

        field = serializer.fields['assigned_user']
        assert field.read_only


class TestPlatformEmailAddressSerializer:
    """Tests for PlatformEmailAddressSerializer."""

    def test_password_is_write_only(self):
        """Verify password is write-only."""
        serializer = PlatformEmailAddressSerializer()

        assert serializer.Meta.extra_kwargs['password']['write_only']

    def test_assigned_user_id_is_write_only(self):
        """Verify assigned_user_id is write-only."""
        serializer = PlatformEmailAddressSerializer()

        assert serializer.fields['assigned_user_id'].write_only

    def test_assigned_user_id_allows_null(self):
        """Verify assigned_user_id allows null."""
        serializer = PlatformEmailAddressSerializer()

        assert serializer.fields['assigned_user_id'].allow_null

    def test_validate_assigned_user_id_valid_user(self):
        """Test assigned_user_id validation with valid platform user."""
        # Mock User model which is imported inside validate_assigned_user_id - use actual path
        with patch('smoothschedule.identity.users.models.User') as mock_user_model:
            mock_user = Mock()
            mock_user.pk = 123
            mock_user_model.objects.get.return_value = mock_user

            serializer = PlatformEmailAddressSerializer()
            result = serializer.validate_assigned_user_id(123)

            assert result == mock_user

    @patch('smoothschedule.platform.admin.serializers.User')
    def test_validate_assigned_user_id_none(self, mock_user_model):
        """Test assigned_user_id validation with None."""
        serializer = PlatformEmailAddressSerializer()
        result = serializer.validate_assigned_user_id(None)

        assert result is None
        mock_user_model.objects.get.assert_not_called()

    def test_validate_assigned_user_id_invalid_user(self):
        """Test assigned_user_id validation with non-existent user."""
        # Mock User model which is imported inside validate_assigned_user_id - use actual path
        with patch('smoothschedule.identity.users.models.User') as mock_user_model:
            # Create DoesNotExist exception
            class DoesNotExist(Exception):
                pass

            mock_user_model.DoesNotExist = DoesNotExist
            mock_user_model.objects.get.side_effect = DoesNotExist

            serializer = PlatformEmailAddressSerializer()

            with pytest.raises(serializers.ValidationError) as exc_info:
                serializer.validate_assigned_user_id(999)

            assert 'not found' in str(exc_info.value)

    def test_get_imap_settings_removes_password(self):
        """Test get_imap_settings removes password from settings."""
        mock_obj = Mock()
        mock_obj.get_imap_settings.return_value = {
            'host': 'mail.example.com',
            'port': 993,
            'password': 'secret123',
            'username': 'user@example.com',
        }

        serializer = PlatformEmailAddressSerializer()
        result = serializer.get_imap_settings(mock_obj)

        assert 'password' not in result
        assert result['host'] == 'mail.example.com'
        assert result['port'] == 993

    def test_get_smtp_settings_removes_password(self):
        """Test get_smtp_settings removes password from settings."""
        mock_obj = Mock()
        mock_obj.get_smtp_settings.return_value = {
            'host': 'smtp.example.com',
            'port': 587,
            'password': 'secret456',
            'username': 'user@example.com',
        }

        serializer = PlatformEmailAddressSerializer()
        result = serializer.get_smtp_settings(mock_obj)

        assert 'password' not in result
        assert result['host'] == 'smtp.example.com'

    def test_validate_local_part_valid(self):
        """Test local_part validation accepts valid format."""
        serializer = PlatformEmailAddressSerializer()

        valid_values = ['support', 'sales-team', 'info.contact', 'user_name']

        for value in valid_values:
            result = serializer.validate_local_part(value)
            assert result == value.lower().strip()

    def test_validate_local_part_invalid_format(self):
        """Test local_part validation rejects invalid format."""
        serializer = PlatformEmailAddressSerializer()

        invalid_values = ['.starts-with-dot', 'ends-with-dot.', '-starts-with-hyphen']

        for value in invalid_values:
            with pytest.raises(serializers.ValidationError):
                serializer.validate_local_part(value)

    def test_validate_local_part_too_long(self):
        """Test local_part validation rejects strings over 64 characters."""
        serializer = PlatformEmailAddressSerializer()

        too_long = 'a' * 65

        with pytest.raises(serializers.ValidationError) as exc_info:
            serializer.validate_local_part(too_long)

        assert '64 characters' in str(exc_info.value)

    def test_validate_password_minimum_length(self):
        """Test password validation requires minimum 8 characters."""
        serializer = PlatformEmailAddressSerializer()

        with pytest.raises(serializers.ValidationError) as exc_info:
            serializer.validate_password('short')

        assert '8 characters' in str(exc_info.value)

    def test_validate_password_valid(self):
        """Test password validation accepts valid password."""
        serializer = PlatformEmailAddressSerializer()

        result = serializer.validate_password('validpassword123')
        assert result == 'validpassword123'

    def test_validate_checks_uniqueness_on_create(self):
        """Test cross-field validation checks email uniqueness on create."""
        # Mock PlatformEmailAddress which is imported at top of serializers.py - patch in serializers namespace
        with patch('smoothschedule.platform.admin.serializers.PlatformEmailAddress') as mock_model:
            mock_model.objects.filter.return_value.exists.return_value = True

            serializer = PlatformEmailAddressSerializer()
            serializer.instance = None  # Creating new instance

            attrs = {
                'local_part': 'support',
                'domain': 'smoothschedule.com',
            }

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

            # Check that local_part field has a validation error
            assert 'local_part' in exc_info.value.detail
            # The error detail can be either a list or a string - handle both cases
            error_detail = exc_info.value.detail['local_part']
            if isinstance(error_detail, list):
                error_message = str(error_detail[0])
            else:
                error_message = str(error_detail)
            assert 'already exists' in error_message.lower()

    @patch('smoothschedule.platform.admin.serializers.PlatformEmailAddress')
    def test_validate_allows_same_email_on_update(self, mock_model):
        """Test validation allows keeping same email on update."""
        mock_qs = Mock()
        mock_qs.exclude.return_value.exists.return_value = False
        mock_model.objects.filter.return_value = mock_qs

        mock_instance = Mock()
        mock_instance.pk = 123
        mock_instance.local_part = 'support'
        mock_instance.domain = 'smoothschedule.com'

        serializer = PlatformEmailAddressSerializer()
        serializer.instance = mock_instance

        attrs = {
            'local_part': 'support',
            'domain': 'smoothschedule.com',
        }

        # Should not raise
        result = serializer.validate(attrs)
        assert result == attrs

    def test_create_handles_assigned_user_id(self):
        """Test create method handles assigned_user_id separately."""
        mock_user = Mock()
        mock_user.id = 456

        validated_data = {
            'local_part': 'support',
            'domain': 'smoothschedule.com',
            'password': 'secure123',
            'assigned_user_id': mock_user,
        }

        serializer = PlatformEmailAddressSerializer()

        with patch.object(serializers.ModelSerializer, 'create') as mock_create:
            mock_instance = Mock()
            mock_instance.save = Mock()
            mock_create.return_value = mock_instance

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

            # Verify assigned_user was set and saved
            assert mock_instance.assigned_user == mock_user
            mock_instance.save.assert_called_once_with(update_fields=['assigned_user'])

    def test_update_handles_assigned_user_id(self):
        """Test update method handles assigned_user_id."""
        mock_instance = Mock()
        mock_user = Mock()

        validated_data = {
            'display_name': 'Updated Support',
            'assigned_user_id': mock_user,
        }

        serializer = PlatformEmailAddressSerializer()

        with patch.object(serializers.ModelSerializer, 'update') as mock_update:
            mock_update.return_value = mock_instance

            result = serializer.update(mock_instance, validated_data.copy())

            # Verify assigned_user was set
            assert mock_instance.assigned_user == mock_user

            # Verify assigned_user_id was removed from validated_data before parent call
            call_args = mock_update.call_args[0][1]
            assert 'assigned_user_id' not in call_args


class TestPlatformEmailAddressCreateSerializer:
    """Tests for PlatformEmailAddressCreateSerializer."""

    def test_create_syncs_to_mail_server(self):
        """Test create method syncs to mail server."""
        validated_data = {
            'local_part': 'Support',  # Should be normalized
            'domain': 'smoothschedule.com',
            'password': 'secure123',
        }

        serializer = PlatformEmailAddressCreateSerializer()

        # Mock get_mail_server_service which is imported from .mail_server
        with patch('smoothschedule.platform.admin.mail_server.get_mail_server_service') as mock_get_service:
            mock_service = Mock()
            mock_service.sync_account.return_value = (True, 'Success')
            mock_get_service.return_value = mock_service

            with patch.object(PlatformEmailAddressSerializer, 'create') as mock_create:
                mock_instance = Mock()
                mock_instance.delete = Mock()
                mock_create.return_value = mock_instance

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

                # Verify local_part was normalized to lowercase
                call_args = mock_create.call_args[0][0]
                assert call_args['local_part'] == 'support'

                # Verify mail server sync was called
                mock_service.sync_account.assert_called_once_with(mock_instance)

    def test_create_deletes_on_mail_server_failure(self):
        """Test create deletes DB record if mail server sync fails."""
        validated_data = {
            'local_part': 'support',
            'domain': 'smoothschedule.com',
            'password': 'secure123',
        }

        serializer = PlatformEmailAddressCreateSerializer()

        # Mock get_mail_server_service which is imported from .mail_server
        with patch('smoothschedule.platform.admin.mail_server.get_mail_server_service') as mock_get_service:
            mock_service = Mock()
            mock_service.sync_account.return_value = (False, 'Mail server error')
            mock_get_service.return_value = mock_service

            with patch.object(PlatformEmailAddressSerializer, 'create') as mock_create:
                mock_instance = Mock()
                mock_instance.delete = Mock()
                mock_create.return_value = mock_instance

                with pytest.raises(serializers.ValidationError) as exc_info:
                    serializer.create(validated_data.copy())

                # Verify instance was deleted
                mock_instance.delete.assert_called_once()

                # Verify error message
                assert 'mail_server' in exc_info.value.detail
                assert 'Mail server error' in str(exc_info.value.detail['mail_server'])


class TestPlatformEmailAddressUpdateSerializer:
    """Tests for PlatformEmailAddressUpdateSerializer."""

    def test_password_is_optional(self):
        """Verify password is optional on update."""
        serializer = PlatformEmailAddressUpdateSerializer()

        assert not serializer.Meta.extra_kwargs['password']['required']

    def test_sender_name_is_optional(self):
        """Verify sender_name is optional on update."""
        serializer = PlatformEmailAddressUpdateSerializer()

        assert not serializer.Meta.extra_kwargs['sender_name']['required']

    def test_validate_assigned_user_id_valid(self):
        """Test assigned_user_id validation."""
        # Mock User which is imported inside validate_assigned_user_id - use actual path
        with patch('smoothschedule.identity.users.models.User') as mock_user_model:
            mock_user = Mock()
            mock_user_model.objects.get.return_value = mock_user

            serializer = PlatformEmailAddressUpdateSerializer()
            result = serializer.validate_assigned_user_id(123)

            assert result == mock_user

    def test_validate_password_minimum_length_if_provided(self):
        """Test password validation only applies if password is provided."""
        serializer = PlatformEmailAddressUpdateSerializer()

        # Should pass validation if not provided (None or empty)
        result = serializer.validate_password(None)
        assert result is None

        # Should validate if provided
        with pytest.raises(serializers.ValidationError):
            serializer.validate_password('short')

    def test_update_without_password_change(self):
        """Test update without password change doesn't sync to mail server."""
        mock_instance = Mock()
        mock_instance.password = 'existing_password'

        validated_data = {
            'display_name': 'Updated Name',
            'color': '#ff0000',
        }

        serializer = PlatformEmailAddressUpdateSerializer()

        with patch.object(serializers.ModelSerializer, 'update') as mock_update:
            mock_update.return_value = mock_instance

            result = serializer.update(mock_instance, validated_data.copy())

            # Verify update was called
            assert result is not None

    def test_update_with_password_change_syncs(self):
        """Test update with password change syncs to mail server."""
        mock_instance = Mock()
        mock_instance.password = 'old_password'

        validated_data = {
            'password': 'new_password',
        }

        serializer = PlatformEmailAddressUpdateSerializer()

        # Mock get_mail_server_service which is imported from .mail_server
        with patch('smoothschedule.platform.admin.mail_server.get_mail_server_service') as mock_get_service:
            mock_service = Mock()
            mock_service.sync_account.return_value = (True, 'Success')
            mock_get_service.return_value = mock_service

            with patch.object(serializers.ModelSerializer, 'update') as mock_update:
                mock_update.return_value = mock_instance

                result = serializer.update(mock_instance, validated_data.copy())

                # Mail server sync SHOULD be called
                mock_service.sync_account.assert_called_once_with(mock_instance)

    def test_update_raises_on_mail_server_failure(self):
        """Test update raises error if mail server sync fails."""
        mock_instance = Mock()
        mock_instance.password = 'old_password'

        validated_data = {
            'password': 'new_password',
        }

        serializer = PlatformEmailAddressUpdateSerializer()

        # Mock get_mail_server_service which is imported from .mail_server
        with patch('smoothschedule.platform.admin.mail_server.get_mail_server_service') as mock_get_service:
            mock_service = Mock()
            mock_service.sync_account.return_value = (False, 'Sync failed')
            mock_get_service.return_value = mock_service

            with patch.object(serializers.ModelSerializer, 'update') as mock_update:
                mock_update.return_value = mock_instance

                with pytest.raises(serializers.ValidationError) as exc_info:
                    serializer.update(mock_instance, validated_data.copy())

                assert 'mail_server' in exc_info.value.detail

    def test_update_handles_assigned_user_id(self):
        """Test update handles assigned_user_id separately."""
        mock_instance = Mock()
        mock_instance.password = 'password123'
        mock_user = Mock()

        validated_data = {
            'display_name': 'Updated',
            'assigned_user_id': mock_user,
        }

        serializer = PlatformEmailAddressUpdateSerializer()

        with patch.object(serializers.ModelSerializer, 'update') as mock_update:
            mock_update.return_value = mock_instance

            result = serializer.update(mock_instance, validated_data.copy())

            # Verify assigned_user was set
            assert mock_instance.assigned_user == mock_user
