"""Dépendances FastAPI : base, utilisateur courant, permissions (refus par défaut), CSRF."""
from __future__ import annotations

from collections.abc import Callable
from typing import Any

from fastapi import Depends, Request
from sqlalchemy.orm import Session

from ..core import security
from ..core.config import get_config
from ..core.db import get_db
from ..services import auth as auth_service

UNSAFE_METHODS = {"POST", "PUT", "PATCH", "DELETE"}


class ApiError(Exception):
    def __init__(self, status: int, message: str, code: str = "error", extra: dict[str, Any] | None = None):
        super().__init__(message)
        self.status, self.message, self.code, self.extra = status, message, code, extra or {}


def client_ip(request: Request) -> str | None:
    return request.client.host if request.client else None


def get_principal_partial(request: Request, db: Session = Depends(get_db)) -> auth_service.Principal:
    """Session valide, éventuellement incomplète (MFA à saisir, MFA à configurer, mot de passe à changer)."""
    cfg = get_config()
    loaded = auth_service.load_session(db, request.cookies.get(cfg.cookie_name))
    if loaded is None:
        raise ApiError(401, "Session absente ou expirée. Veuillez vous reconnecter.", "unauthenticated")
    sess, user = loaded
    if request.method in UNSAFE_METHODS:
        sent = request.headers.get("x-csrf-token", "")
        if not sent or not security.constant_eq(sent, sess.csrf_token):
            raise ApiError(403, "Jeton de sécurité (CSRF) manquant ou invalide. Rechargez la page.", "csrf")
    request.state.user_id = user.id
    request.state.username = user.username
    return auth_service.Principal(
        user=user, session=sess, permissions=auth_service.permissions_for(db, user),
        mfa_required=auth_service.mfa_required_for(db, user),
    )


def get_principal(principal: auth_service.Principal = Depends(get_principal_partial)) -> auth_service.Principal:
    if not principal.session.mfa_verified:
        raise ApiError(401, "Code de vérification (MFA) requis.", "mfa_required")
    if principal.needs_mfa_setup:
        raise ApiError(403, "Vous devez activer la double authentification avant de continuer.", "mfa_setup_required")
    if principal.needs_password_change:
        raise ApiError(403, "Vous devez changer votre mot de passe avant de continuer.", "password_change_required")
    return principal


def require(*perms: str) -> Callable[..., auth_service.Principal]:
    """Chaque route déclare explicitement ses permissions : rien n'est accordé par défaut."""
    if not perms:
        raise RuntimeError("require() doit recevoir au moins une permission")

    def dependency(principal: auth_service.Principal = Depends(get_principal)) -> auth_service.Principal:
        missing = [p for p in perms if not principal.can(p)]
        if missing:
            raise ApiError(403, "Vous n'avez pas les droits nécessaires pour cette action.", "forbidden")
        return principal

    return dependency
