"""Storefront webhook receivers.

PR 2 wires Gumroad. Future storefronts each get their own route
(``/webhooks/lemonsqueezy``, ``/webhooks/stripe``, ...). All share
the same downstream flow: audit-log the raw payload, parse via
adapter, mint, send email, mark processed.

Handler contract
----------------

We **always** return 200 once a request authenticates, even on
downstream failures. Gumroad retries non-2xx for ~3 days, which
would turn a single broken sale into hours of duplicate webhook
storms. Our idempotency keys (``UNIQUE(source, source_order_id)``)
make at-least-once handling safe; the storefront retries on
network errors only.

When something downstream fails (unmapped product, DB error, email
failure), we record the cause in ``gumroad_events.error`` so the
operator can fix and replay.

Unauthenticated requests return 404 — we don't want to signal
endpoint existence or "wrong secret" to a prober.
"""

from __future__ import annotations

import logging
from typing import Optional

from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
from sqlalchemy.orm import Session

from app.adapters.gumroad import GumroadAdapter, UnmappedProductError
from app.config import get_settings
from app.db import get_session
from app.email import EmailDeliveryError, LicenseEmail, get_email_service
from app.mint import mint_from_sale
from app.models import GumroadEvent

router = APIRouter(prefix="/webhooks")
log = logging.getLogger(__name__)


def _gumroad_adapter() -> GumroadAdapter:
    settings = get_settings()
    return GumroadAdapter(secret=settings.resolve_gumroad_secret())


@router.post("/gumroad", status_code=200)
async def gumroad(
    request: Request,
    secret: Optional[str] = Query(default=None),
    session: Session = Depends(get_session),
) -> dict:
    adapter = _gumroad_adapter()

    if not adapter.verify_secret(secret):
        # 404 — no information leak about endpoint existence.
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Not found")

    # Gumroad's Ping is form-encoded; FastAPI doesn't auto-parse
    # without a Form() dependency, and we want the raw map for the
    # audit log regardless of schema.
    raw_form = await request.form()
    payload = {k: str(v) for k, v in raw_form.items()}

    # Audit row FIRST — any later failure leaves us a replayable record.
    event = GumroadEvent(
        event_type="sale",
        order_id=payload.get("sale_id"),
        raw_payload=payload,
    )
    session.add(event)
    session.flush()

    try:
        sale = adapter.parse_sale(payload)
    except UnmappedProductError as e:
        event.error = str(e)
        log.warning("Gumroad sale with unmapped product: %s", e)
        return {"status": "logged-no-mint", "reason": "unmapped_product"}
    except Exception as e:  # pragma: no cover — defensive
        event.error = f"parse error: {e!r}"
        log.exception("Gumroad parse failure")
        return {"status": "logged-no-mint", "reason": "parse_error"}

    if sale is None:
        event.error = "payload did not parse as a sale"
        return {"status": "logged-no-mint", "reason": "not_a_sale"}

    try:
        row = mint_from_sale(session, sale)
        session.flush()
    except Exception as e:  # pragma: no cover — defensive
        event.error = f"mint error: {e!r}"
        log.exception("mint_from_sale failed")
        return {"status": "logged-no-mint", "reason": "mint_error"}

    try:
        get_email_service().send_license(
            LicenseEmail(
                to_name=row.name,
                to_email=row.email,
                tier=row.tier,
                license_key=row.license_key,
                expires_at_iso=row.expires_at.isoformat(),
                blob=row.blob,
            )
        )
    except EmailDeliveryError as e:
        event.error = f"email error: {e}"
        log.warning("Email delivery failed (license already minted): %s", e)
        # The buyer can still be served from the DB via the renewal
        # portal in PR 3 / a manual resend, so we don't fail the
        # webhook over an email hiccup.
        event.processed = True
        return {"status": "minted-email-failed", "license_key": row.license_key}

    event.processed = True
    return {"status": "ok", "license_key": row.license_key}
