"""OAuth/session-роуты gnexus-auth — скроировано с gnexus-creds.

GET  /auth/login?return_to=/… — 302 на gnexus-auth (PKCE, state в oauth_states)
GET  /auth/callback?code&state — обмен кода, сессия в cookie, redirect на return_to
POST /auth/logout — удалить сессию
GET  /api/v1/me — профиль из сессии (для экрана входа и identity в дровере)
POST /webhooks/gnexus-auth(+/) — HMAC-верифицированные события gnexus-auth
"""

from datetime import datetime, timedelta, timezone

from fastapi import APIRouter, Request, Response
from fastapi.responses import JSONResponse, RedirectResponse
from gnexus_gauth.exceptions import (
    StateValidationException,
    TokenExchangeException,
    TransportException,
)
from gnexus_gauth.oauth import AuthorizationUrlBuilder, PkceGenerator

from app.auth import (
    SCOPES,
    SESSION_COOKIE,
    allowlisted,
    auth_enabled,
    create_session,
    delete_session,
    exchange_and_fetch,
    get_ctx,
    safe_return_to,
)
from app.config import get_settings
from app.db import get_db
from app.security import auth_mode_error

router = APIRouter()
webhook_router = APIRouter()


def _now() -> datetime:
    return datetime.now(timezone.utc)


def _now_iso() -> str:
    return _now().isoformat()


@router.get("/auth/login")
async def login(request: Request, return_to: str = "/") -> RedirectResponse:
    """Начало OAuth: state + PKCE → oauth_states, 302 на gnexus-auth."""
    settings = get_settings()
    return_to = safe_return_to(return_to)
    ctx = get_ctx()
    state = PkceGenerator.generate_state()
    verifier = PkceGenerator.generate_verifier()
    challenge = PkceGenerator.generate_challenge(verifier)
    db = get_db()
    await db.execute(
        "INSERT INTO oauth_states (state, pkce_verifier, return_to, expires_at) VALUES (?, ?, ?, ?)",
        (state, verifier, return_to, (_now() + timedelta(seconds=settings.session_ttl_seconds)).isoformat()),
    )
    await db.commit()
    url = AuthorizationUrlBuilder(ctx["config"]).build(
        state=state,
        pkce_challenge=challenge,
        return_to=return_to,
        scopes=SCOPES,
    )
    return RedirectResponse(url, status_code=302)


@router.get("/auth/callback")
async def callback(request: Request, code: str = "", state: str = ""):
    """Обмен кода на токен, сессия в cookie, редирект на return_to."""
    if not state or not code:
        raise auth_mode_error()
    settings = get_settings()
    db = get_db()
    cursor = await db.execute(
        "SELECT pkce_verifier, return_to, expires_at FROM oauth_states WHERE state = ?",
        (state,),
    )
    saved = await cursor.fetchone()
    if saved is None or saved["expires_at"] < _now_iso():
        return JSONResponse(
            {"detail": "Invalid or expired OAuth state."}, status_code=400
        )
    try:
        import asyncio

        _, user = await asyncio.to_thread(
            exchange_and_fetch, code, saved["pkce_verifier"]
        )
    except (TokenExchangeException, StateValidationException, TransportException):
        return JSONResponse(
            {"detail": "Authorization server is unreachable or rejected the login."},
            status_code=502,
        )
    if not allowlisted(user.email, user.user_id):
        return JSONResponse({"detail": "User is not allowed here."}, status_code=403)
    if user.status in {"disabled", "blocked", "deleted"}:
        return JSONResponse({"detail": "User is disabled."}, status_code=403)
    session_id = await create_session(user)
    await db.execute("DELETE FROM oauth_states WHERE state = ?", (state,))
    await db.commit()
    response = RedirectResponse(safe_return_to(saved["return_to"]), status_code=302)
    response.set_cookie(
        SESSION_COOKIE,
        session_id,
        httponly=True,
        samesite="lax",
        max_age=settings.session_ttl_seconds,
        secure=bool(settings.auth_redirect_uri.startswith("https://")),
    )
    return response


@router.post("/auth/logout")
async def logout(request: Request) -> Response:
    """Удалить свою сессию (cookie гасится тоже)."""
    settings = get_settings()
    await delete_session(request.cookies.get(SESSION_COOKIE))
    response = JSONResponse({"status": "ok"})
    response.delete_cookie(
        SESSION_COOKIE,
        httponly=True,
        samesite="lax",
        secure=bool(settings.auth_redirect_uri.startswith("https://")),
    )
    return response


# --- /me для фронтенда (в api/v1 — тот же префикс SPA) -------------------------

me_router = APIRouter(prefix="/api/v1")


@me_router.get("/me", dependencies=[])
async def me(request: Request) -> dict:
    """Профиль текущего пользователя + флаг режима авторизации.

    Auth выключен → {auth_enabled: false} — SPA молча открывает панель.
    Включён и сессии нет → 401 (SPA показывает экран входа).
    Включён и сессия есть → {auth_enabled: true, user: …}.
    """
    if not auth_enabled():
        return {"auth_enabled": False, "user": None}
    from app.auth import get_session

    session = await get_session(request.cookies.get(SESSION_COOKIE))
    if session is None:
        raise auth_mode_error()
    return {
        "auth_enabled": True,
        "user": {
            "user_id": session["user_id"],
            "email": session["email"],
            "display_name": session["display_name"],
            "avatar_url": session["avatar_url"],
        },
    }


# --- Webhook gnexus-auth (HMAC) ------------------------------------------------

@webhook_router.post("/webhooks/gnexus-auth")
@webhook_router.post("/webhooks/gnexus-auth/")
async def gnexus_auth_webhook(request: Request) -> dict:
    from app.auth import apply_webhook_update, get_ctx

    settings = get_settings()
    raw = (await request.body()).decode("utf-8", errors="replace")
    headers = dict(request.headers)
    ctx = get_ctx()
    try:
        ctx["webhook"].verify(raw, headers, settings.auth_webhook_secret)
        event = ctx["parser"].parse(raw)
    except Exception:  # noqa: BLE001 — чужое/несанкционированное всегда 401
        return JSONResponse({"detail": "invalid webhook signature"}, status_code=401)
    subject = (
        event.target_identifiers.get("sub")
        or event.target_identifiers.get("user_id")
        or event.metadata.get("sub")
    )
    if subject:
        await apply_webhook_update(str(subject), event.event_type, event.metadata)
    return {"status": "ok"}


# /api/v1/me проверяет сессию без require_admin — он сам является gate-палкой.
# Остальное API защищается в security.py (routers подключаются ниже).