"""BW Auth (Pattern B, PUBLIC mode) — interactive login, OAuth callback, logout.

All PUBLIC (no app session required) — AuthMiddleware allowlists them (they're
not under /api or /notes). The SILENT auto-signin probe for anonymous SPA loads
lives in main.py's SPA handler; this router handles:

  GET /login          explicit "Sign in" click → interactive OAuth (silent=False)
  GET /auth/callback  the OAuth return — a real ?code, OR a silent-probe miss
                      (?error=login_required) for an anonymous visitor
  GET /logout         clear the app session, opt out of the silent probe, then
                      full BW sign-out

Sync def: these do blocking server-to-server calls to id-auth (finish_login)
+ a DB upsert; FastAPI runs sync routes in a threadpool. No AI/SSE here.
"""
from __future__ import annotations

from fastapi import APIRouter, Depends, Request
from sqlalchemy.orm import Session
from starlette.responses import PlainTextResponse, RedirectResponse

from .. import bw_auth
from ..app_session import (
    OPTOUT_COOKIE,
    OPTOUT_MAX_AGE,
    PROBE_MAX_AGE,
    PROBED_COOKIE,
    SESSION_COOKIE,
    safe_next,
    sign_session,
    verify_session,
)
from ..config import get_settings
from ..db import get_db
from ..services.bw_identity import establish_user, report_access_level

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

# Where to land after login — a same-origin path, stashed before the redirect.
NEXT_COOKIE = "bw_next"

_COOKIE_KW = dict(httponly=True, secure=True, samesite="lax", path="/")


def _apply_login_cookies(resp, pending: dict) -> None:
    """Attach the PKCE + state cookies collected by start_login()."""
    for name, (value, max_age) in pending.items():
        resp.set_cookie(name, value, max_age=max_age, **_COOKIE_KW)


@router.get("/login")
def login(request: Request):
    """Explicit sign-in. Interactive (a non-SSO'd visitor lands on the BW login
    page — expected). Clears the opt-out + probe guard so a signed-out user who
    clicks Sign in is opted back in."""
    nxt = safe_next(request.query_params.get("next"))
    pending: dict = {}

    def set_cookie(name, value, max_age):
        pending[name] = (value, max_age)

    url, _state = bw_auth.start_login(set_cookie, silent=False)
    resp = RedirectResponse(url, status_code=302)
    resp.delete_cookie(OPTOUT_COOKIE, path="/")
    resp.delete_cookie(PROBED_COOKIE, path="/")
    resp.set_cookie(NEXT_COOKIE, nxt, max_age=600, **_COOKIE_KW)
    _apply_login_cookies(resp, pending)
    return resp


@router.get("/auth/probe")
def probe(request: Request):
    """Client-triggered SILENT SSO probe (SW-denylisted, so it reaches the
    server). AppShell redirects here when /api/me says should_probe. Fires a
    prompt=none authorize: an already-SSO'd visitor comes back with a code
    (signed in), an anonymous one comes back with error=login_required (welcome
    page). Loop-guarded by the bw_probed cookie so it runs at most once."""
    settings = get_settings()
    token = request.cookies.get(SESSION_COOKIE, "")
    # Already signed in, already probed, or opted out → don't (re-)probe.
    if verify_session(token, settings.app_session_secret):
        return RedirectResponse("/", status_code=302)
    if request.cookies.get(OPTOUT_COOKIE) or request.cookies.get(PROBED_COOKIE):
        return RedirectResponse("/", status_code=302)
    if not (settings.domain_mode == "public" and settings.bw_client_id):
        return RedirectResponse("/", status_code=302)

    nxt = safe_next(request.query_params.get("next") or "/")
    pending: dict = {}

    def set_cookie(name, value, max_age):
        pending[name] = (value, max_age)

    url, _state = bw_auth.start_login(set_cookie, silent=True)
    resp = RedirectResponse(url, status_code=302)
    resp.set_cookie(PROBED_COOKIE, "1", max_age=PROBE_MAX_AGE, **_COOKIE_KW)
    resp.set_cookie(NEXT_COOKIE, nxt, max_age=600, **_COOKIE_KW)
    _apply_login_cookies(resp, pending)
    return resp


@router.get("/auth/callback")
def callback(request: Request, db: Session = Depends(get_db)):
    settings = get_settings()
    nxt = safe_next(request.cookies.get(NEXT_COOKIE))

    # Silent-probe miss: an anonymous visitor. Set the loop-guard, land on the
    # SPA (which shows the app's OWN welcome page). Do NOT re-probe.
    if bw_auth.is_login_required(dict(request.query_params)):
        resp = RedirectResponse(nxt, status_code=302)
        resp.set_cookie(PROBED_COOKIE, "1", max_age=PROBE_MAX_AGE, **_COOKIE_KW)
        resp.delete_cookie(NEXT_COOKIE, path="/")
        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, "")

    # Anti-CSRF: returned state must match what we set before redirecting.
    if not code or not state or not expected_state or state != expected_state:
        return PlainTextResponse("invalid or missing state", status_code=400)
    if not verifier:
        return PlainTextResponse(
            "missing PKCE verifier (session expired?)", status_code=400
        )

    try:
        claims = bw_auth.finish_login(code, verifier)  # verified id_token claims
    except bw_auth.BWAuthError:
        # Auth failed — bounce to the welcome page with a flag the SPA can show.
        resp = RedirectResponse("/?auth_error=1", status_code=302)
        resp.delete_cookie(bw_auth.PKCE_COOKIE, path="/")
        resp.delete_cookie(bw_auth.STATE_COOKIE, path="/")
        return resp

    user = establish_user(db, claims)
    username = user.external_username
    access_level = user.access_level
    db.commit()
    # Mirror the access level to the BW hub (best-effort, outside the txn).
    report_access_level(username, access_level)

    token = sign_session(
        username, settings.app_session_secret, settings.app_session_days * 86400
    )
    resp = RedirectResponse(nxt, status_code=302)
    resp.set_cookie(
        SESSION_COOKIE, token,
        max_age=settings.app_session_days * 86400, **_COOKIE_KW,
    )
    resp.delete_cookie(bw_auth.PKCE_COOKIE, path="/")
    resp.delete_cookie(bw_auth.STATE_COOKIE, path="/")
    resp.delete_cookie(PROBED_COOKIE, path="/")
    resp.delete_cookie(NEXT_COOKIE, path="/")
    return resp


@router.get("/logout")
def logout():
    """Clear the app session, SET the opt-out marker (so the silent probe won't
    instantly re-sign-in), then full BW sign-out (revokes the shared SSO
    session, otherwise the user is silently re-authed)."""
    resp = RedirectResponse(bw_auth.logout_url(), status_code=302)
    resp.delete_cookie(SESSION_COOKIE, path="/")
    resp.set_cookie(OPTOUT_COOKIE, "1", max_age=OPTOUT_MAX_AGE, **_COOKIE_KW)
    resp.delete_cookie(PROBED_COOKIE, path="/")
    return resp
