import logging
from dataclasses import dataclass
from typing import Annotated, Protocol

import jwt
from fastapi import Depends
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from jwt import PyJWK, PyJWKClient

from app.core.config import get_settings
from app.core.errors import AppError

logger = logging.getLogger(__name__)

ALGORITHMS = ["EdDSA"]
_UNAUTHORIZED_HEADERS = {"WWW-Authenticate": "Bearer"}


class JwksClient(Protocol):
    def get_signing_key_from_jwt(self, token: str) -> PyJWK: ...


_jwks_client: JwksClient | None = None


def set_jwks_client(client: JwksClient) -> None:
    global _jwks_client
    _jwks_client = client


def get_jwks_client() -> JwksClient:
    global _jwks_client
    if _jwks_client is None:
        _jwks_client = PyJWKClient(get_settings().jwks_url, cache_jwk_set=True, lifespan=300)
    return _jwks_client


@dataclass(frozen=True)
class CurrentUser:
    user_id: str
    email: str
    org_id: str | None


_bearer = HTTPBearer(auto_error=False)


def get_current_user(
    creds: Annotated[HTTPAuthorizationCredentials | None, Depends(_bearer)],
) -> CurrentUser:
    if creds is None:
        raise AppError(401, "Unauthorized", "Jeton manquant", _UNAUTHORIZED_HEADERS)
    settings = get_settings()
    try:
        key = get_jwks_client().get_signing_key_from_jwt(creds.credentials)
        claims = jwt.decode(
            creds.credentials,
            key.key,
            algorithms=ALGORITHMS,
            issuer=settings.auth_base_url,
            audience=settings.auth_base_url,
            options={"require": ["exp", "sub"]},
        )
    # JWKS injoignable (`PyJWKClientConnectionError`) ou non-JSON (`ValueError`, levée par
    # `json.load` dans `fetch_data`) : c'est une panne du fournisseur de clés, pas un jeton
    # invalide. À garder AVANT `PyJWTError`, dont `PyJWKClientConnectionError` dérive.
    except (jwt.PyJWKClientConnectionError, ValueError) as exc:
        logger.exception("JWKS indisponible (%s)", settings.jwks_url)
        raise AppError(503, "Service Unavailable", "Fournisseur de clés indisponible") from exc
    except jwt.PyJWTError as exc:
        raise AppError(401, "Unauthorized", "Jeton invalide", _UNAUTHORIZED_HEADERS) from exc
    return CurrentUser(
        user_id=str(claims["sub"]),
        email=str(claims.get("email", "")),
        org_id=claims.get("org_id") or None,
    )


CurrentUserDep = Annotated[CurrentUser, Depends(get_current_user)]


@dataclass(frozen=True)
class CurrentOrgUser(CurrentUser):
    org_id: str


def get_current_org_user(user: CurrentUserDep) -> CurrentOrgUser:
    # 404 et non 403 : ne pas révéler si une organisation existe (spec architecture §9).
    if user.org_id is None:
        raise AppError(404, "Not Found", "Aucune organisation active")
    return CurrentOrgUser(user_id=user.user_id, email=user.email, org_id=user.org_id)


CurrentOrgDep = Annotated[CurrentOrgUser, Depends(get_current_org_user)]
