"""Source-adapter interface.

The Mint API speaks only the normalized event types defined here.
Each storefront has its own adapter that:

- Verifies the storefront's webhook signature in its native format.
- Parses the storefront's payload into a :class:`SaleEvent` or
  :class:`RefundEvent`.
- Maps the storefront's product/variant IDs to a license tier via
  the per-source config in :mod:`app.adapters.config`.

Adding a new source (Lemon Squeezy, Stripe, Paddle) is one new
module that implements :class:`SourceAdapter`. The Mint API and DB
do not change.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from decimal import Decimal
from typing import Any, Optional, Protocol


@dataclass(frozen=True)
class SaleEvent:
    """A storefront sale, normalized.

    The Mint API consumes this directly — it never reaches into the
    raw storefront payload. Anything storefront-specific that's worth
    keeping is preserved in :attr:`raw_payload` for audit.
    """

    source: str                              # e.g. "gumroad", "manual"
    source_order_id: Optional[str]           # storefront's order ID; None for manual mints
    buyer_name: str
    buyer_email: str
    tier: str                                # mapped from product/variant
    years: int = 1
    promotion: Optional[str] = None
    amount_paid: Optional[Decimal] = None
    currency: Optional[str] = "USD"
    notes: Optional[str] = None
    raw_payload: dict = field(default_factory=dict)


@dataclass(frozen=True)
class RefundEvent:
    """A storefront refund — marks an existing license revoked."""

    source: str
    source_order_id: str
    reason: Optional[str] = None
    raw_payload: dict = field(default_factory=dict)


class SourceAdapter(Protocol):
    """Interface every storefront adapter implements."""

    source_name: str

    def verify_webhook(self, *, body: bytes, headers: dict[str, str]) -> bool:
        """Return True iff the request came from the legitimate storefront."""
        ...

    def parse_sale(self, payload: dict[str, Any]) -> Optional[SaleEvent]:
        """Return a :class:`SaleEvent` if *payload* is a sale, else None."""
        ...

    def parse_refund(self, payload: dict[str, Any]) -> Optional[RefundEvent]:
        """Return a :class:`RefundEvent` if *payload* is a refund, else None."""
        ...
