"""Meta routes: liveness, version, and the SPA's session-state endpoint.

/api/health and /api/me are the only two exact paths AuthMiddleware lets through
without a session — /api/me because PUBLIC mode requires an anonymous visitor to
be able to ask "am I signed in?" and get an answer rather than a 401.
"""

from __future__ import annotations

import contextlib

from fastapi import APIRouter, BackgroundTasks, Depends, Request
from pydantic import BaseModel
from sqlalchemy import select
from sqlalchemy.orm import Session

from .. import bw_auth
from ..app_session import OPTOUT_COOKIE, PROBED_COOKIE
from ..auth import get_authenticated_user
from ..config import get_settings
from ..db import get_db
from ..models.identity import User
from ..services.roles import is_admitted, label_for
from ..version import APP_VERSION

router = APIRouter(prefix="/api", tags=["meta"])


class HealthOut(BaseModel):
    ok: bool


class VersionOut(BaseModel):
    version: str


class MeOut(BaseModel):
    """Everything the SPA needs to pick its view, in one call.

    `authenticated` and `admitted` are deliberately separate: a signed-in but
    not-invited visitor is authenticated and NOT admitted, and the SPA shows
    them the "not invited" screen rather than either the welcome page or the
    app.
    """

    authenticated: bool
    admitted: bool = False
    username: str | None = None
    display_name: str = ""
    email: str = ""
    role: str = "none"
    role_label: str = ""
    # PUBLIC mode: should the client fire the one-time silent SSO probe
    # (a full-page navigation to /auth/probe)? True only when anonymous, not
    # opted out, and not already probed — so it can never loop.
    should_probe: bool = False
    # Whether this deployment can complete a sign-in at all. False means the
    # SPA hides the Sign in button instead of offering one that 503s.
    auth_available: bool = False
    login_url: str = "/login"
    logout_url: str = "/logout"


@router.get("/health", response_model=HealthOut)
def health() -> HealthOut:
    """Public. Liveness only — no data, no config, nothing about the session."""
    return HealthOut(ok=True)


@router.get("/version", response_model=VersionOut)
def version() -> VersionOut:
    """Gated: the running version is deploy-confirmation for the operator, not
    a fact anonymous visitors need."""
    return VersionOut(version=APP_VERSION)


def _client_ip(request: Request) -> str:
    """The real client IP behind Caddy — first X-Forwarded-For hop, falling back
    to the socket peer."""
    return (
        request.headers.get("X-Forwarded-For", "").split(",")[0].strip()
        or (request.client.host if request.client else "")
    )


def _report_presence(username: str, client_ip: str) -> None:
    """Identified-presence heartbeat to the BW hub, so who's-online shows this
    user's NAME rather than an anonymous IP (id-auth is not in a Pattern B app's
    request path, so it cannot know otherwise).

    Runs as a BackgroundTask AFTER the response, so the ~0.5s round-trip adds no
    latency to /api/me. Best-effort: a presence hiccup must never surface.
    """
    # Cosmetics: a presence failure must never be user-visible, so every
    # exception is swallowed (including a missing BW config in tests/dev).
    with contextlib.suppress(Exception):
        bw_auth.report_presence(username, ip=client_ip or None)


@router.get("/me", response_model=MeOut)
def me(
    request: Request,
    background_tasks: BackgroundTasks,
    db: Session = Depends(get_db),
) -> MeOut:
    """Public. Session state for the SPA: the first call it makes on load.

    Returns authenticated=false for an anonymous visitor rather than 401 — that
    is the whole point of allowlisting it.
    """
    settings = get_settings()
    auth = get_authenticated_user(request)

    if auth is None:
        should_probe = (
            settings.domain_mode == "public"
            and settings.auth_configured
            and not request.cookies.get(OPTOUT_COOKIE)
            and not request.cookies.get(PROBED_COOKIE)
        )
        return MeOut(
            authenticated=False,
            should_probe=should_probe,
            auth_available=settings.auth_configured,
        )

    row = db.scalar(
        select(User).where(User.external_username == auth.username)
    )
    if row is None:
        # Valid signature, account gone (deleted since the cookie was minted).
        # Report as anonymous; the SPA will show the welcome page.
        return MeOut(authenticated=False, auth_available=settings.auth_configured)

    background_tasks.add_task(
        _report_presence, row.external_username, _client_ip(request)
    )
    return MeOut(
        authenticated=True,
        admitted=is_admitted(row.role),
        username=row.external_username,
        display_name=row.display_name,
        email=row.email,
        role=row.role,
        role_label=label_for(row.role),
        auth_available=settings.auth_configured,
    )
