"""Pattern B sign-in — "Sign in with Bowden Works".

The app owns its own session; identity comes from auth.bowden.works over OAuth2
authorization-code + PKCE. This host is a Pattern B app host, so it is NEVER gated
(id-auth's gate would claim /auth/* and 404 sign-in). The SPA renders its own
sign-in screen when /api/bw/me says unauthenticated; the button hits /auth/login.

Routes:
  * GET /auth/login    — interactive login (a non-SSO'd visitor lands on the BW
                         login page; expected — the app can't host that form).
  * GET /auth/probe    — one-shot SILENT probe: an already-SSO'd visitor is signed
                         in with no interaction; an anonymous one bounces back.
  * GET /auth/callback — redeem ?code (verify + establish the app session), or
                         handle a silent-probe ?error=login_required.
  * GET /logout        — clear the app session + opt-out marker, then BW sign-out.
"""

import logging

from fastapi import APIRouter, Request
from fastapi.responses import RedirectResponse

from app import bw_auth, bw_client
from app.config import get_settings

log = logging.getLogger(__name__)

router = APIRouter(tags=["auth"])

PROBED_COOKIE = bw_auth.PROBED_COOKIE
OPTOUT_COOKIE = bw_auth.OPTOUT_COOKIE
PROBE_MAX_AGE = 600
OPTOUT_MAX_AGE = 60 * 60 * 24 * 365


def _apply_login_cookies(resp: RedirectResponse, pending: dict) -> None:
    """Attach the PKCE + state cookies start_login() collected. HttpOnly + Secure +
    SameSite=Lax: they carry the PKCE verifier and CSRF nonce across the round-trip
    and must not be JS-readable."""
    for name, (value, max_age) in pending.items():
        resp.set_cookie(name, value, max_age=max_age, httponly=True,
                        secure=True, samesite="lax")


def _establish_session(request: Request, claims: dict) -> str:
    """Once we have a verified BW identity, set the app session and report presence.
    Membership + level live in the accounts kit (granted by an admin invite); this
    only records who is signed in."""
    username = claims["username"].strip().lower()
    request.session["user"] = username
    try:
        bw_client.report_presence(username)
    except Exception as exc:  # noqa: BLE001 - presence is cosmetic, never fatal
        log.warning("report_presence failed for %s: %s", username, exc)
    return username


@router.get("/auth/login")
def login() -> RedirectResponse:
    """Explicit sign-in: interactive OAuth. Clears the opt-out + probe guard."""
    pending: dict = {}
    url, _state = bw_auth.start_login(
        lambda n, v, a: pending.__setitem__(n, (v, a)), silent=False)
    resp = RedirectResponse(url, status_code=302)
    resp.delete_cookie(OPTOUT_COOKIE)
    resp.delete_cookie(PROBED_COOKIE)
    _apply_login_cookies(resp, pending)
    return resp


@router.get("/auth/probe")
def probe(request: Request) -> RedirectResponse:
    """One-shot silent probe for the SPA's auto-sign-in. Loop-guarded: skipped when
    already signed in, opted out, or probed within the window."""
    if request.session.get("user"):
        return RedirectResponse("/", status_code=302)
    if request.cookies.get(OPTOUT_COOKIE) or request.cookies.get(PROBED_COOKIE):
        return RedirectResponse("/", status_code=302)
    pending: dict = {}
    url, _state = bw_auth.start_login(
        lambda n, v, a: pending.__setitem__(n, (v, a)), silent=True)
    resp = RedirectResponse(url, status_code=302)
    resp.set_cookie(PROBED_COOKIE, "1", max_age=PROBE_MAX_AGE, httponly=True,
                    secure=True, samesite="lax")
    _apply_login_cookies(resp, pending)
    return resp


@router.get("/auth/callback")
def callback(request: Request) -> RedirectResponse:
    """id-auth 302s the browser back here — a real ?code, or a silent-probe miss."""
    if bw_auth.is_login_required(dict(request.query_params)):
        # Anonymous visitor: nothing to establish. Back to the SPA (sign-in screen).
        resp = RedirectResponse("/", status_code=302)
        resp.delete_cookie(PROBED_COOKIE)
        return resp

    code = request.query_params.get("code", "")
    state = request.query_params.get("state", "")
    expected_state = request.cookies.get(bw_auth.STATE_COOKIE, "")
    verifier = request.cookies.get(bw_auth.PKCE_COOKIE, "")
    if not code or not state or not expected_state or state != expected_state:
        return _fail(request, "invalid or missing state")
    if not verifier:
        return _fail(request, "missing PKCE verifier (session expired?)")
    try:
        claims = bw_auth.finish_login(code, verifier)
    except bw_auth.BWAuthError as exc:
        log.warning("BW login failed: %s", exc)
        return _fail(request, "sign-in failed")

    _establish_session(request, claims)
    resp = RedirectResponse("/", status_code=302)
    for cookie in (bw_auth.PKCE_COOKIE, bw_auth.STATE_COOKIE, PROBED_COOKIE):
        resp.delete_cookie(cookie)
    return resp


def _fail(request: Request, why: str) -> RedirectResponse:
    resp = RedirectResponse("/?auth_error=1", status_code=302)
    for cookie in (bw_auth.PKCE_COOKIE, bw_auth.STATE_COOKIE, PROBED_COOKIE):
        resp.delete_cookie(cookie)
    return resp


@router.get("/logout")
def logout(request: Request) -> RedirectResponse:
    """Clear the app session + set the opt-out marker (so the silent probe won't
    instantly re-sign-in), then bounce to BW for a full SSO sign-out."""
    request.session.clear()
    target = bw_auth.logout_url() if get_settings().has_bw_client else "/"
    resp = RedirectResponse(target, status_code=302)
    resp.set_cookie(OPTOUT_COOKIE, "1", max_age=OPTOUT_MAX_AGE, httponly=True,
                    secure=True, samesite="lax")
    resp.delete_cookie(PROBED_COOKIE)
    return resp
