"""Storm-safe base view — the #250 lesson encoded once.

A callback/event endpoint must NEVER answer a caller with a 5xx that the caller will treat as a
failure and RETRY: that is exactly the positive-feedback loop that turned the pool-service
single-threaded stall into a self-sustaining register/false-eviction storm. A StormSafeAPIView
catches UNEXPECTED handler errors and returns a soft body (default HTTP 200 {ok:false,error:...})
instead of a 500 — the precise behaviour the hand-rolled pdf_feed_service had ("never 500 the
caller into a retry storm"), now inherited.

DRF's own APIExceptions still pass through with their intended status (ValidationError -> 400,
AuthenticationFailed -> 401, NotFound -> 404, ...) — only genuinely-unhandled exceptions are
softened. A service that WANTS real 5xx (e.g. an internal RPC where the caller SHOULD retry with
backoff) just uses a plain rest_framework APIView instead — the base is opt-in.
"""
import logging

from rest_framework.exceptions import APIException
from rest_framework.response import Response
from rest_framework.views import APIView

log = logging.getLogger("andrea_svc_web")


class StormSafeAPIView(APIView):
    """APIView whose unexpected handler errors return `soft_error_status` (default 200) with an
    {ok:false,error} body instead of a 500. Override `soft_error_status`/`soft_error_body` to tune."""

    #: status returned for an unexpected (non-DRF) exception — 200 = "handled, do not retry".
    soft_error_status = 200

    def soft_error_body(self, exc: Exception) -> dict:
        return {"ok": False, "error": str(exc)}

    def handle_exception(self, exc):
        # Let DRF's typed exceptions (auth/validation/throttling/404) resolve normally.
        if isinstance(exc, APIException):
            return super().handle_exception(exc)
        log.exception("unhandled error in %s.%s", self.__class__.__module__, self.__class__.__name__)
        return Response(self.soft_error_body(exc), status=self.soft_error_status)
