"""RFC 9457 problem details for every error the API returns."""
import logging
from http import HTTPStatus
from typing import Any

from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from starlette.exceptions import HTTPException as StarletteHTTPException

from .observability import request_id

log = logging.getLogger("orders.errors")
PROBLEM_JSON = "application/problem+json"
PROBLEM_BASE = "https://docs.brew.example/problems/"


class ApiError(Exception):
    """A domain error with a stable problem type that clients can branch on."""

    def __init__(self, status: int, detail: str, *, type_: str | None = None,
                 headers: dict[str, str] | None = None, **extra: Any) -> None:
        super().__init__(detail)
        self.status, self.detail, self.type, self.headers, self.extra = status, detail, type_, headers, extra


def problem(request: Request, status: int, detail: str | None = None, *, type_: str | None = None,
            headers: dict[str, str] | None = None, **extra: Any) -> JSONResponse:
    body = {"type": PROBLEM_BASE + type_ if type_ else "about:blank",
            "title": HTTPStatus(status).phrase,
            "status": status,
            "detail": detail,
            "instance": request.url.path,
            "requestId": request_id.get(),
            **extra}
    return JSONResponse(body, status_code=status, headers=headers, media_type=PROBLEM_JSON)


def install_error_handlers(app: FastAPI) -> None:
    @app.exception_handler(ApiError)
    async def api_error(request: Request, exc: ApiError) -> JSONResponse:
        return problem(request, exc.status, exc.detail, type_=exc.type, headers=exc.headers, **exc.extra)

    @app.exception_handler(StarletteHTTPException)
    async def http_error(request: Request, exc: StarletteHTTPException) -> JSONResponse:
        return problem(request, exc.status_code, str(exc.detail), headers=exc.headers)

    @app.exception_handler(RequestValidationError)
    async def validation_error(request: Request, exc: RequestValidationError) -> JSONResponse:
        # no "input" field: never echo submitted values (they may contain passwords)
        errors = [{"loc": list(e["loc"]), "msg": e["msg"], "type": e["type"]} for e in exc.errors()]
        return problem(request, 422, "the request is invalid", type_="validation-error", errors=errors)

    @app.exception_handler(Exception)
    async def unexpected(request: Request, exc: Exception) -> JSONResponse:
        log.exception("unhandled error")
        return problem(request, 500, "an unexpected error occurred; quote the requestId when reporting it")
