"""Comprehensive unit tests for StripeAdapter."""

from unittest.mock import Mock, patch

import pytest

from augint_billing_lib.adapters.stripe import StripeAdapter


@pytest.mark.unit
class TestStripeAdapterInit:
    """Test StripeAdapter initialization."""

    @patch("augint_billing_lib.adapters.stripe.stripe")
    def test_init_with_price_id(self, mock_stripe):
        """Test initialization with explicit price ID."""
        adapter = StripeAdapter("sk_test_123", "price_metered")
        assert adapter.price_id == "price_metered"
        assert mock_stripe.api_key == "sk_test_123"

    @patch("augint_billing_lib.adapters.stripe.stripe")
    def test_init_without_price_id(self, mock_stripe):
        """Test initialization without price ID."""
        adapter = StripeAdapter("sk_test_456", None)
        assert adapter.price_id is None
        assert mock_stripe.api_key == "sk_test_456"


@pytest.mark.unit
class TestStripeAdapterMethods:
    """Test StripeAdapter methods."""

    @patch("augint_billing_lib.adapters.stripe.stripe")
    def test_has_default_payment_method_true(self, mock_stripe):
        """Test has_default_payment_method returns True when method exists."""
        adapter = StripeAdapter("sk_test", "price_123")

        # Mock customer with default payment method
        mock_customer = {"id": "cus_test", "invoice_settings": {"default_payment_method": "pm_123"}}

        with patch.object(adapter, "_retrieve_customer", return_value=mock_customer):
            result = adapter.has_default_payment_method("cus_test")
            assert result is True

    @patch("augint_billing_lib.adapters.stripe.stripe")
    def test_has_default_payment_method_false(self, mock_stripe):
        """Test has_default_payment_method returns False when no method."""
        adapter = StripeAdapter("sk_test", "price_123")

        # Mock customer without default payment method
        mock_customer = {"id": "cus_test", "invoice_settings": {}}

        with patch.object(adapter, "_retrieve_customer", return_value=mock_customer):
            result = adapter.has_default_payment_method("cus_test")
            assert result is False

    @patch("augint_billing_lib.adapters.stripe.stripe")
    def test_has_default_payment_method_no_invoice_settings(self, mock_stripe):
        """Test has_default_payment_method when no invoice settings."""
        adapter = StripeAdapter("sk_test", "price_123")

        # Mock customer without invoice_settings key
        mock_customer = {"id": "cus_test"}

        with patch.object(adapter, "_retrieve_customer", return_value=mock_customer):
            result = adapter.has_default_payment_method("cus_test")
            assert result is False

    @patch("augint_billing_lib.adapters.stripe.stripe")
    def test_ensure_metered_subscription_existing(self, mock_stripe):
        """Test ensure_metered_subscription when subscription already exists."""
        adapter = StripeAdapter("sk_test", "price_metered")

        # Mock existing metered subscription
        mock_sub = {
            "id": "sub_123",
            "items": {
                "data": [{"id": "si_existing", "price": {"recurring": {"usage_type": "metered"}}}]
            },
        }

        mock_subs = Mock()
        mock_subs.auto_paging_iter.return_value = [mock_sub]

        with patch.object(adapter, "_list_subs", return_value=mock_subs):
            result = adapter.ensure_metered_subscription("cus_test")
            assert result == "si_existing"

    @patch("augint_billing_lib.adapters.stripe.stripe")
    def test_ensure_metered_subscription_create_new(self, mock_stripe):
        """Test ensure_metered_subscription creates new subscription."""
        adapter = StripeAdapter("sk_test", "price_metered")

        # Mock no existing subscriptions
        mock_subs = Mock()
        mock_subs.auto_paging_iter.return_value = []

        # Mock new subscription creation
        mock_new_sub = {"id": "sub_new", "items": {"data": [{"id": "si_new"}]}}

        with (
            patch.object(adapter, "_list_subs", return_value=mock_subs),
            patch.object(adapter, "_create_sub", return_value=mock_new_sub),
        ):
            result = adapter.ensure_metered_subscription("cus_test")
            assert result == "si_new"

    @patch("augint_billing_lib.adapters.stripe.stripe")
    def test_ensure_metered_subscription_no_price_id(self, mock_stripe):
        """Test ensure_metered_subscription raises error when no price ID."""
        adapter = StripeAdapter("sk_test", None)

        # Mock no existing subscriptions
        mock_subs = Mock()
        mock_subs.auto_paging_iter.return_value = []

        with patch.object(adapter, "_list_subs", return_value=mock_subs):
            with pytest.raises(RuntimeError) as exc:
                adapter.ensure_metered_subscription("cus_test")
            assert "No metered price available" in str(exc.value)

    @patch("augint_billing_lib.adapters.stripe.stripe")
    def test_cancel_subscription_if_any(self, mock_stripe):
        """Test cancel_subscription_if_any cancels all active subscriptions."""
        adapter = StripeAdapter("sk_test", "price_123")

        # Mock active subscriptions
        mock_subs = Mock()
        mock_subs.auto_paging_iter.return_value = [{"id": "sub_1"}, {"id": "sub_2"}]

        with (
            patch.object(adapter, "_list_subs", return_value=mock_subs),
            patch.object(adapter, "_modify_sub") as mock_modify,
        ):
            adapter.cancel_subscription_if_any("cus_test")

            # Verify both subscriptions were cancelled
            assert mock_modify.call_count == 2
            mock_modify.assert_any_call("sub_1", cancel_at_period_end=True)
            mock_modify.assert_any_call("sub_2", cancel_at_period_end=True)

    @patch("augint_billing_lib.adapters.stripe.stripe")
    def test_cancel_subscription_no_active(self, mock_stripe):
        """Test cancel_subscription_if_any when no active subscriptions."""
        adapter = StripeAdapter("sk_test", "price_123")

        # Mock no active subscriptions
        mock_subs = Mock()
        mock_subs.auto_paging_iter.return_value = []

        with (
            patch.object(adapter, "_list_subs", return_value=mock_subs),
            patch.object(adapter, "_modify_sub") as mock_modify,
        ):
            adapter.cancel_subscription_if_any("cus_test")

            # Verify no modifications were made
            mock_modify.assert_not_called()

    @patch("augint_billing_lib.adapters.stripe.stripe")
    def test_report_usage(self, mock_stripe):
        """Test report_usage calls Stripe Meter Event API correctly."""
        adapter = StripeAdapter("sk_test", "price_123")

        # Mock subscription item and subscription retrieval
        mock_sub_item = Mock()
        mock_sub_item.subscription = "sub_123"
        mock_stripe.SubscriptionItem.retrieve.return_value = mock_sub_item

        mock_subscription = Mock()
        mock_subscription.customer = "cus_test123"
        mock_stripe.Subscription.retrieve.return_value = mock_subscription

        # Mock MeterEvent creation
        mock_meter_event = {"id": "evt_123", "object": "billing.meter_event"}
        mock_stripe.billing.MeterEvent.create.return_value = mock_meter_event

        result = adapter.report_usage("si_123", 1000, 1234567890, "key_123")

        # Verify subscription lookups
        mock_stripe.SubscriptionItem.retrieve.assert_called_once_with("si_123")
        mock_stripe.Subscription.retrieve.assert_called_once_with("sub_123")

        # Verify MeterEvent creation
        mock_stripe.billing.MeterEvent.create.assert_called_once_with(
            event_name="api_usage",
            payload={"stripe_customer_id": "cus_test123", "value": "1000"},
            timestamp=1234567890,
            idempotency_key="key_123",
        )

        assert result == mock_meter_event

    @patch("augint_billing_lib.adapters.stripe.stripe")
    def test_discover_metered_price_id_with_product(self, mock_stripe):
        """Test discover_metered_price_id for specific product."""
        adapter = StripeAdapter("sk_test", None)

        # Mock prices for specific product
        mock_prices = Mock()
        mock_prices.auto_paging_iter.return_value = [
            {"id": "price_metered_123", "recurring": {"usage_type": "metered"}}
        ]

        with patch.object(adapter, "_list_prices", return_value=mock_prices) as mock_list:
            result = adapter.discover_metered_price_id("prod_api")

            assert result == "price_metered_123"
            mock_list.assert_called_with(active=True, product="prod_api", limit=100)

    @patch("augint_billing_lib.adapters.stripe.stripe")
    def test_discover_metered_price_id_no_product(self, mock_stripe):
        """Test discover_metered_price_id searches all products."""
        adapter = StripeAdapter("sk_test", None)

        # Mock prices with one metered
        mock_prices = Mock()
        mock_prices.auto_paging_iter.return_value = [
            {"id": "price_fixed", "recurring": {"usage_type": "licensed"}},
            {"id": "price_metered_456", "recurring": {"usage_type": "metered"}},
        ]

        with patch.object(adapter, "_list_prices", return_value=mock_prices) as mock_list:
            result = adapter.discover_metered_price_id()

            assert result == "price_metered_456"
            mock_list.assert_called_with(active=True, limit=100)

    @patch("augint_billing_lib.adapters.stripe.stripe")
    def test_discover_metered_price_id_none_found(self, mock_stripe):
        """Test discover_metered_price_id returns None when no metered prices."""
        adapter = StripeAdapter("sk_test", None)

        # Mock prices with no metered ones
        mock_prices = Mock()
        mock_prices.auto_paging_iter.return_value = [
            {"id": "price_1", "recurring": {"usage_type": "licensed"}},
            {"id": "price_2", "recurring": None},
        ]

        with patch.object(adapter, "_list_prices", return_value=mock_prices):
            result = adapter.discover_metered_price_id()
            assert result is None


@pytest.mark.unit
class TestStripeAdapterRetryDecorators:
    """Test retry decorators on static methods."""

    @patch("augint_billing_lib.adapters.stripe.stripe.Price.list")
    def test_list_prices_retry(self, mock_list):
        """Test _list_prices includes retry decorator."""
        mock_list.return_value = {"data": []}

        adapter = StripeAdapter("sk_test", "price_123")
        result = adapter._list_prices()

        mock_list.assert_called_once_with(limit=100, active=True)
        assert result == {"data": []}

    @patch("augint_billing_lib.adapters.stripe.stripe.Customer.retrieve")
    def test_retrieve_customer_retry(self, mock_retrieve):
        """Test _retrieve_customer includes retry decorator."""
        mock_retrieve.return_value = {"id": "cus_123"}

        adapter = StripeAdapter("sk_test", "price_123")
        result = adapter._retrieve_customer("cus_123")

        mock_retrieve.assert_called_once_with("cus_123")
        assert result == {"id": "cus_123"}

    @patch("augint_billing_lib.adapters.stripe.stripe.Subscription.list")
    def test_list_subs_retry(self, mock_list):
        """Test _list_subs includes retry decorator."""
        mock_list.return_value = {"data": []}

        adapter = StripeAdapter("sk_test", "price_123")
        result = adapter._list_subs(customer="cus_123")

        mock_list.assert_called_once_with(customer="cus_123", limit=100)
        assert result == {"data": []}

    @patch("augint_billing_lib.adapters.stripe.stripe.Subscription.create")
    def test_create_sub_retry(self, mock_create):
        """Test _create_sub includes retry decorator."""
        mock_create.return_value = {"id": "sub_123"}

        adapter = StripeAdapter("sk_test", "price_123")
        result = adapter._create_sub(customer="cus_123", items=[])

        mock_create.assert_called_once_with(customer="cus_123", items=[])
        assert result == {"id": "sub_123"}

    @patch("augint_billing_lib.adapters.stripe.stripe.Subscription.modify")
    def test_modify_sub_retry(self, mock_modify):
        """Test _modify_sub includes retry decorator."""
        mock_modify.return_value = {"id": "sub_123"}

        adapter = StripeAdapter("sk_test", "price_123")
        result = adapter._modify_sub("sub_123", cancel_at_period_end=True)

        mock_modify.assert_called_once_with("sub_123", cancel_at_period_end=True)
        assert result == {"id": "sub_123"}
