"""
Unit tests for billing serializers.

Tests serializer methods without database access using mocks.
"""
from unittest.mock import Mock, patch, MagicMock
import pytest


class TestPlanFeatureSerializerGetValue:
    """Test PlanFeatureSerializer.get_value method."""

    def test_get_value_returns_obj_get_value(self):
        """Test get_value returns the result of obj.get_value()."""
        from smoothschedule.billing.api.serializers import PlanFeatureSerializer

        serializer = PlanFeatureSerializer()

        mock_obj = Mock()
        mock_obj.get_value.return_value = True

        result = serializer.get_value(mock_obj)

        assert result is True
        mock_obj.get_value.assert_called_once()

    def test_get_value_returns_int_value(self):
        """Test get_value returns integer values."""
        from smoothschedule.billing.api.serializers import PlanFeatureSerializer

        serializer = PlanFeatureSerializer()

        mock_obj = Mock()
        mock_obj.get_value.return_value = 100

        result = serializer.get_value(mock_obj)

        assert result == 100


class TestAddOnFeatureSerializerGetValue:
    """Test AddOnFeatureSerializer.get_value method."""

    def test_get_value_returns_obj_get_value(self):
        """Test get_value returns the result of obj.get_value()."""
        from smoothschedule.billing.api.serializers import AddOnFeatureSerializer

        serializer = AddOnFeatureSerializer()

        mock_obj = Mock()
        mock_obj.get_value.return_value = False

        result = serializer.get_value(mock_obj)

        assert result is False
        mock_obj.get_value.assert_called_once()

    def test_get_value_returns_int_value(self):
        """Test get_value returns integer values."""
        from smoothschedule.billing.api.serializers import AddOnFeatureSerializer

        serializer = AddOnFeatureSerializer()

        mock_obj = Mock()
        mock_obj.get_value.return_value = 50

        result = serializer.get_value(mock_obj)

        assert result == 50


class TestPlanVersionCreateSerializerCreate:
    """Test PlanVersionCreateSerializer.create method."""

    def test_create_new_version_for_existing_plan(self):
        """Test creating a new plan version for an existing plan."""
        from smoothschedule.billing.api.serializers import PlanVersionCreateSerializer

        validated_data = {
            'plan_code': 'pro',
            'name': 'Pro Plan v2',
            'is_public': True,
            'price_monthly_cents': 2999,
            'price_yearly_cents': 29990,
            'features': [],
        }

        mock_plan = Mock()
        mock_plan.versions.order_by.return_value.first.return_value = Mock(version=1)
        mock_plan_version = Mock(id=1, name='Pro Plan v2')

        with patch('smoothschedule.billing.api.serializers.Plan') as MockPlan:
            MockPlan.objects.get.return_value = mock_plan

            with patch('smoothschedule.billing.api.serializers.PlanVersion') as MockPlanVersion:
                MockPlanVersion.objects.create.return_value = mock_plan_version

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

        assert result == mock_plan_version
        MockPlanVersion.objects.create.assert_called_once()

    def test_create_first_version_for_plan(self):
        """Test creating first version (version=1) when no versions exist."""
        from smoothschedule.billing.api.serializers import PlanVersionCreateSerializer

        validated_data = {
            'plan_code': 'starter',
            'name': 'Starter Plan v1',
            'is_public': True,
            'price_monthly_cents': 999,
            'price_yearly_cents': 9990,
            'features': [],
        }

        mock_plan = Mock()
        mock_plan.versions.order_by.return_value.first.return_value = None
        mock_plan_version = Mock(id=1, name='Starter Plan v1')

        with patch('smoothschedule.billing.api.serializers.Plan') as MockPlan:
            MockPlan.objects.get.return_value = mock_plan

            with patch('smoothschedule.billing.api.serializers.PlanVersion') as MockPlanVersion:
                MockPlanVersion.objects.create.return_value = mock_plan_version

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

        # Should use version=1 since no previous versions exist
        MockPlanVersion.objects.create.assert_called_once()
        call_kwargs = MockPlanVersion.objects.create.call_args[1]
        assert call_kwargs['version'] == 1

    def test_create_raises_validation_error_for_nonexistent_plan(self):
        """Test that creating version for non-existent plan raises ValidationError."""
        from smoothschedule.billing.api.serializers import PlanVersionCreateSerializer
        from smoothschedule.billing.models import Plan
        from rest_framework.exceptions import ValidationError

        validated_data = {
            'plan_code': 'nonexistent',
            'name': 'Some Plan v1',
            'is_public': True,
            'features': [],
        }

        with patch('smoothschedule.billing.api.serializers.Plan') as MockPlan:
            MockPlan.DoesNotExist = Plan.DoesNotExist
            MockPlan.objects.get.side_effect = Plan.DoesNotExist()

            serializer = PlanVersionCreateSerializer()

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

            assert 'plan_code' in exc_info.value.detail

    def test_create_with_features(self):
        """Test creating version with features."""
        from smoothschedule.billing.api.serializers import PlanVersionCreateSerializer

        validated_data = {
            'plan_code': 'pro',
            'name': 'Pro Plan v1',
            'is_public': True,
            'price_monthly_cents': 2999,
            'features': [
                {'feature_code': 'sms', 'bool_value': True},
                {'feature_code': 'max_users', 'int_value': 10},
            ],
        }

        mock_plan = Mock()
        mock_plan.versions.order_by.return_value.first.return_value = None
        mock_plan_version = Mock(id=1)
        mock_feature = Mock()

        with patch('smoothschedule.billing.api.serializers.Plan') as MockPlan:
            MockPlan.objects.get.return_value = mock_plan

            with patch('smoothschedule.billing.api.serializers.PlanVersion') as MockPlanVersion:
                MockPlanVersion.objects.create.return_value = mock_plan_version

                with patch('smoothschedule.billing.api.serializers.Feature') as MockFeature:
                    MockFeature.objects.get.return_value = mock_feature

                    with patch('smoothschedule.billing.api.serializers.PlanFeature') as MockPlanFeature:
                        serializer = PlanVersionCreateSerializer()
                        serializer.create(validated_data)

        # Should create 2 plan features
        assert MockPlanFeature.objects.create.call_count == 2

    def test_create_skips_unknown_features(self):
        """Test that unknown feature codes are skipped."""
        from smoothschedule.billing.api.serializers import PlanVersionCreateSerializer
        from smoothschedule.billing.models import Feature

        validated_data = {
            'plan_code': 'pro',
            'name': 'Pro Plan v1',
            'features': [
                {'feature_code': 'unknown_feature', 'bool_value': True},
            ],
        }

        mock_plan = Mock()
        mock_plan.versions.order_by.return_value.first.return_value = None
        mock_plan_version = Mock(id=1)

        with patch('smoothschedule.billing.api.serializers.Plan') as MockPlan:
            MockPlan.objects.get.return_value = mock_plan

            with patch('smoothschedule.billing.api.serializers.PlanVersion') as MockPlanVersion:
                MockPlanVersion.objects.create.return_value = mock_plan_version

                with patch('smoothschedule.billing.api.serializers.Feature') as MockFeature:
                    MockFeature.DoesNotExist = Feature.DoesNotExist
                    MockFeature.objects.get.side_effect = Feature.DoesNotExist()

                    with patch('smoothschedule.billing.api.serializers.PlanFeature') as MockPlanFeature:
                        serializer = PlanVersionCreateSerializer()
                        serializer.create(validated_data)

        # Should not create any plan features
        MockPlanFeature.objects.create.assert_not_called()


class TestPlanVersionUpdateSerializerUpdate:
    """Test PlanVersionUpdateSerializer.update method."""

    def test_update_instance_fields(self):
        """Test updating instance fields."""
        from smoothschedule.billing.api.serializers import PlanVersionUpdateSerializer

        mock_instance = Mock()
        validated_data = {
            'name': 'Updated Name',
            'price_monthly_cents': 3999,
        }

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

        assert mock_instance.name == 'Updated Name'
        assert mock_instance.price_monthly_cents == 3999
        mock_instance.save.assert_called_once()
        assert result == mock_instance

    def test_update_features_when_provided(self):
        """Test that features are updated when provided."""
        from smoothschedule.billing.api.serializers import PlanVersionUpdateSerializer

        mock_instance = Mock()
        mock_features_qs = Mock()
        mock_instance.features.all.return_value = mock_features_qs

        mock_feature = Mock()

        validated_data = {
            'name': 'Updated Name',
            'features': [
                {'feature_code': 'sms', 'bool_value': True},
            ],
        }

        with patch('smoothschedule.billing.api.serializers.Feature') as MockFeature:
            MockFeature.objects.get.return_value = mock_feature

            with patch('smoothschedule.billing.api.serializers.PlanFeature') as MockPlanFeature:
                serializer = PlanVersionUpdateSerializer()
                serializer.update(mock_instance, validated_data)

        mock_features_qs.delete.assert_called_once()
        MockPlanFeature.objects.create.assert_called_once()

    def test_update_does_not_touch_features_when_none(self):
        """Test that features are not touched when not provided."""
        from smoothschedule.billing.api.serializers import PlanVersionUpdateSerializer

        mock_instance = Mock()
        validated_data = {
            'name': 'Updated Name',
        }

        serializer = PlanVersionUpdateSerializer()
        serializer.update(mock_instance, validated_data)

        # features.all() should not be called when features is not in validated_data
        mock_instance.features.all.assert_not_called()

    def test_update_skips_unknown_features(self):
        """Test that unknown feature codes are skipped during update."""
        from smoothschedule.billing.api.serializers import PlanVersionUpdateSerializer
        from smoothschedule.billing.models import Feature

        mock_instance = Mock()
        mock_features_qs = Mock()
        mock_instance.features.all.return_value = mock_features_qs

        validated_data = {
            'features': [
                {'feature_code': 'nonexistent', 'bool_value': True},
            ],
        }

        with patch('smoothschedule.billing.api.serializers.Feature') as MockFeature:
            MockFeature.DoesNotExist = Feature.DoesNotExist
            MockFeature.objects.get.side_effect = Feature.DoesNotExist()

            with patch('smoothschedule.billing.api.serializers.PlanFeature') as MockPlanFeature:
                serializer = PlanVersionUpdateSerializer()
                serializer.update(mock_instance, validated_data)

        # Should delete old features but not create new ones
        mock_features_qs.delete.assert_called_once()
        MockPlanFeature.objects.create.assert_not_called()


class TestPlanVersionDetailSerializerGetSubscriberCount:
    """Test PlanVersionDetailSerializer.get_subscriber_count method."""

    def test_get_subscriber_count(self):
        """Test counting active subscribers."""
        from smoothschedule.billing.api.serializers import PlanVersionDetailSerializer

        serializer = PlanVersionDetailSerializer()
        mock_obj = Mock()

        with patch('smoothschedule.billing.api.serializers.Subscription') as MockSubscription:
            MockSubscription.objects.filter.return_value.count.return_value = 42

            result = serializer.get_subscriber_count(mock_obj)

        assert result == 42
        MockSubscription.objects.filter.assert_called_once_with(
            plan_version=mock_obj,
            status__in=['active', 'trial'],
        )


class TestAddOnProductCreateSerializerCreate:
    """Test AddOnProductCreateSerializer.create method."""

    def test_create_addon_without_features(self):
        """Test creating an add-on without features."""
        from smoothschedule.billing.api.serializers import AddOnProductCreateSerializer

        validated_data = {
            'code': 'extra_sms',
            'name': 'Extra SMS Pack',
            'price_monthly_cents': 999,
            'is_active': True,
            'features': [],
        }

        mock_addon = Mock(id=1, name='Extra SMS Pack')

        with patch('smoothschedule.billing.api.serializers.AddOnProduct') as MockAddOnProduct:
            MockAddOnProduct.objects.create.return_value = mock_addon

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

        assert result == mock_addon
        MockAddOnProduct.objects.create.assert_called_once()

    def test_create_addon_with_features(self):
        """Test creating an add-on with features."""
        from smoothschedule.billing.api.serializers import AddOnProductCreateSerializer

        validated_data = {
            'code': 'extra_sms',
            'name': 'Extra SMS Pack',
            'price_monthly_cents': 999,
            'features': [
                {'feature_code': 'sms_credits', 'int_value': 1000},
            ],
        }

        mock_addon = Mock(id=1)
        mock_feature = Mock()

        with patch('smoothschedule.billing.api.serializers.AddOnProduct') as MockAddOnProduct:
            MockAddOnProduct.objects.create.return_value = mock_addon

            with patch('smoothschedule.billing.api.serializers.Feature') as MockFeature:
                MockFeature.objects.get.return_value = mock_feature

                with patch('smoothschedule.billing.api.serializers.AddOnFeature') as MockAddOnFeature:
                    serializer = AddOnProductCreateSerializer()
                    serializer.create(validated_data)

        MockAddOnFeature.objects.create.assert_called_once()

    def test_create_addon_skips_unknown_features(self):
        """Test that unknown feature codes are skipped."""
        from smoothschedule.billing.api.serializers import AddOnProductCreateSerializer
        from smoothschedule.billing.models import Feature

        validated_data = {
            'code': 'extra_sms',
            'name': 'Extra SMS Pack',
            'features': [
                {'feature_code': 'nonexistent', 'bool_value': True},
            ],
        }

        mock_addon = Mock(id=1)

        with patch('smoothschedule.billing.api.serializers.AddOnProduct') as MockAddOnProduct:
            MockAddOnProduct.objects.create.return_value = mock_addon

            with patch('smoothschedule.billing.api.serializers.Feature') as MockFeature:
                MockFeature.DoesNotExist = Feature.DoesNotExist
                MockFeature.objects.get.side_effect = Feature.DoesNotExist()

                with patch('smoothschedule.billing.api.serializers.AddOnFeature') as MockAddOnFeature:
                    serializer = AddOnProductCreateSerializer()
                    serializer.create(validated_data)

        MockAddOnFeature.objects.create.assert_not_called()


class TestAddOnProductCreateSerializerUpdate:
    """Test AddOnProductCreateSerializer.update method."""

    def test_update_addon_fields(self):
        """Test updating add-on fields."""
        from smoothschedule.billing.api.serializers import AddOnProductCreateSerializer

        mock_instance = Mock()
        validated_data = {
            'name': 'Updated Name',
            'price_monthly_cents': 1999,
        }

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

        assert mock_instance.name == 'Updated Name'
        assert mock_instance.price_monthly_cents == 1999
        mock_instance.save.assert_called_once()
        assert result == mock_instance

    def test_update_addon_features_when_provided(self):
        """Test updating add-on features when provided."""
        from smoothschedule.billing.api.serializers import AddOnProductCreateSerializer

        mock_instance = Mock()
        mock_features_qs = Mock()
        mock_instance.features.all.return_value = mock_features_qs

        mock_feature = Mock()

        validated_data = {
            'name': 'Updated Name',
            'features': [
                {'feature_code': 'sms', 'bool_value': True},
            ],
        }

        with patch('smoothschedule.billing.api.serializers.Feature') as MockFeature:
            MockFeature.objects.get.return_value = mock_feature

            with patch('smoothschedule.billing.api.serializers.AddOnFeature') as MockAddOnFeature:
                serializer = AddOnProductCreateSerializer()
                serializer.update(mock_instance, validated_data)

        mock_features_qs.delete.assert_called_once()
        MockAddOnFeature.objects.create.assert_called_once()

    def test_update_does_not_touch_features_when_none(self):
        """Test that features are not touched when not provided."""
        from smoothschedule.billing.api.serializers import AddOnProductCreateSerializer

        mock_instance = Mock()
        validated_data = {
            'name': 'Updated Name',
        }

        serializer = AddOnProductCreateSerializer()
        serializer.update(mock_instance, validated_data)

        mock_instance.features.all.assert_not_called()

    def test_update_addon_skips_unknown_features(self):
        """Test that unknown feature codes are skipped during update."""
        from smoothschedule.billing.api.serializers import AddOnProductCreateSerializer
        from smoothschedule.billing.models import Feature

        mock_instance = Mock()
        mock_features_qs = Mock()
        mock_instance.features.all.return_value = mock_features_qs

        validated_data = {
            'features': [
                {'feature_code': 'nonexistent', 'int_value': 100},
            ],
        }

        with patch('smoothschedule.billing.api.serializers.Feature') as MockFeature:
            MockFeature.DoesNotExist = Feature.DoesNotExist
            MockFeature.objects.get.side_effect = Feature.DoesNotExist()

            with patch('smoothschedule.billing.api.serializers.AddOnFeature') as MockAddOnFeature:
                serializer = AddOnProductCreateSerializer()
                serializer.update(mock_instance, validated_data)

        mock_features_qs.delete.assert_called_once()
        MockAddOnFeature.objects.create.assert_not_called()


class TestPlanWithVersionsSerializerGetActiveVersion:
    """Test PlanWithVersionsSerializer.get_active_version method."""

    def test_get_active_version_returns_serialized_version(self):
        """Test getting active version returns serialized data."""
        from smoothschedule.billing.api.serializers import PlanWithVersionsSerializer

        mock_obj = Mock()
        mock_active_version = Mock(id=1, name='Active v1')
        mock_obj.versions.filter.return_value.first.return_value = mock_active_version

        serializer = PlanWithVersionsSerializer()

        with patch('smoothschedule.billing.api.serializers.PlanVersionDetailSerializer') as MockDetailSerializer:
            MockDetailSerializer.return_value.data = {'id': 1, 'name': 'Active v1'}

            result = serializer.get_active_version(mock_obj)

        assert result == {'id': 1, 'name': 'Active v1'}
        mock_obj.versions.filter.assert_called_once_with(is_public=True, is_legacy=False)

    def test_get_active_version_returns_none_when_no_active(self):
        """Test getting active version returns None when none exists."""
        from smoothschedule.billing.api.serializers import PlanWithVersionsSerializer

        mock_obj = Mock()
        mock_obj.versions.filter.return_value.first.return_value = None

        serializer = PlanWithVersionsSerializer()
        result = serializer.get_active_version(mock_obj)

        assert result is None


class TestPlanWithVersionsSerializerGetTotalSubscribers:
    """Test PlanWithVersionsSerializer.get_total_subscribers method."""

    def test_get_total_subscribers(self):
        """Test counting total subscribers across all versions."""
        from smoothschedule.billing.api.serializers import PlanWithVersionsSerializer

        serializer = PlanWithVersionsSerializer()
        mock_obj = Mock()

        with patch('smoothschedule.billing.api.serializers.Subscription') as MockSubscription:
            MockSubscription.objects.filter.return_value.count.return_value = 100

            result = serializer.get_total_subscribers(mock_obj)

        assert result == 100
        MockSubscription.objects.filter.assert_called_once_with(
            plan_version__plan=mock_obj,
            status__in=['active', 'trial'],
        )
