"""View-as enter/exit — the app-layer context swap (judgment #7).

No JWT minting, no DB-level identity: the cookie names a target
username; resolve_actor honors it ONLY after re-verifying the real
user's super-admin flag against the DB on every request (a forged or
stale cookie impersonates nobody). Enter is strict where the old app
was silent: a non-super-admin gets 403, a bogus target 404 (T-VAS-12
rebuild inversion). Every enter/exit writes an audit row recording
BOTH identities (T-AZ-031 — the old app had none).
"""

from __future__ import annotations

import logging

from sqlalchemy import select
from sqlalchemy.orm import Session

from app.models import AuditLog, User

log = logging.getLogger("with.authz")

VIEW_AS_COOKIE = "with_view_as"
VIEW_AS_TTL = 8 * 60 * 60  # 8h, parity with the old cookie window


class ViewAsError(Exception):
    def __init__(self, status: int, code: str, summary: str, detail: str):
        super().__init__(summary)
        self.status = status
        self.code = code
        self.summary = summary
        self.detail = detail


def enter_view_as(session: Session, actor, target_username: str) -> User:
    """Validate + audit an enter. Returns the target users row; the
    router sets the cookie. `actor` is the resolved (real) Actor."""
    if actor.is_viewing_as:
        raise ViewAsError(
            409,
            "VIEW_AS_NESTED",
            "Already viewing as another user",
            "Exit the current view-as before entering another (no nesting).",
        )
    if not actor.allowed("can_enter_view_as"):
        raise ViewAsError(
            403,
            "VIEW_AS_FORBIDDEN",
            "View-as is super-admin only",
            "Only a super admin can view the app as another user.",
        )
    target_username = (target_username or "").strip()
    if not target_username or target_username == actor.real_username:
        raise ViewAsError(
            400,
            "VIEW_AS_BAD_TARGET",
            "Pick a different user",
            "View-as needs another user's username (not your own, not empty).",
        )
    target = session.scalar(select(User).where(User.idauth_username == target_username))
    if target is None:
        raise ViewAsError(
            404,
            "VIEW_AS_TARGET_NOT_FOUND",
            f"No user named {target_username!r}",
            "The view-as target must be an existing app user.",
        )

    # View-as never widens tenant access (ADR #015): the real user and the
    # target must share at least one tenant, or there is nothing to view.
    # (Lazy import avoids a context<->view_as import cycle.)
    from app.services.authz.context import allowed_tenants

    real_allowed = allowed_tenants(session, actor.person_party_id, actor.is_super_admin)
    target_allowed = allowed_tenants(
        session, target.person_party_id, bool(target.is_super_admin)
    )
    if not ({t.id for t in real_allowed} & {t.id for t in target_allowed}):
        raise ViewAsError(
            409,
            "VIEW_AS_TENANT_MISMATCH",
            "No shared books with that user",
            "You can only view the app as someone who shares at least one of "
            "your tenants — view-as never grants access to books you can't see.",
        )

    session.add(
        AuditLog(
            tenant_id=actor.tenant_id,
            actor_user_id=actor.real_user_id,
            impersonated_by=None,
            action="view_as.enter",
            entity_type="user",
            entity_id=target.id,
            after={"real": actor.real_username, "target": target.idauth_username},
        )
    )
    session.commit()
    log.info("view-as enter: %s -> %s", actor.real_username, target.idauth_username)
    return target


def exit_view_as(session: Session, actor) -> None:
    """Audit an exit (the router clears the cookie). Always allowed —
    exiting must never be blockable."""
    if not actor.is_viewing_as:
        return
    session.add(
        AuditLog(
            tenant_id=actor.tenant_id,
            actor_user_id=actor.real_user_id,
            impersonated_by=actor.user_id,
            action="view_as.exit",
            entity_type="user",
            entity_id=actor.user_id,
            after={"real": actor.real_username, "target": actor.username},
        )
    )
    session.commit()
    log.info("view-as exit: %s (was %s)", actor.real_username, actor.username)
