"""
Autenticacion por clave de API.

La version anterior buscaba la clave en claro:

    ApiKeys.objects.get(key=apikey, active=True)

lo que obliga a guardarla utilizable en la base de datos. Ahora se localiza el
registro por su prefijo, que es publico, y se compara el hash del secreto en
tiempo constante.
"""

from __future__ import annotations

import logging

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed

from core.utils.http import get_client_ip
from keys.models.api_keys import ApiKeys

logger = logging.getLogger("core.auth")


class ApiKeyAuthentication(BaseAuthentication):
    """Autenticacion por clave de API en la cabecera X-API-Key."""

    def authenticate(self, request):
        apikey = request.META.get("HTTP_X_API_KEY")
        if not apikey:
            # Sin cabecera: la peticion queda anonima y la clase de permiso
            # (IsAuthenticated) es la que la rechaza con 401.
            return None

        registro = ApiKeys.authenticate(apikey)
        if registro is None:
            # Un solo mensaje para todos los casos de fallo: distinguir entre
            # "no existe", "esta revocada" y "el formato es incorrecto" es
            # informacion util para quien esta probando claves.
            logger.warning(
                "Intento de autenticacion con clave de API invalida",
                extra={"source_ip": get_client_ip(request)},
            )
            raise AuthenticationFailed("Invalid API-Key")

        usuario = registro.account.user
        if not usuario.is_active:
            raise AuthenticationFailed("Invalid API-Key")

        registro.touch()

        # Se deja el registro en la peticion para que la auditoria sepa que
        # credencial pidio el envio.
        request.api_key_record = registro

        return (usuario, None)

    def authenticate_header(self, request):
        return "X-API-Key"
