"""/view-as/start + /view-as/stop — impersonation via the bw_view_as
drop-in (mirrors the SPA reference in
/srv/system/id-auth/app-auth/example-react/backend/app.py).

The drop-in owns the mechanism and guards (server-side re-auth on
start, no self/nesting, rank-based no-escalation, target existence);
app policy lives in app/services/view_as.py. State rides a SIGNED
cookie bound to the real user; every read path re-authorizes it via
bw_view_as.verify() (services/view_as.resolve), so a stale grant
fail-closes on the very next request.

Read-only default: M0 never starts mode='act'; future write handlers
gate on bw_view_as.can_write(session) via services/view_as.resolve.
Both routes are behind the default-deny middleware (never allowlisted).
"""
from __future__ import annotations

import logging

from fastapi import APIRouter, Request
from pydantic import BaseModel
from starlette.responses import JSONResponse, RedirectResponse, Response

from ..services import view_as as view_as_service
from ..services.auth import bw_auth, bw_view_as
from ..services.auth import session as auth_session

log = logging.getLogger("volleyboard.view_as")

router = APIRouter()

# ViewAsError.code -> HTTP status (codes are the drop-in's stable set).
_STATUS_BY_CODE = {
    "NOT_SIGNED_IN": 401,
    "BAD_TARGET": 400,
    "FORBIDDEN": 403,
    "TARGET_NOT_FOUND": 404,
    "ESCALATION": 403,
    "NESTED": 409,
}


def _report_safe(fn, *args, **kwargs) -> None:
    try:
        fn(*args, **kwargs)
    except bw_auth.BWAuthError as e:
        log.debug("hub report skipped: %s", e)


class StartViewAs(BaseModel):
    target: str


@router.post("/view-as/start")
def start(body: StartViewAs, request: Request) -> Response:
    """Begin impersonation. bw_view_as.start() re-checks authorization
    SERVER-SIDE (can_view_as + rank_of + target_valid) — the client is
    never trusted. Any previously-active view-as is first re-verified
    (fail-closed); a live one must be stopped before starting another
    (the drop-in's NESTED guard)."""
    session, from_cookie = view_as_service.session_map(request, include_dev_header=False)
    # Re-verify existing state so a stale/revoked view-as can't block or
    # leak into the new start.
    bw_view_as.verify(
        session,
        can_view_as=view_as_service.can_view_as,
        rank_of=view_as_service.rank_of,
        target_valid=view_as_service.target_valid,
    )
    real = bw_view_as.real_username(session)
    try:
        target = bw_view_as.start(
            session,
            body.target,
            can_view_as=view_as_service.can_view_as,
            rank_of=view_as_service.rank_of,
            target_valid=view_as_service.target_valid,
        )
    except bw_view_as.ViewAsError as e:
        resp = JSONResponse(
            status_code=_STATUS_BY_CODE.get(e.code, 403),
            content={
                "error_code": e.code,
                "summary": str(e),
                "detail": "View-as was refused; your real session is untouched.",
            },
        )
        if from_cookie and not bw_view_as.is_impersonating(session):
            # The re-verify above dropped a stale cookie state.
            resp.delete_cookie(auth_session.VIEW_AS_COOKIE, path="/")
        return resp

    mode = bw_view_as.mode(session)
    log.info("view-as start: %s -> %s (%s)", real, target, mode)
    _report_safe(bw_auth.report_impersonation, real, target, mode=mode)

    resp = JSONResponse(
        {
            "ok": True,
            "viewing_as": target,
            "viewing_as_label": view_as_service.display_label(target),
            "mode": mode,
        }
    )
    resp.set_cookie(
        auth_session.VIEW_AS_COOKIE,
        auth_session.mint_view_as(real, target, mode),
        max_age=auth_session.VIEW_AS_TTL,
        httponly=True,
        secure=True,
        samesite="lax",
        path="/",
    )
    return resp


def _do_stop(request: Request) -> tuple[str | None, str | None, bool]:
    """Clear the view-as state. Never blockable: works whatever state the
    impersonation is in (VIEW-AS.md: exiting must always succeed)."""
    session, _ = view_as_service.session_map(request, include_dev_header=False)
    real = bw_view_as.real_username(session)
    target = bw_view_as.effective_username(session)  # capture BEFORE stopping
    was = bw_view_as.is_impersonating(session)
    mode = bw_view_as.mode(session)
    bw_view_as.stop(session)
    if was:
        log.info("view-as stop: %s (was %s)", real, target)
        _report_safe(bw_auth.report_impersonation, real, target, mode=mode, active=False)
    return real, target, was


@router.post("/view-as/stop")
def stop_post(request: Request) -> Response:
    _do_stop(request)
    resp = JSONResponse({"ok": True})
    resp.delete_cookie(auth_session.VIEW_AS_COOKIE, path="/")
    return resp


@router.get("/view-as/stop", include_in_schema=False)
def stop_get(request: Request) -> Response:
    """GET so the un-hideable banner's "Return to yourself" link (a plain
    navigation) works — mirrors example-react."""
    _do_stop(request)
    resp = RedirectResponse(url="/", status_code=302)
    resp.delete_cookie(auth_session.VIEW_AS_COOKIE, path="/")
    return resp
