from __future__ import annotations

from typing import Any

from .._compat import compact_types, optional_import
from .base import ErrorInfo, clean_message, default_message, http_detail_mapping, safe_client_message

FastAPIHTTPException = optional_import("fastapi", "HTTPException")
JSONResponse = optional_import("fastapi.responses", "JSONResponse")
StarletteHTTPException = optional_import("starlette.exceptions", "HTTPException")
RequestValidationError = optional_import("fastapi.exceptions", "RequestValidationError")
PydanticValidationError = optional_import("pydantic", "ValidationError")

HTTP_EXCEPTION_TYPES = compact_types(FastAPIHTTPException, StarletteHTTPException)
VALIDATION_EXCEPTION_TYPES = compact_types(RequestValidationError, PydanticValidationError)


def _normalize_validation_errors(exc: Exception) -> list[dict[str, str]]:
    if not hasattr(exc, "errors"):
        return [{"type": "validation_error", "field": "data", "message": safe_client_message(exc, "Validation failed.")}]

    normalized: list[dict[str, str]] = []
    for error in exc.errors():  # type: ignore[attr-defined]
        location = [str(part) for part in error.get("loc", []) if part != "__root__"]
        source = location[0] if location and location[0] in {"body", "query", "path", "header", "cookie"} else None
        if source:
            location = location[1:]

        detail = {
            "type": error.get("type") or "validation_error",
            "field": ".".join(location) if location else "data",
            "message": error.get("msg") or error.get("message") or "Invalid value.",
        }
        if source:
            detail["source"] = source
        normalized.append(detail)
    return normalized


def describe_http_or_validation_exception(exc: Exception, *, allow_long_message: bool) -> ErrorInfo | None:
    if HTTP_EXCEPTION_TYPES and isinstance(exc, HTTP_EXCEPTION_TYPES):
        status_code = getattr(exc, "status_code", 500)
        detail = getattr(exc, "detail", None)
        detail_mapping = http_detail_mapping(detail)
        if detail_mapping is not None:
            details = detail_mapping.get("details")
            normalized_details = [dict(item) for item in details] if isinstance(details, list) else None
            code = str(detail_mapping.get("code") or f"http_{status_code}")
            error_type = str(detail_mapping.get("error_type") or exc.__class__.__name__)
            if status_code >= 500:
                message = default_message(status_code)
            else:
                message = clean_message(
                    str(detail_mapping.get("message") or ""),
                    allow_long_message=allow_long_message,
                ) or default_message(status_code)
            return ErrorInfo(
                status_code=status_code,
                code=code,
                message=message,
                details=normalized_details,
                error_type=error_type,
            )

        if status_code >= 500:
            message = default_message(status_code)
        elif isinstance(detail, str):
            message = clean_message(detail, allow_long_message=allow_long_message) or default_message(status_code)
        else:
            message = default_message(status_code)
        return ErrorInfo(
            status_code=status_code,
            code=f"http_{status_code}",
            message=message,
            error_type=exc.__class__.__name__,
        )

    if VALIDATION_EXCEPTION_TYPES and isinstance(exc, VALIDATION_EXCEPTION_TYPES):
        return ErrorInfo(
            status_code=422,
            code="validation_error",
            message=default_message(422),
            details=_normalize_validation_errors(exc),
            error_type=exc.__class__.__name__,
        )

    return None
