"""Cloudflare Turnstile verification (tasks 2.10, 8.5).

The open registration link is the one endpoint anyone on the internet can POST to, and it
creates guest records. Turnstile is what keeps a script from filling the guest list with
noise the couple then has to clean out by hand the week of the wedding.

The widget token from the browser proves nothing on its own — it is only evidence once
this server has exchanged it with Cloudflare. A frontend that renders the widget without
this call looks protected and is not, which is the failure mode this module exists to
close.
"""

import logging

import httpx

from app.config import get_settings

logger = logging.getLogger(__name__)

VERIFY_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify"

#: Cloudflare's documented always-passing test secret. Present in dev so the flow is
#: exercisable without an account; `assert_production_ready()` refuses to boot on it.
TEST_SECRET_PREFIX = "1x0000"

TIMEOUT_SECONDS = 8.0


async def verify(token: str | None, *, remote_ip: str | None = None) -> bool:
    """Exchange a widget token with Cloudflare. False means "do not accept this request".

    A token is single-use: Cloudflare rejects the second exchange of the same one, which is
    what stops a captured token being replayed across many submissions.
    """
    settings = get_settings()
    secret = settings.turnstile_secret_key

    if secret.startswith(TEST_SECRET_PREFIX):
        # The dev test key passes for any input, including none. Short-circuiting keeps
        # local development and CI free of a network call whose answer is always yes.
        return True

    if not token:
        return False

    payload = {"secret": secret, "response": token}
    if remote_ip:
        payload["remoteip"] = remote_ip

    try:
        async with httpx.AsyncClient(timeout=TIMEOUT_SECONDS) as client:
            response = await client.post(VERIFY_URL, data=payload)
        response.raise_for_status()
        result = response.json()
    except (httpx.HTTPError, ValueError):
        # Fail closed. Cloudflare being unreachable is rare; letting every bot through
        # while it is unreachable is not a trade worth making for an endpoint whose only
        # job is creating records. The per-IP rate limit remains the second line either
        # way, and a genuine guest can retry.
        logger.exception("Turnstile verification could not be completed")
        return False

    if not result.get("success"):
        logger.info("Turnstile rejected a submission: %s", result.get("error-codes"))
        return False
    return True
