"""
Piezas compartidas por las vistas de la API.

La API pública y la interfaz consumen **los mismos servicios**. Es la condición
que sostiene el principio IV: si una decisión viviera sólo en una vista de
Inertia, la API no la tendría y el producto se comportaría distinto según por
dónde se lo use. Estas vistas, entonces, no calculan nada: traducen entre HTTP y
la capa de servicios.
"""

from rest_framework import serializers
from rest_framework.views import APIView

from apps.core.errors import ApiError, ValidationFailed
from apps.core.idempotency import IdempotencyConflict, lookup, remember, validate_key


class ErrorSerializer(serializers.Serializer):
    """Forma de toda respuesta de error. Documentada para que el esquema la publique."""

    code = serializers.CharField()
    message = serializers.CharField()
    details = serializers.DictField(required=False)


class BaseApiView(APIView):
    """
    Base de las vistas de la API.

    Traduce los errores de servicio a respuestas del contrato y resuelve la
    idempotencia. Que esté acá y no repetido en cada vista es lo que garantiza
    que un reintento se comporte igual en todos los endpoints que gastan cuota.
    """

    def handle_exception(self, exc):
        if isinstance(exc, IdempotencyConflict):
            exc = ValidationFailed(str(exc))
        if isinstance(exc, ApiError):
            return exc.as_response()
        return super().handle_exception(exc)

    # --- Idempotencia -----------------------------------------------------

    def idempotency_key(self, request) -> str:
        return validate_key(request.headers.get('Idempotency-Key'))

    def replay(self, request):
        """
        Devuelve la respuesta guardada si este pedido ya se atendió.

        El caso real: un pipeline dispara la sincronización, se corta la red
        antes de que llegue la respuesta y reintenta. Sin esto, el sitio gasta
        el doble de cuota por un problema que no tuvo nada que ver con el sitio.
        """
        key = self.idempotency_key(request)
        if not key:
            return None

        stored = lookup(request.user, key, request.data)
        if stored is None:
            return None

        from rest_framework.response import Response

        response = Response(stored.body, status=stored.status_code)
        response['Idempotent-Replay'] = 'true'
        return response

    def store(self, request, response):
        key = self.idempotency_key(request)
        if key and 200 <= response.status_code < 300:
            remember(
                request.user,
                key,
                request.data,
                status_code=response.status_code,
                body=response.data,
            )
        return response
