"""Tests for webhook handling and signature verification."""

from __future__ import annotations

from unittest.mock import MagicMock, patch

import pytest
from billing.webhooks import handle_webhook
from stripe import SignatureVerificationError


class TestHandleWebhook:
    """Tests for handle_webhook function."""

    def _make_stripe_event(self, event_type: str, obj_data: dict) -> MagicMock:
        """Helper to create a mock Stripe event."""
        event = MagicMock()
        event.type = event_type
        event.data.object = MagicMock(**obj_data)
        # Ensure dict-style access works for nested items
        if "items" in obj_data:
            event.data.object.__getitem__ = lambda self, key: obj_data[key]
        return event

    @patch("billing.webhooks.stripe.Webhook.construct_event")
    def test_checkout_completed_event(self, mock_construct: MagicMock) -> None:
        mock_event = self._make_stripe_event(
            "checkout.session.completed",
            {
                "id": "cs_test_123",
                "customer": "cus_test_456",
                "client_reference_id": "user_abc",
                "subscription": "sub_test_789",
                "customer_email": "user@example.com",
                "customer_details": None,
                "metadata": {"user_id": "user_abc", "plan": "pro"},
            },
        )
        mock_construct.return_value = mock_event

        result = handle_webhook(b"payload", "sig_header")

        assert result.type == "checkout.session.completed"
        assert result.data["session_id"] == "cs_test_123"
        assert result.data["customer_id"] == "cus_test_456"
        assert result.data["user_id"] == "user_abc"
        assert result.data["subscription_id"] == "sub_test_789"

    @patch("billing.webhooks.stripe.Webhook.construct_event")
    def test_subscription_deleted_event(self, mock_construct: MagicMock) -> None:
        mock_event = self._make_stripe_event(
            "customer.subscription.deleted",
            {
                "id": "sub_test_789",
                "customer": "cus_test_456",
                "status": "canceled",
                "ended_at": 1700000000,
            },
        )
        mock_construct.return_value = mock_event

        result = handle_webhook(b"payload", "sig_header")

        assert result.type == "customer.subscription.deleted"
        assert result.data["subscription_id"] == "sub_test_789"
        assert result.data["status"] == "canceled"
        assert result.data["ended_at"] == 1700000000

    @patch("billing.webhooks.stripe.Webhook.construct_event")
    def test_invoice_payment_succeeded(self, mock_construct: MagicMock) -> None:
        mock_event = self._make_stripe_event(
            "invoice.payment_succeeded",
            {
                "id": "in_test_001",
                "customer": "cus_test_456",
                "subscription": "sub_test_789",
                "amount_paid": 2999,
                "currency": "usd",
                "hosted_invoice_url": "https://invoice.stripe.com/i/test",
                "invoice_pdf": "https://invoice.stripe.com/i/test/pdf",
            },
        )
        mock_construct.return_value = mock_event

        result = handle_webhook(b"payload", "sig_header")

        assert result.type == "invoice.payment_succeeded"
        assert result.data["amount_paid"] == 2999
        assert result.data["currency"] == "usd"
        assert result.data["pdf_url"] == "https://invoice.stripe.com/i/test/pdf"

    @patch("billing.webhooks.stripe.Webhook.construct_event")
    def test_invoice_payment_failed(self, mock_construct: MagicMock) -> None:
        mock_event = self._make_stripe_event(
            "invoice.payment_failed",
            {
                "id": "in_test_002",
                "customer": "cus_test_456",
                "subscription": "sub_test_789",
                "amount_due": 2999,
                "currency": "usd",
                "attempt_count": 2,
                "next_payment_attempt": 1700100000,
            },
        )
        mock_construct.return_value = mock_event

        result = handle_webhook(b"payload", "sig_header")

        assert result.type == "invoice.payment_failed"
        assert result.data["attempt_count"] == 2
        assert result.data["next_payment_attempt"] == 1700100000

    @patch("billing.webhooks.stripe.Webhook.construct_event")
    def test_unhandled_event_type_returns_raw_data(self, mock_construct: MagicMock) -> None:
        mock_event = MagicMock()
        mock_event.type = "charge.refunded"
        mock_event.data.object = {"id": "ch_test", "amount": 1000}
        mock_construct.return_value = mock_event

        result = handle_webhook(b"payload", "sig_header")

        assert result.type == "charge.refunded"

    @patch("billing.webhooks.stripe.Webhook.construct_event")
    def test_invalid_signature_raises_value_error(self, mock_construct: MagicMock) -> None:
        mock_construct.side_effect = SignatureVerificationError(
            "No signatures found matching the expected signature for payload",
            sig_header="bad_sig",
        )

        with pytest.raises(ValueError, match="Invalid webhook signature"):
            handle_webhook(b"payload", "bad_sig")

    @patch("billing.webhooks.stripe.Webhook.construct_event")
    def test_construct_event_called_with_correct_args(self, mock_construct: MagicMock) -> None:
        mock_event = MagicMock()
        mock_event.type = "charge.refunded"
        mock_event.data.object = {}
        mock_construct.return_value = mock_event

        handle_webhook(b"raw_body_data", "whsec_sig_value")

        mock_construct.assert_called_once_with(
            b"raw_body_data",
            "whsec_sig_value",
            "whsec_test_fake_secret",
        )
