"""View As — app policy + the cookie<->bw_view_as session bridge.

The MECHANISM (guards, fail-closed verify, me_fields) is the canonical
drop-in app/services/auth/bw_view_as.py; this module supplies the two
policy hooks VIEW-AS.md requires (+ rank_of / target_valid) and adapts
the drop-in's dict-session contract to our stateless signed cookies.

M0 policy:
  can_view_as(real, target) — real is "rian" (app admin) OR a lead
      (boundary player of any team_node). Target-existence-INDEPENDENT,
      so target_valid= is passed to BOTH start() and verify() (VIEW-AS.md
      Example A's ghost-impersonation trap).
  viewable_targets(real)    — every other player (the picker list; start()
      re-authorizes server-side regardless of what the picker offered).
  rank_of                   — admin 3, lead 2, player 1: no viewing a peer
      or superior (no escalation), enforced by start() AND verify().

Session bridge: bw_view_as mutates a dict {'user', 'bw_acting_as',
'bw_acting_mode'}. We rebuild that dict per request — 'user' from the
middleware-resolved identity (signed session cookie ONLY), acting state
from the SIGNED vb_view_as cookie (bound to the real username; a forged
or replayed cookie reads as no state). After start/stop/verify the
router persists the dict back to the cookie.
"""
from __future__ import annotations

from sqlalchemy import select
from starlette.requests import Request

from ..db import SessionLocal
from ..models import Player
from . import roles
from .auth import bw_view_as
from .auth import config as auth_config
from .auth import session as auth_session

# ------------------------------------------------------------------ the hooks


def can_view_as(real: str, target: str) -> bool:
    """YOUR policy (VIEW-AS.md): the app admin or any lead may impersonate.
    Always re-checked server-side by bw_view_as.start() AND verify() —
    the client (and the picker) are never trusted."""
    return roles.is_admin(real) or roles.role_of(real) == roles.ROLE_LEAD


def viewable_targets(real: str) -> list[dict]:
    """Every OTHER player, as [{'username','label'}] for the picker."""
    db = SessionLocal()
    try:
        rows = db.execute(
            select(Player.username, Player.display_name)
            .where(Player.username != real)
            .order_by(Player.username)
        ).all()
        return [
            {"username": username, "label": display_name or username}
            for username, display_name in rows
        ]
    finally:
        db.close()


def target_valid(target: str) -> bool:
    """bw_view_as target_valid hook: the target must still be a real,
    current Player row. Required because can_view_as above is
    target-existence-independent — without this a target deleted
    mid-session would ghost (VIEW-AS.md Example A)."""
    db = SessionLocal()
    try:
        return (
            db.scalar(select(Player.id).where(Player.username == target)) is not None
        )
    finally:
        db.close()


def rank_of(username: str) -> int:
    """No-escalation ranks: start()/verify() refuse a target of equal or
    higher rank than the real user."""
    if roles.is_admin(username):
        return 3
    if roles.role_of(username) == roles.ROLE_LEAD:
        return 2
    return 1


def display_label(username: str | None) -> str | None:
    """username -> display_name (or the username itself) for banners."""
    if not username:
        return username
    db = SessionLocal()
    try:
        name = db.scalar(
            select(Player.display_name).where(Player.username == username)
        )
        return name or username
    finally:
        db.close()


# ----------------------------------------------------- cookie <-> session dict


def session_map(request: Request, *, include_dev_header: bool = True) -> tuple[dict, bool]:
    """Rebuild the bw_view_as session dict for this request.

    Returns (session, from_cookie): 'user' comes ONLY from the
    middleware-resolved identity; acting state from the signed view-as
    cookie when it verifies FOR THIS real user. In a non-production env
    with the AUTH_DEV_USER stub active, an X-View-As header may supply
    the acting state instead (the overnight perspective-check tool) —
    it still passes through the same verify() policy below. The header
    overlay is a READ-path override only; the /view-as/* routes manage
    cookie state and pass include_dev_header=False.
    """
    real = getattr(request.state, "username", None) or ""
    session: dict = {"user": real}
    token = request.cookies.get(auth_session.VIEW_AS_COOKIE, "")
    state = auth_session.read_view_as(token, real) if (token and real) else None
    if state:
        session[bw_view_as.ACTING_KEY] = state["target"]
        session[bw_view_as.MODE_KEY] = state["mode"]
        return session, True

    if include_dev_header and auth_config.dev_stub_active():
        header_target = (request.headers.get("X-View-As") or "").strip()
        if header_target:
            session[bw_view_as.ACTING_KEY] = header_target
            session[bw_view_as.MODE_KEY] = "readonly"
    return session, False


def resolve(request: Request) -> tuple[dict, bool]:
    """Session dict with any active view-as RE-AUTHORIZED (the per-request
    verify() VIEW-AS.md step 3 requires). Fail-closed: a view-as that is
    no longer allowed (role revoked, target deleted, rank change) is
    auto-stopped and the request proceeds as the real user.

    Returns (session, drop_cookie): drop_cookie is True when cookie-borne
    state was auto-stopped — the caller must delete the cookie on its
    response so the stale state doesn't come back next request.
    """
    session, from_cookie = session_map(request)
    bw_view_as.verify(
        session,
        can_view_as=can_view_as,
        rank_of=rank_of,
        target_valid=target_valid,
    )
    drop_cookie = from_cookie and not bw_view_as.is_impersonating(session)
    return session, drop_cookie
