"""Monthly-digest signup.

The form ships ahead of email delivery on purpose (see models.PendingDigest):
collecting the list is the slow part, so signups are stored now and confirmed
once SMTP is live. Nothing is mailed while mailer.enabled() is False.

Privacy: the address is used for the digest and nothing else, and the only other
thing recorded is the bare referrer domain for attribution.
"""
from __future__ import annotations

import re
import time

from fastapi import Request, Response
from fastapi.concurrency import run_in_threadpool
from fastapi.responses import JSONResponse
from sqlalchemy import select

from notifications import mailer
from core import metrics
from accounts.db import async_session_maker
from accounts.models import PendingDigest

# Deliberately permissive: real-world addresses defeat clever patterns, and the
# confirmation email is the actual validator. This only rejects obvious junk.
_EMAIL_RE = re.compile(r"^[^@\s]+@[^@\s.]+(\.[^@\s.]+)+$")

# The same answer for every outcome — new row, duplicate, or throttled. Anything
# else would turn the endpoint into an oracle for "is this address subscribed?".
GENERIC_OK = "Check your inbox to confirm your address."

# Don't re-send a confirmation to an unconfirmed address more than this often.
RESEND_INTERVAL_S = 600


def _valid(email: str) -> bool:
    return bool(email) and len(email) <= 320 and bool(_EMAIL_RE.match(email))


async def _store(email: str, place: str | None, source: str | None) -> None:
    """Upsert the signup. A repeat submit updates the existing row rather than
    creating a second one — the unique constraint on email is the idempotency."""
    async with async_session_maker() as session:
        existing = (
            await session.execute(
                select(PendingDigest).where(PendingDigest.email == email)
            )
        ).scalar_one_or_none()

        now = time.time()
        if existing is None:
            session.add(PendingDigest(
                email=email, place_label=place, source=source, created_at=now,
            ))
        else:
            if place and not existing.place_label:
                existing.place_label = place
            # Re-subscribing after an unsubscribe re-opens the row.
            existing.unsubscribed_at = None
            if existing.confirmed_at is None:
                if existing.last_sent_at and now - existing.last_sent_at < RESEND_INTERVAL_S:
                    await session.commit()
                    return
                existing.last_sent_at = now
        await session.commit()

    # Nothing is sent until a real mail backend is configured; the console
    # backend logs it so the flow can be verified end-to-end without a server.
    if mailer.enabled():
        # Blocking SMTP connect+send — keep it off the event loop.
        await run_in_threadpool(
            mailer.send,
            email,
            "Confirm your Thermograph digest",
            "Thanks for signing up for the Thermograph monthly digest.\n\n"
            "Confirmation links are not active yet, but you're on the list, and "
            "you'll get a confirmation as soon as they are.\n",
        )


async def signup(request: Request) -> Response:
    """POST {BASE}/digest — accepts a JSON body (the fetch path) or a plain form
    post (the no-JS path). Always answers 200 with the same message."""
    email = ""
    place = None
    try:
        ctype = request.headers.get("content-type", "")
        if ctype.startswith("application/json"):
            body = await request.json()
            if isinstance(body, dict):
                email = str(body.get("email") or "").strip().lower()
                place = (str(body.get("place")).strip() or None) if body.get("place") else None
        else:
            form = await request.form()
            email = str(form.get("email") or "").strip().lower()
            place = (str(form.get("place")).strip() or None) if form.get("place") else None
    except Exception:  # noqa: BLE001 - a malformed body is just an invalid signup
        pass

    if _valid(email):
        source = metrics.normalize_referrer(
            request.headers.get("referer"), request.headers.get("host")
        )
        try:
            await _store(email, place, source)
        except Exception:  # noqa: BLE001 - never surface storage detail to the form
            pass
        await run_in_threadpool(
            metrics.record_event,
            "home.digest_signup",
            referer=request.headers.get("referer"),
            host=request.headers.get("host"),
            ip=request.client.host if request.client else None,
        )
        return JSONResponse({"ok": True, "message": GENERIC_OK})

    return JSONResponse(
        {"ok": False, "message": "That doesn't look like an email address."},
        status_code=400,
    )
