"""End-to-end tests for complete billing workflows."""

from datetime import UTC, datetime, timedelta

import pytest

from augint_billing_lib.models import Link


@pytest.mark.e2e
class TestCardAttachmentFlow:
    """Test complete card attachment flow."""

    def test_card_attachment_to_billing_flow(self, billing_service, mock_repo, mock_stripe):
        """Test flow from card attachment to usage billing."""
        # Step 1: Customer starts as free user
        link = Link(
            api_key_id="api_key_123",
            stripe_customer_id="cus_test",
            plan="free",
            usage_plan_id="FREE_10K",
        )
        mock_repo.save(link)

        # Step 2: Customer attaches payment method
        mock_stripe.has_pm = True
        pm_event = {"type": "payment_method.attached", "data": {"object": {"customer": "cus_test"}}}

        result = billing_service.handle_stripe_event(pm_event)
        assert result["target_plan"] == "metered"

        # Step 3: Apply plan move (simulating Lambda handler)
        moved = billing_service.apply_plan_move_for_customer(
            "cus_test", "metered", result["subscription_item_id"], "FREE_10K", "METERED"
        )
        assert moved == 1

        # Verify customer is now on metered plan
        updated_link = mock_repo.get_by_api_key("api_key_123")
        assert updated_link.plan == "metered"
        assert updated_link.usage_plan_id == "METERED"
        assert updated_link.metered_subscription_item_id == "si_test999"

        # Step 4: Report usage
        billing_service.usage.set_usage("METERED", "api_key_123", 500)

        since = datetime(2024, 1, 1, 0, 0, 0, tzinfo=UTC)
        until = datetime(2024, 1, 1, 1, 0, 0, tzinfo=UTC)

        reports = billing_service.reconcile_usage_window(since, until)

        assert len(reports) == 1
        assert reports[0]["api_key_id"] == "api_key_123"
        assert reports[0]["units"] == 500

        # Verify usage was reported to Stripe
        assert len(mock_stripe.usage_reports) == 1
        assert mock_stripe.usage_reports[0]["units"] == 500


@pytest.mark.e2e
class TestPaymentFailureFlow:
    """Test payment failure and recovery flow."""

    def test_payment_failure_demotion_flow(self, billing_service, mock_repo):
        """Test flow when payment fails."""
        # Step 1: Customer on metered plan
        link = Link(
            api_key_id="api_key_123",
            stripe_customer_id="cus_test",
            plan="metered",
            usage_plan_id="METERED",
            metered_subscription_item_id="si_active",
            current_month_reported_units=10000,
        )
        mock_repo.save(link)

        # Step 2: Payment fails
        fail_event = {
            "type": "invoice.payment_failed",
            "data": {"object": {"customer": "cus_test", "amount_due": 5000}},
        }

        result = billing_service.handle_stripe_event(fail_event)
        assert result["target_plan"] == "free"

        # Step 3: Apply demotion
        moved = billing_service.apply_plan_move_for_customer(
            "cus_test", "free", None, "FREE_10K", "METERED"
        )
        assert moved == 1

        # Verify customer is back on free plan
        updated_link = mock_repo.get_by_api_key("api_key_123")
        assert updated_link.plan == "free"
        assert updated_link.usage_plan_id == "FREE_10K"

        # Usage should not be reported for free customers
        metered = mock_repo.scan_metered()
        assert len(metered) == 0

    def test_payment_recovery_flow(self, billing_service, mock_repo, mock_stripe):
        """Test recovery after payment failure."""
        # Step 1: Customer demoted to free after payment failure
        link = Link(
            api_key_id="api_key_123",
            stripe_customer_id="cus_test",
            plan="free",
            usage_plan_id="FREE_10K",
        )
        mock_repo.save(link)

        # Step 2: Customer updates payment method
        mock_stripe.has_pm = True

        # Step 3: Setup intent succeeds
        setup_event = {
            "type": "setup_intent.succeeded",
            "data": {"object": {"customer": "cus_test", "payment_method": "pm_new"}},
        }

        result = billing_service.handle_stripe_event(setup_event)
        assert result["target_plan"] == "metered"

        # Step 4: Re-promote to metered
        moved = billing_service.apply_plan_move_for_customer(
            "cus_test", "metered", result["subscription_item_id"], "FREE_10K", "METERED"
        )
        assert moved == 1

        # Verify customer is back on metered plan
        updated_link = mock_repo.get_by_api_key("api_key_123")
        assert updated_link.plan == "metered"
        assert updated_link.usage_plan_id == "METERED"


@pytest.mark.e2e
class TestMonthlyUsageReporting:
    """Test monthly usage reporting scenarios."""

    def test_continuous_usage_reporting(self, billing_service, mock_repo, mock_usage):
        """Test continuous hourly usage reporting."""
        # Setup metered customer
        link = Link(
            api_key_id="api_key_123",
            stripe_customer_id="cus_test",
            plan="metered",
            usage_plan_id="METERED",
            metered_subscription_item_id="si_test",
            last_reported_usage_ts=None,
            current_month_reported_units=0,
        )
        mock_repo.save(link)

        # Simulate hourly reporting over 3 hours
        base_time = datetime(2024, 1, 15, 10, 0, 0, tzinfo=UTC)
        usage_values = [100, 250, 400]  # Cumulative usage

        for hour, total_usage in enumerate(usage_values):
            since = base_time + timedelta(hours=hour)
            until = since + timedelta(hours=1)

            mock_usage.set_usage("METERED", "api_key_123", total_usage)

            reports = billing_service.reconcile_usage_window(since, until)

            assert len(reports) == 1
            assert reports[0]["units"] == total_usage  # MVP assumes non-overlapping

        # Check all usage reports
        assert len(billing_service.stripe.usage_reports) == 3
        reported_units = [r["units"] for r in billing_service.stripe.usage_reports]
        assert reported_units == [100, 250, 400]  # All reported as-is in MVP

    def test_multiple_customers_reporting(self, billing_service, mock_repo, mock_usage):
        """Test usage reporting for multiple customers."""
        # Setup multiple metered customers
        customers = [
            ("api_1", "cus_1", "si_1", 100),
            ("api_2", "cus_2", "si_2", 200),
            ("api_3", "cus_3", "si_3", 300),
        ]

        for api_key, cus_id, sub_item, usage in customers:
            link = Link(
                api_key_id=api_key,
                stripe_customer_id=cus_id,
                plan="metered",
                usage_plan_id="METERED",
                metered_subscription_item_id=sub_item,
            )
            mock_repo.save(link)
            mock_usage.set_usage("METERED", api_key, usage)

        # Report usage for all
        since = datetime(2024, 1, 1, 0, 0, 0, tzinfo=UTC)
        until = datetime(2024, 1, 1, 1, 0, 0, tzinfo=UTC)

        reports = billing_service.reconcile_usage_window(since, until)

        assert len(reports) == 3

        # Verify each customer's usage
        report_map = {r["api_key_id"]: r["units"] for r in reports}
        assert report_map == {"api_1": 100, "api_2": 200, "api_3": 300}

        # Verify Stripe reports
        assert len(billing_service.stripe.usage_reports) == 3
        stripe_map = {
            r["subscription_item_id"]: r["units"] for r in billing_service.stripe.usage_reports
        }
        assert stripe_map == {"si_1": 100, "si_2": 200, "si_3": 300}


@pytest.mark.e2e
class TestIdempotency:
    """Test idempotency in operations."""

    def test_usage_reporting_idempotency(self, billing_service, mock_repo, mock_usage, mock_stripe):
        """Test idempotency keys in usage reporting."""
        link = Link(
            api_key_id="api_key_123",
            stripe_customer_id="cus_test",
            plan="metered",
            usage_plan_id="METERED",
            metered_subscription_item_id="si_test",
        )
        mock_repo.save(link)
        mock_usage.set_usage("METERED", "api_key_123", 100)

        until = datetime(2024, 1, 15, 10, 0, 0, tzinfo=UTC)
        since = until - timedelta(hours=1)

        # First report
        _ = billing_service.reconcile_usage_window(since, until)
        idem_key1 = mock_stripe.usage_reports[0]["idempotency_key"]

        # Verify idempotency key format (now uses api_key:window_start:window_end)
        assert "api_key_123:" in idem_key1
        assert "2024-01-15T09:00:00" in idem_key1  # window start
        assert "2024-01-15T10:00:00" in idem_key1  # window end

        # Same window = skipped (overlap prevention), no new report
        mock_stripe.usage_reports.clear()
        reports = billing_service.reconcile_usage_window(since, until)

        # Verify no new report was made (window already processed)
        assert len(mock_stripe.usage_reports) == 0
        assert len(reports) == 0  # No reports since window was already processed

        # Different window = different idempotency key
        until2 = until + timedelta(hours=1)
        mock_stripe.usage_reports.clear()
        _ = billing_service.reconcile_usage_window(until, until2)
        idem_key3 = mock_stripe.usage_reports[0]["idempotency_key"]

        assert idem_key3 != idem_key1
