"""FastAPI dependencies for auth."""

from __future__ import annotations

from typing import Annotated

from fastapi import Depends, HTTPException, Request, status
from sqlalchemy.orm import Session as DbSession

from cyclone.auth import sessions, users
from cyclone.auth.permissions import Role, allowed_roles
from cyclone.db import SessionLocal


def _db():
    db = SessionLocal()()
    try:
        yield db
    finally:
        db.close()


DbSessionDep = Annotated[DbSession, Depends(_db)]


AUTH_DISABLED = False


async def get_current_user(
    request: Request,
    db: DbSessionDep,
) -> dict:
    """Return the public User shape. Raises 401 if session is missing/expired.

    When AUTH_DISABLED is True (dev escape hatch), returns a synthetic admin
    user without checking credentials.
    """
    if AUTH_DISABLED:
        return {
            "id": 0,
            "username": "dev",
            "role": Role.ADMIN.value,
            "createdAt": None,
            "disabledAt": None,
        }

    sid = request.cookies.get("cyclone_session")
    if not sid:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="session_expired",
        )
    sess = sessions.get_valid(db, sid)
    if sess is None:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="session_expired",
        )
    user = users.get(db, sess.user_id)
    if user is None or user.disabled_at is not None:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="account_disabled",
        )
    # Sliding expiry: refresh both DB and cookie.
    sessions.touch(db, sid)
    request.state.user = user
    request.state.session_id = sid
    return users.to_public(user)


def require_role(*allowed: Role):
    """Dependency factory: gate the endpoint to specific roles.

    Falls back to PERMISSIONS matrix lookup if no explicit roles given.
    """
    async def _dep(
        request: Request,
        user: dict = Depends(get_current_user),
    ) -> dict:
        user_role = user.get("role")
        if allowed:
            if user_role not in {r.value for r in allowed}:
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail="forbidden",
                )
            return user
        # Otherwise consult the matrix.
        method = request.method
        path = request.url.path
        roles = allowed_roles(method, path)
        if roles is None:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="forbidden",
            )
        if user_role not in {r.value for r in roles}:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="forbidden",
            )
        return user
    return _dep


async def matrix_gate(
    request: Request,
    user: dict = Depends(get_current_user),
) -> dict:
    """App-wide gate: requires auth, then enforces the PERMISSIONS matrix.

    Behavior:
    * AUTH_DISABLED short-circuits (synthetic admin, no role check).
    * No session cookie → 401 from get_current_user.
    * Endpoint not in the matrix → 403 (fail-closed).
    * User role not allowed for (method, path) → 403.
    * Empty allowed-roles set (e.g. /api/healthz) → public, no role check.

    Used as ``dependencies=[Depends(matrix_gate)]`` on every authenticated
    route. Centralizing the gate here means the matrix is the source of
    truth — no need to wire per-route ``require_role(...)`` calls.
    """
    if AUTH_DISABLED:
        return user
    method = request.method
    path = request.url.path
    roles = allowed_roles(method, path)
    if roles is None:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="forbidden",
        )
    user_role = user.get("role")
    if user_role not in {r.value for r in roles}:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="forbidden",
        )
    return user
