"""
API tests for billing endpoints.

Tests verify:
- /api/me/entitlements/ returns effective entitlements
- /api/me/subscription/ returns subscription with is_legacy flag
- /api/billing/plans/ filters by is_public and date window
- /api/billing/invoices/ is tenant-isolated
- /api/billing/invoices/{id}/ returns 404 for other tenant's invoice
"""

from datetime import timedelta
from unittest.mock import Mock
from unittest.mock import patch

import pytest
from django.urls import reverse
from django.utils import timezone
from rest_framework import status
from rest_framework.test import APIClient


@pytest.fixture
def clean_tenant_subscription(shared_tenant):
    """Delete any existing subscription for shared_tenant before test."""
    from smoothschedule.billing.models import Subscription
    Subscription.objects.filter(business=shared_tenant).delete()
    yield shared_tenant


@pytest.fixture
def clean_second_tenant_subscription(second_shared_tenant):
    """Delete any existing subscription for second_shared_tenant before test."""
    from smoothschedule.billing.models import Subscription
    Subscription.objects.filter(business=second_shared_tenant).delete()
    yield second_shared_tenant


# =============================================================================
# /api/me/entitlements/ Tests
# =============================================================================


class TestEntitlementsEndpoint:
    """Tests for the /api/me/entitlements/ endpoint."""

    def test_returns_entitlements_from_service(self):
        """Endpoint should return dict from EntitlementService."""
        from smoothschedule.billing.api.views import EntitlementsView

        mock_request = Mock()
        mock_request.user = Mock()
        mock_request.user.tenant = Mock()

        with patch(
            "smoothschedule.billing.api.views.EntitlementService"
        ) as MockService:
            MockService.get_effective_entitlements.return_value = {
                "sms": True,
                "max_users": 10,
                "advanced_reporting": False,
            }

            view = EntitlementsView()
            view.request = mock_request

            response = view.get(mock_request)

            assert response.status_code == 200
            assert response.data["sms"] is True
            assert response.data["max_users"] == 10
            assert response.data["advanced_reporting"] is False

    def test_returns_empty_dict_when_no_subscription(self):
        """Endpoint should return empty dict when no subscription."""
        from smoothschedule.billing.api.views import EntitlementsView

        mock_request = Mock()
        mock_request.user = Mock()
        mock_request.user.tenant = Mock()

        with patch(
            "smoothschedule.billing.api.views.EntitlementService"
        ) as MockService:
            MockService.get_effective_entitlements.return_value = {}

            view = EntitlementsView()
            view.request = mock_request

            response = view.get(mock_request)

            assert response.status_code == 200
            assert response.data == {}


# =============================================================================
# /api/me/subscription/ Tests
# =============================================================================


class TestSubscriptionEndpoint:
    """Tests for the /api/me/subscription/ endpoint."""

    def test_returns_subscription_with_is_legacy_flag(self):
        """Subscription response should include is_legacy flag."""
        from smoothschedule.billing.api.views import CurrentSubscriptionView

        mock_subscription = Mock()
        mock_subscription.id = 1
        mock_subscription.status = "active"
        mock_subscription.plan_version = Mock()
        mock_subscription.plan_version.id = 10
        mock_subscription.plan_version.name = "Pro Plan v1"
        mock_subscription.plan_version.is_legacy = True
        mock_subscription.plan_version.plan = Mock()
        mock_subscription.plan_version.plan.code = "pro"
        mock_subscription.current_period_start = timezone.now()
        mock_subscription.current_period_end = timezone.now() + timedelta(days=30)

        mock_request = Mock()
        mock_request.user = Mock()
        mock_request.user.tenant = Mock()
        mock_request.user.tenant.billing_subscription = mock_subscription

        view = CurrentSubscriptionView()
        view.request = mock_request

        with patch(
            "smoothschedule.billing.api.views.SubscriptionSerializer"
        ) as MockSerializer:
            mock_serializer = Mock()
            mock_serializer.data = {
                "id": 1,
                "status": "active",
                "plan_version": {
                    "id": 10,
                    "name": "Pro Plan v1",
                    "is_legacy": True,
                    "plan": {"code": "pro"},
                },
            }
            MockSerializer.return_value = mock_serializer

            response = view.get(mock_request)

            assert response.status_code == 200
            assert response.data["plan_version"]["is_legacy"] is True

    def test_returns_404_when_no_subscription(self):
        """Should return 404 when tenant has no subscription."""
        from smoothschedule.billing.api.views import CurrentSubscriptionView

        mock_request = Mock()
        mock_request.user = Mock()
        mock_request.user.tenant = Mock()
        mock_request.user.tenant.billing_subscription = None

        view = CurrentSubscriptionView()
        view.request = mock_request

        response = view.get(mock_request)

        assert response.status_code == 404


# =============================================================================
# /api/billing/plans/ Tests
# =============================================================================


@pytest.mark.django_db
class TestPlansEndpoint:
    """Tests for the /api/billing/plans/ endpoint."""

    def test_filters_by_is_public_true(self):
        """Should only return public plan versions."""
        from smoothschedule.billing.models import Plan
        from smoothschedule.billing.models import PlanVersion
        from smoothschedule.billing.api.views import PlanCatalogView

        # Create public and non-public plans
        plan = Plan.objects.create(code="test_public", name="Test Public Plan")
        public_pv = PlanVersion.objects.create(
            plan=plan, version=1, name="Public v1", is_public=True, is_legacy=False
        )
        private_pv = PlanVersion.objects.create(
            plan=plan, version=2, name="Private v2", is_public=False, is_legacy=False
        )

        mock_request = Mock()
        view = PlanCatalogView()
        view.request = mock_request

        response = view.get(mock_request)

        # Should only return public plans
        plan_names = [pv["name"] for pv in response.data]
        assert "Public v1" in plan_names
        assert "Private v2" not in plan_names

    def test_excludes_legacy_plans(self):
        """Should exclude legacy plan versions."""
        from smoothschedule.billing.models import Plan
        from smoothschedule.billing.models import PlanVersion
        from smoothschedule.billing.api.views import PlanCatalogView

        # Create legacy and non-legacy plans
        plan = Plan.objects.create(code="test_legacy", name="Test Legacy Plan")
        current_pv = PlanVersion.objects.create(
            plan=plan, version=1, name="Current v1", is_public=True, is_legacy=False
        )
        legacy_pv = PlanVersion.objects.create(
            plan=plan, version=2, name="Legacy v2", is_public=True, is_legacy=True
        )

        mock_request = Mock()
        view = PlanCatalogView()
        view.request = mock_request

        response = view.get(mock_request)

        # Should only return non-legacy plans
        plan_names = [pv["name"] for pv in response.data]
        assert "Current v1" in plan_names
        assert "Legacy v2" not in plan_names


# =============================================================================
# /api/billing/invoices/ Tests (Database required for tenant isolation)
# =============================================================================


@pytest.mark.django_db
class TestInvoicesEndpointIsolation:
    """Tests for invoice tenant isolation."""

    def test_cannot_see_other_tenants_invoices(
        self, clean_tenant_subscription, clean_second_tenant_subscription
    ):
        """A tenant should only see their own invoices."""
        from smoothschedule.billing.models import Invoice
        from smoothschedule.billing.models import Plan
        from smoothschedule.billing.models import PlanVersion
        from smoothschedule.billing.models import Subscription
        from smoothschedule.identity.users.models import User

        shared_tenant = clean_tenant_subscription
        second_shared_tenant = clean_second_tenant_subscription

        # Create plan
        plan = Plan.objects.create(code="isolation_test", name="Test Plan")
        pv = PlanVersion.objects.create(plan=plan, version=1, name="Test Plan v1")

        now = timezone.now()

        # Create subscription for tenant 1
        sub1 = Subscription.objects.create(
            business=shared_tenant,
            plan_version=pv,
            status="active",
            current_period_start=now,
            current_period_end=now + timedelta(days=30),
        )

        # Create subscription for tenant 2
        sub2 = Subscription.objects.create(
            business=second_shared_tenant,
            plan_version=pv,
            status="active",
            current_period_start=now,
            current_period_end=now + timedelta(days=30),
        )

        # Create invoices for both tenants
        invoice1 = Invoice.objects.create(
            business=shared_tenant,
            subscription=sub1,
            period_start=now,
            period_end=now + timedelta(days=30),
            subtotal_amount=1000,
            total_amount=1000,
        )

        invoice2 = Invoice.objects.create(
            business=second_shared_tenant,
            subscription=sub2,
            period_start=now,
            period_end=now + timedelta(days=30),
            subtotal_amount=2000,
            total_amount=2000,
        )

        # Query invoices for tenant 1 only
        tenant1_invoices = Invoice.objects.filter(business=shared_tenant)
        tenant2_invoices = Invoice.objects.filter(business=second_shared_tenant)

        # Tenant 1 should only see their invoice
        assert tenant1_invoices.count() == 1
        assert tenant1_invoices.first().total_amount == 1000

        # Tenant 2 should only see their invoice
        assert tenant2_invoices.count() == 1
        assert tenant2_invoices.first().total_amount == 2000

    def test_invoice_detail_returns_404_for_other_tenant(
        self, clean_tenant_subscription, clean_second_tenant_subscription
    ):
        """Requesting another tenant's invoice should return 404."""
        from smoothschedule.billing.models import Invoice
        from smoothschedule.billing.models import Plan
        from smoothschedule.billing.models import PlanVersion
        from smoothschedule.billing.models import Subscription

        shared_tenant = clean_tenant_subscription
        second_shared_tenant = clean_second_tenant_subscription

        # Create plan
        plan = Plan.objects.create(code="detail_404_test", name="Test Plan")
        pv = PlanVersion.objects.create(plan=plan, version=1, name="Test Plan v1")

        now = timezone.now()

        # Create subscription for tenant 2
        sub2 = Subscription.objects.create(
            business=second_shared_tenant,
            plan_version=pv,
            status="active",
            current_period_start=now,
            current_period_end=now + timedelta(days=30),
        )

        # Create invoice for tenant 2
        invoice2 = Invoice.objects.create(
            business=second_shared_tenant,
            subscription=sub2,
            period_start=now,
            period_end=now + timedelta(days=30),
            subtotal_amount=2000,
            total_amount=2000,
        )

        # Try to get tenant 2's invoice from tenant 1's perspective
        # This should return None (404 in API)
        result = Invoice.objects.filter(
            business=shared_tenant, id=invoice2.id
        ).first()

        assert result is None


# =============================================================================
# /api/billing/addons/ Tests
# =============================================================================


class TestAddOnsEndpoint:
    """Tests for the /api/billing/addons/ endpoint."""

    def test_returns_active_addons_only(self):
        """Should only return active add-on products."""
        from smoothschedule.billing.api.views import AddOnCatalogView

        mock_request = Mock()
        mock_request.user = Mock()
        mock_request.user.tenant = Mock()

        view = AddOnCatalogView()
        view.request = mock_request

        with patch(
            "smoothschedule.billing.api.views.AddOnProduct"
        ) as MockAddOn:
            mock_queryset = Mock()
            MockAddOn.objects.filter.return_value = mock_queryset
            mock_queryset.all.return_value = []

            with patch(
                "smoothschedule.billing.api.views.AddOnProductSerializer"
            ):
                view.get(mock_request)

            # Verify filter was called with is_active=True
            MockAddOn.objects.filter.assert_called_with(is_active=True)


# =============================================================================
# Additional Unit Tests for Views
# =============================================================================


class TestEntitlementsViewNoTenant:
    """Tests for EntitlementsView when user has no tenant."""

    def test_returns_empty_dict_when_no_tenant(self):
        """Should return empty dict when user has no tenant attribute."""
        from smoothschedule.billing.api.views import EntitlementsView

        mock_request = Mock()
        mock_request.user = Mock(spec=['id', 'email'])  # No tenant attribute

        view = EntitlementsView()
        view.request = mock_request

        response = view.get(mock_request)

        assert response.status_code == 200
        assert response.data == {}

    def test_returns_empty_dict_when_tenant_is_none(self):
        """Should return empty dict when user.tenant is None."""
        from smoothschedule.billing.api.views import EntitlementsView

        mock_request = Mock()
        mock_request.user = Mock()
        mock_request.user.tenant = None

        view = EntitlementsView()
        view.request = mock_request

        response = view.get(mock_request)

        assert response.status_code == 200
        assert response.data == {}


class TestCurrentSubscriptionViewNoTenant:
    """Tests for CurrentSubscriptionView when user has no tenant."""

    def test_returns_400_when_no_tenant_attribute(self):
        """Should return 400 when user has no tenant attribute."""
        from smoothschedule.billing.api.views import CurrentSubscriptionView

        mock_request = Mock()
        mock_request.user = Mock(spec=['id', 'email'])  # No tenant attribute

        view = CurrentSubscriptionView()
        view.request = mock_request

        response = view.get(mock_request)

        assert response.status_code == 400
        assert "No tenant context" in str(response.data)

    def test_returns_400_when_tenant_is_none(self):
        """Should return 400 when user.tenant is None."""
        from smoothschedule.billing.api.views import CurrentSubscriptionView

        mock_request = Mock()
        mock_request.user = Mock()
        mock_request.user.tenant = None

        view = CurrentSubscriptionView()
        view.request = mock_request

        response = view.get(mock_request)

        assert response.status_code == 400
        assert "No tenant context" in str(response.data)


class TestInvoiceListView:
    """Tests for InvoiceListView."""

    def test_returns_400_when_no_tenant(self):
        """Should return 400 when user has no tenant."""
        from smoothschedule.billing.api.views import InvoiceListView

        mock_request = Mock()
        mock_request.user = Mock()
        mock_request.user.tenant = None

        view = InvoiceListView()
        view.request = mock_request

        response = view.get(mock_request)

        assert response.status_code == 400
        assert "No tenant context" in str(response.data)

    def test_returns_paginated_invoices(self):
        """Should return paginated invoice list."""
        from smoothschedule.billing.api.views import InvoiceListView

        mock_tenant = Mock()
        mock_request = Mock()
        mock_request.user = Mock()
        mock_request.user.tenant = mock_tenant
        mock_request.query_params = {'page': '1', 'page_size': '10'}

        with patch('smoothschedule.billing.api.views.Invoice') as MockInvoice:
            mock_qs = Mock()
            mock_qs.count.return_value = 25
            mock_qs.__getitem__ = Mock(return_value=[])
            MockInvoice.objects.filter.return_value.order_by.return_value = mock_qs

            with patch('smoothschedule.billing.api.views.InvoiceListSerializer') as MockSerializer:
                MockSerializer.return_value.data = []

                view = InvoiceListView()
                view.request = mock_request
                response = view.get(mock_request)

        assert response.status_code == 200
        assert response.data['count'] == 25
        assert response.data['page'] == 1
        assert response.data['page_size'] == 10

    def test_uses_default_pagination(self):
        """Should use default page_size of 20 when not specified."""
        from smoothschedule.billing.api.views import InvoiceListView

        mock_tenant = Mock()
        mock_request = Mock()
        mock_request.user = Mock()
        mock_request.user.tenant = mock_tenant
        mock_request.query_params = {}  # No pagination params

        with patch('smoothschedule.billing.api.views.Invoice') as MockInvoice:
            mock_qs = Mock()
            mock_qs.count.return_value = 5
            mock_qs.__getitem__ = Mock(return_value=[])
            MockInvoice.objects.filter.return_value.order_by.return_value = mock_qs

            with patch('smoothschedule.billing.api.views.InvoiceListSerializer') as MockSerializer:
                MockSerializer.return_value.data = []

                view = InvoiceListView()
                view.request = mock_request
                response = view.get(mock_request)

        assert response.data['page_size'] == 20


class TestInvoiceDetailView:
    """Tests for InvoiceDetailView."""

    def test_returns_400_when_no_tenant(self):
        """Should return 400 when user has no tenant."""
        from smoothschedule.billing.api.views import InvoiceDetailView

        mock_request = Mock()
        mock_request.user = Mock()
        mock_request.user.tenant = None

        view = InvoiceDetailView()
        view.request = mock_request

        response = view.get(mock_request, invoice_id=1)

        assert response.status_code == 400
        assert "No tenant context" in str(response.data)

    def test_returns_404_when_invoice_not_found(self):
        """Should return 404 when invoice doesn't exist."""
        from smoothschedule.billing.api.views import InvoiceDetailView
        from smoothschedule.billing.models import Invoice

        mock_tenant = Mock()
        mock_request = Mock()
        mock_request.user = Mock()
        mock_request.user.tenant = mock_tenant

        with patch('smoothschedule.billing.api.views.Invoice') as MockInvoice:
            MockInvoice.DoesNotExist = Invoice.DoesNotExist
            MockInvoice.objects.prefetch_related.return_value.get.side_effect = Invoice.DoesNotExist()

            view = InvoiceDetailView()
            view.request = mock_request
            response = view.get(mock_request, invoice_id=999)

        assert response.status_code == 404
        assert "Invoice not found" in str(response.data)

    def test_returns_invoice_when_found(self):
        """Should return invoice data when found."""
        from smoothschedule.billing.api.views import InvoiceDetailView

        mock_tenant = Mock()
        mock_request = Mock()
        mock_request.user = Mock()
        mock_request.user.tenant = mock_tenant

        mock_invoice = Mock(id=1, total_amount=1000)

        with patch('smoothschedule.billing.api.views.Invoice') as MockInvoice:
            MockInvoice.objects.prefetch_related.return_value.get.return_value = mock_invoice

            with patch('smoothschedule.billing.api.views.InvoiceSerializer') as MockSerializer:
                MockSerializer.return_value.data = {'id': 1, 'total_amount': 1000}

                view = InvoiceDetailView()
                view.request = mock_request
                response = view.get(mock_request, invoice_id=1)

        assert response.status_code == 200
        assert response.data['id'] == 1


# =============================================================================
# Admin ViewSet Tests
# =============================================================================


class TestFeatureViewSet:
    """Tests for FeatureViewSet."""

    def test_get_serializer_class_for_create(self):
        """Should use FeatureCreateSerializer for create action."""
        from smoothschedule.billing.api.views import FeatureViewSet
        from smoothschedule.billing.api.serializers import FeatureCreateSerializer

        viewset = FeatureViewSet()
        viewset.action = 'create'

        assert viewset.get_serializer_class() == FeatureCreateSerializer

    def test_get_serializer_class_for_update(self):
        """Should use FeatureCreateSerializer for update action."""
        from smoothschedule.billing.api.views import FeatureViewSet
        from smoothschedule.billing.api.serializers import FeatureCreateSerializer

        viewset = FeatureViewSet()
        viewset.action = 'update'

        assert viewset.get_serializer_class() == FeatureCreateSerializer

    def test_get_serializer_class_for_list(self):
        """Should use FeatureSerializer for list action."""
        from smoothschedule.billing.api.views import FeatureViewSet
        from smoothschedule.billing.api.serializers import FeatureSerializer

        viewset = FeatureViewSet()
        viewset.action = 'list'

        assert viewset.get_serializer_class() == FeatureSerializer


class TestPlanViewSet:
    """Tests for PlanViewSet."""

    def test_get_serializer_class_for_create(self):
        """Should use PlanCreateSerializer for create action."""
        from smoothschedule.billing.api.views import PlanViewSet
        from smoothschedule.billing.api.serializers import PlanCreateSerializer

        viewset = PlanViewSet()
        viewset.action = 'create'

        assert viewset.get_serializer_class() == PlanCreateSerializer

    def test_get_serializer_class_for_retrieve(self):
        """Should use PlanWithVersionsSerializer for retrieve action."""
        from smoothschedule.billing.api.views import PlanViewSet
        from smoothschedule.billing.api.serializers import PlanWithVersionsSerializer

        viewset = PlanViewSet()
        viewset.action = 'retrieve'

        assert viewset.get_serializer_class() == PlanWithVersionsSerializer

    def test_get_serializer_class_for_list_default(self):
        """Should use PlanSerializer for list action (default)."""
        from smoothschedule.billing.api.views import PlanViewSet
        from smoothschedule.billing.api.serializers import PlanSerializer

        viewset = PlanViewSet()
        viewset.action = 'destroy'  # Not in create/update/retrieve

        assert viewset.get_serializer_class() == PlanSerializer

    def test_list_uses_plan_with_versions_serializer(self):
        """List action should use PlanWithVersionsSerializer."""
        from smoothschedule.billing.api.views import PlanViewSet
        from smoothschedule.billing.api.serializers import PlanWithVersionsSerializer

        viewset = PlanViewSet()
        viewset.action = 'list'
        viewset.format_kwarg = None

        with patch.object(viewset, 'get_queryset') as mock_get_queryset:
            mock_get_queryset.return_value = []

            with patch.object(PlanWithVersionsSerializer, '__init__', return_value=None) as mock_init:
                mock_serializer = Mock()
                mock_serializer.data = []

                with patch('smoothschedule.billing.api.views.PlanWithVersionsSerializer', return_value=mock_serializer):
                    response = viewset.list(Mock())

        assert response.status_code == 200

    def test_create_version_action(self):
        """Should create a new version for a plan."""
        from smoothschedule.billing.api.views import PlanViewSet

        viewset = PlanViewSet()
        viewset.action = 'create_version'
        viewset.format_kwarg = None
        viewset.kwargs = {'pk': 1}

        mock_plan = Mock(code='test_plan')
        mock_request = Mock()
        mock_request.data = {'name': 'New Version', 'price_monthly_cents': 1000}

        with patch.object(viewset, 'get_object', return_value=mock_plan):
            with patch('smoothschedule.billing.api.views.PlanVersionCreateSerializer') as MockCreateSerializer:
                mock_serializer = Mock()
                mock_serializer.is_valid.return_value = True
                mock_serializer.save.return_value = Mock(id=1, name='New Version')
                MockCreateSerializer.return_value = mock_serializer

                with patch('smoothschedule.billing.api.views.PlanVersionDetailSerializer') as MockDetailSerializer:
                    MockDetailSerializer.return_value.data = {'id': 1, 'name': 'New Version'}

                    response = viewset.create_version(mock_request, pk=1)

        assert response.status_code == 201


class TestPlanVersionViewSet:
    """Tests for PlanVersionViewSet."""

    def test_get_serializer_class_for_create(self):
        """Should use PlanVersionCreateSerializer for create action."""
        from smoothschedule.billing.api.views import PlanVersionViewSet
        from smoothschedule.billing.api.serializers import PlanVersionCreateSerializer

        viewset = PlanVersionViewSet()
        viewset.action = 'create'

        assert viewset.get_serializer_class() == PlanVersionCreateSerializer

    def test_get_serializer_class_for_update(self):
        """Should use PlanVersionUpdateSerializer for update action."""
        from smoothschedule.billing.api.views import PlanVersionViewSet
        from smoothschedule.billing.api.serializers import PlanVersionUpdateSerializer

        viewset = PlanVersionViewSet()
        viewset.action = 'update'

        assert viewset.get_serializer_class() == PlanVersionUpdateSerializer

    def test_get_serializer_class_for_retrieve(self):
        """Should use PlanVersionDetailSerializer for retrieve action."""
        from smoothschedule.billing.api.views import PlanVersionViewSet
        from smoothschedule.billing.api.serializers import PlanVersionDetailSerializer

        viewset = PlanVersionViewSet()
        viewset.action = 'retrieve'

        assert viewset.get_serializer_class() == PlanVersionDetailSerializer

    def test_update_without_subscribers_updates_directly(self):
        """Should update version directly when no active subscribers."""
        from smoothschedule.billing.api.views import PlanVersionViewSet

        viewset = PlanVersionViewSet()
        viewset.action = 'update'
        viewset.format_kwarg = None
        viewset.kwargs = {'pk': 1}

        mock_version = Mock(id=1, name='Old Name')
        mock_request = Mock()
        mock_request.data = {'name': 'New Name'}

        with patch.object(viewset, 'get_object', return_value=mock_version):
            with patch('smoothschedule.billing.api.views.Subscription') as MockSubscription:
                MockSubscription.objects.filter.return_value.count.return_value = 0

                with patch.object(viewset, 'get_serializer') as mock_get_serializer:
                    mock_serializer = Mock()
                    mock_serializer.is_valid.return_value = True
                    mock_get_serializer.return_value = mock_serializer

                    with patch.object(viewset, 'perform_update'):
                        with patch('smoothschedule.billing.api.views.PlanVersionDetailSerializer') as MockDetailSerializer:
                            MockDetailSerializer.return_value.data = {'id': 1, 'name': 'New Name'}

                            response = viewset.update(mock_request, pk=1)

        assert response.status_code == 200

    def test_update_with_subscribers_creates_new_version(self):
        """Should create new version when there are active subscribers."""
        from smoothschedule.billing.api.views import PlanVersionViewSet

        viewset = PlanVersionViewSet()
        viewset.action = 'update'
        viewset.format_kwarg = None
        viewset.kwargs = {'pk': 1}

        mock_plan = Mock()
        mock_plan.versions.count.return_value = 1
        mock_version = Mock(
            id=1, name='Old Name', plan=mock_plan, version=1,
            is_legacy=False, is_public=True,
            price_monthly_cents=1000, price_yearly_cents=10000,
            transaction_fee_percent=0, transaction_fee_fixed_cents=0,
            trial_days=14, sms_price_per_message_cents=1,
            masked_calling_price_per_minute_cents=5,
            proxy_number_monthly_fee_cents=100,
            default_auto_reload_enabled=False,
            default_auto_reload_threshold_cents=0,
            default_auto_reload_amount_cents=0,
            is_most_popular=False, show_price=True,
            marketing_features=[], stripe_product_id=None,
            stripe_price_id_monthly=None, stripe_price_id_yearly=None
        )
        mock_version.features.all.return_value = []

        mock_request = Mock()
        mock_request.data = {'name': 'Updated Name'}

        with patch.object(viewset, 'get_object', return_value=mock_version):
            with patch('smoothschedule.billing.api.views.Subscription') as MockSubscription:
                MockSubscription.objects.filter.return_value.count.return_value = 5

                with patch('smoothschedule.billing.api.views.transaction') as mock_transaction:
                    # Mock the atomic context manager
                    mock_transaction.atomic.return_value.__enter__ = Mock()
                    mock_transaction.atomic.return_value.__exit__ = Mock(return_value=False)

                    with patch('smoothschedule.billing.api.views.PlanVersion') as MockPlanVersion:
                        new_version = Mock(id=2, name='Updated Name', version=2)
                        MockPlanVersion.objects.create.return_value = new_version

                        with patch('smoothschedule.billing.api.views.PlanVersionDetailSerializer') as MockDetailSerializer:
                            MockDetailSerializer.return_value.data = {'id': 2, 'name': 'Updated Name'}

                            response = viewset.update(mock_request, pk=1)

        assert response.status_code == 201
        assert mock_version.is_legacy is True

    def test_mark_legacy_action(self):
        """Should mark version as legacy."""
        from smoothschedule.billing.api.views import PlanVersionViewSet

        viewset = PlanVersionViewSet()
        viewset.action = 'mark_legacy'
        viewset.format_kwarg = None
        viewset.kwargs = {'pk': 1}

        mock_version = Mock(id=1, is_legacy=False, is_public=True)

        with patch.object(viewset, 'get_object', return_value=mock_version):
            with patch('smoothschedule.billing.api.views.PlanVersionDetailSerializer') as MockSerializer:
                MockSerializer.return_value.data = {'id': 1, 'is_legacy': True}

                response = viewset.mark_legacy(Mock(), pk=1)

        assert response.status_code == 200
        assert mock_version.is_legacy is True
        assert mock_version.is_public is False
        mock_version.save.assert_called_once()

    def test_subscribers_action(self):
        """Should return list of subscribers."""
        from smoothschedule.billing.api.views import PlanVersionViewSet

        viewset = PlanVersionViewSet()
        viewset.action = 'subscribers'
        viewset.format_kwarg = None
        viewset.kwargs = {'pk': 1}

        mock_version = Mock()
        mock_version.id = 1
        mock_version.name = 'Test Version'

        mock_business = Mock()
        mock_business.id = 10
        mock_business.name = 'Test Business'

        mock_subscription = Mock()
        mock_subscription.business = mock_business
        mock_subscription.status = 'active'
        mock_subscription.started_at = timezone.now()

        with patch.object(viewset, 'get_object', return_value=mock_version):
            with patch('smoothschedule.billing.api.views.Subscription') as MockSubscription:
                mock_qs = Mock()
                mock_qs.count.return_value = 1
                mock_qs.__iter__ = Mock(return_value=iter([mock_subscription]))
                mock_qs.__getitem__ = Mock(return_value=[mock_subscription])
                MockSubscription.objects.filter.return_value.select_related.return_value = mock_qs

                response = viewset.subscribers(Mock(), pk=1)

        assert response.status_code == 200
        assert response.data['version'] == 'Test Version'
        assert response.data['subscriber_count'] == 1

    def test_update_features_method(self):
        """Should update features for a version."""
        from smoothschedule.billing.api.views import PlanVersionViewSet

        viewset = PlanVersionViewSet()

        mock_version = Mock()
        mock_version.features.all.return_value.delete = Mock()

        features_data = [
            {'feature_code': 'sms', 'bool_value': True},
        ]

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

            with patch('smoothschedule.billing.api.views.PlanFeature') as MockPlanFeature:
                viewset._update_features(mock_version, features_data)

        mock_version.features.all.return_value.delete.assert_called_once()
        MockPlanFeature.objects.create.assert_called()

    def test_create_features_skips_nonexistent_feature(self):
        """Should skip features that don't exist."""
        from smoothschedule.billing.api.views import PlanVersionViewSet
        from smoothschedule.billing.models import Feature

        viewset = PlanVersionViewSet()

        mock_version = Mock()
        features_data = [
            {'feature_code': 'nonexistent', 'bool_value': True},
        ]

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

            with patch('smoothschedule.billing.api.views.PlanFeature') as MockPlanFeature:
                viewset._create_features(mock_version, features_data)

        MockPlanFeature.objects.create.assert_not_called()


class TestAddOnProductViewSet:
    """Tests for AddOnProductViewSet."""

    def test_get_serializer_class_for_create(self):
        """Should use AddOnProductCreateSerializer for create action."""
        from smoothschedule.billing.api.views import AddOnProductViewSet
        from smoothschedule.billing.api.serializers import AddOnProductCreateSerializer

        viewset = AddOnProductViewSet()
        viewset.action = 'create'

        assert viewset.get_serializer_class() == AddOnProductCreateSerializer

    def test_get_serializer_class_for_update(self):
        """Should use AddOnProductCreateSerializer for update action."""
        from smoothschedule.billing.api.views import AddOnProductViewSet
        from smoothschedule.billing.api.serializers import AddOnProductCreateSerializer

        viewset = AddOnProductViewSet()
        viewset.action = 'update'

        assert viewset.get_serializer_class() == AddOnProductCreateSerializer

    def test_get_serializer_class_for_list(self):
        """Should use AddOnProductSerializer for list action."""
        from smoothschedule.billing.api.views import AddOnProductViewSet
        from smoothschedule.billing.api.serializers import AddOnProductSerializer

        viewset = AddOnProductViewSet()
        viewset.action = 'list'

        assert viewset.get_serializer_class() == AddOnProductSerializer
