"""
Autenticación JWT via Keycloak.

Valida tokens Bearer emitidos por Keycloak usando JWKS (RS256).
Las claves públicas se cachean 1 hora para no consultar Keycloak en cada request.
"""
import time
import logging
from typing import Optional

import httpx
from fastapi import Depends, HTTPException, Security, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials, APIKeyHeader
from jose import jwt, JWTError, ExpiredSignatureError

from config.settings import settings

logger = logging.getLogger(__name__)

security = HTTPBearer()

# Cache de JWKS en memoria: evita fetch a Keycloak en cada request
_jwks_cache: dict = {}
_JWKS_CACHE_TTL = 3600  # 1 hora


async def _get_jwks() -> list:
    """Obtiene las claves públicas de Keycloak con caché de 1 hora."""
    now = time.time()
    if _jwks_cache.get("keys") and (_jwks_cache.get("cached_at", 0) + _JWKS_CACHE_TTL > now):
        return _jwks_cache["keys"]

    jwks_url = f"{settings.KEYCLOAK_ISSUER_URI}/protocol/openid-connect/certs"
    logger.info(f"🔑 Actualizando JWKS desde Keycloak: {jwks_url}")

    async with httpx.AsyncClient() as client:
        response = await client.get(jwks_url, timeout=10)
        response.raise_for_status()
        data = response.json()

    _jwks_cache["keys"] = data["keys"]
    _jwks_cache["cached_at"] = now
    logger.info(f"✅ JWKS cacheadas: {len(data['keys'])} clave(s)")
    return _jwks_cache["keys"]


def _find_key(keys: list, kid: Optional[str]) -> Optional[dict]:
    """Busca la clave por kid. Si no hay kid, retorna la primera."""
    if kid:
        return next((k for k in keys if k.get("kid") == kid), None)
    return keys[0] if keys else None


async def verify_token(
    credentials: HTTPAuthorizationCredentials = Depends(security),
) -> dict:
    """
    Dependencia FastAPI: valida el Bearer JWT emitido por Keycloak.
    Retorna el payload (claims) del token si es válido.

    Uso en rutas:
        @router.get("/ruta")
        async def mi_ruta(payload: dict = Depends(verify_token)):
            ...

    Uso en include_router (protege todo el router):
        app.include_router(router, dependencies=[Depends(verify_token)])
    """
    token = credentials.credentials
    unauthorized = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Token inválido o no autorizado",
        headers={"WWW-Authenticate": "Bearer"},
    )

    try:
        header = jwt.get_unverified_header(token)
        kid = header.get("kid")

        keys = await _get_jwks()
        key = _find_key(keys, kid)

        if not key:
            # Refrescar JWKS por si la clave es nueva en Keycloak
            _jwks_cache.clear()
            keys = await _get_jwks()
            key = _find_key(keys, kid)

        if not key:
            logger.warning(f"⚠️ Clave kid='{kid}' no encontrada en JWKS de Keycloak")
            raise unauthorized

        payload = jwt.decode(
            token,
            key,
            algorithms=["RS256"],
            issuer=settings.KEYCLOAK_ISSUER_URI,
            options={"verify_aud": False},  # audience puede variar según el cliente
        )

        logger.debug(f"✅ Token válido | user={payload.get('preferred_username')} | client={payload.get('azp')}")
        return payload

    except ExpiredSignatureError:
        logger.warning("⚠️ Token JWT expirado")
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Token expirado",
            headers={"WWW-Authenticate": "Bearer"},
        )
    except JWTError as e:
        logger.warning(f"⚠️ Error validando JWT: {e}")
        raise unauthorized
    except httpx.HTTPError as e:
        logger.error(f"❌ Error de conectividad con Keycloak: {e}")
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail="No se pudo verificar el token (error conectando con Keycloak)",
        )


_api_key_header = APIKeyHeader(name="X-API-Key")


async def verify_api_key(api_key: str = Security(_api_key_header)) -> str:
    """
    Dependencia FastAPI: valida la API Key enviada en el header X-API-Key.
    Configurada mediante la variable de entorno SYNC_API_KEY.
    """
    if not settings.SYNC_API_KEY or api_key != settings.SYNC_API_KEY:
        logger.warning("⚠️ Intento de acceso con API Key inválida")
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="API Key inválida o no configurada",
        )
    return api_key


def get_current_user(payload: dict = Depends(verify_token)) -> str:
    """
    Dependencia de conveniencia: retorna el preferred_username del token.

    Uso:
        @router.get("/ruta")
        async def mi_ruta(username: str = Depends(get_current_user)):
            ...
    """
    return payload.get("preferred_username") or payload.get("sub", "unknown")
