"""Authenticated session state for the Studio.

A ``Session`` is the runtime context of a logged-in user. It carries the
:class:`User`, exposes capability checks, and writes audit events for
every gated decision (granted or denied). Window code reaches for the
current Session via the :class:`SessionHolder` singleton; tests can swap
it out trivially.
"""

from __future__ import annotations

from pathlib import Path
from typing import Optional

from arautopilot.core.audit import AuditEvent, AuditLog, AuditOutcome
from arautopilot.core.rbac import Capability, Role, has, requires_dual_auth
from arautopilot.core.user import User
from arautopilot.core.user_store import UserStore


class Session:
    """The current user + their audit log + capability gate."""

    def __init__(
        self,
        user: User,
        store: UserStore,
        audit: AuditLog,
    ) -> None:
        self.user = user
        self.store = store
        self.audit = audit

    @property
    def role(self) -> Role:
        return self.user.role

    def can(self, capability: Capability) -> bool:
        return has(self.user.role, capability)

    def check(self, capability: Capability, *, target: str | None = None,
              extra: dict[str, object] | None = None) -> bool:
        """Capability check with audit side effect.

        Returns True on grant, False on denial. Records an audit event
        either way so the trail is complete.
        """
        granted = self.can(capability)
        self.audit.append(
            AuditEvent(
                user_id=self.user.user_id,
                role=self.user.role.value,
                action=f"check:{capability.value}",
                target=target,
                outcome=AuditOutcome.SUCCESS if granted else AuditOutcome.DENIED,
                reason="" if granted
                else f"role {self.user.role.value!r} lacks {capability.value!r}",
                extra=extra or {},
            )
        )
        return granted

    def needs_dual_auth(self, capability: Capability) -> bool:
        return requires_dual_auth(self.user.role, capability)

    def verify_super_admin_pin(self, pin: str) -> Optional[User]:
        """Look up the (first) Super Admin in the store and verify the PIN.

        Returns the SA :class:`User` on success, ``None`` on failure.
        """
        for sa in self.store.by_role(Role.SUPER_ADMIN):
            if sa.verify_pin(pin):
                return sa
        return None

    def log_dual_auth_grant(
        self,
        capability: Capability,
        sa_user: User,
        *,
        target: str | None = None,
        extra: dict[str, object] | None = None,
    ) -> None:
        """Record an audit event for a successful dual-auth approval."""
        self.audit.append(
            AuditEvent(
                user_id=self.user.user_id,
                role=self.user.role.value,
                action=f"dual_auth_approved:{capability.value}",
                target=target,
                outcome=AuditOutcome.SUCCESS,
                secondary_user_id=sa_user.user_id,
                reason=f"approved by Super Admin {sa_user.display_name!r}",
                extra=extra or {},
            )
        )

    def log_action(
        self,
        action: str,
        *,
        outcome: AuditOutcome = AuditOutcome.SUCCESS,
        target: str | None = None,
        reason: str = "",
        extra: dict[str, object] | None = None,
    ) -> None:
        """Free-form audit event for downstream business actions."""
        self.audit.append(
            AuditEvent(
                user_id=self.user.user_id,
                role=self.user.role.value,
                action=action,
                target=target,
                outcome=outcome,
                reason=reason,
                extra=extra or {},
            )
        )


class SessionHolder:
    """Process-global current session (set after login)."""

    _current: Session | None = None

    @classmethod
    def set(cls, session: Session | None) -> None:
        cls._current = session

    @classmethod
    def current(cls) -> Session | None:
        return cls._current

    @classmethod
    def require(cls) -> Session:
        if cls._current is None:
            raise RuntimeError("no active Studio session -- log in first")
        return cls._current


def studio_data_dir() -> Path:
    """Per-user directory under ``~/.ar-autopilot/studio/`` for the local
    user store and audit log."""
    base = Path.home() / ".ar-autopilot" / "studio"
    base.mkdir(parents=True, exist_ok=True)
    return base
