from __future__ import annotations

from typing import Any

from .exception_handlers.api_http_validation import FastAPIHTTPException, JSONResponse, describe_http_or_validation_exception
from .exception_handlers.base import ErrorInfo, build_response_meta, default_message, extract_request_id, extract_request_value
from .exception_handlers.cache_queue_family import describe_cache_queue_exception
from .exception_handlers.data_tool_family import describe_data_tool_exception
from .exception_handlers.database_family import describe_database_exception
from .exception_handlers.http_auth_cloud_family import describe_http_auth_cloud_exception
from .exception_handlers.python_family import describe_python_exception
from .logging import get_logger


class ApiError(Exception):
    def __init__(
        self,
        message: str,
        *,
        status_code: int = 400,
        code: str = "api_error",
        details: list[dict[str, Any]] | None = None,
        log_message: str | None = None,
    ) -> None:
        super().__init__(message)
        self.message = message
        self.status_code = status_code
        self.code = code
        self.details = details
        self.log_message = log_message


class ErrorHandler:
    def __init__(
        self,
        *,
        logger_name: str = "aniket_tools.errors",
        use_default_message_for_long_errors: bool = True,
    ) -> None:
        self.logger = get_logger(logger_name)
        self.use_default_message_for_long_errors = use_default_message_for_long_errors

    def describe(self, exc: Exception) -> ErrorInfo:
        allow_long_message = not self.use_default_message_for_long_errors

        if isinstance(exc, ApiError):
            return ErrorInfo(
                status_code=exc.status_code,
                code=exc.code,
                message=exc.message,
                details=exc.details,
                error_type=exc.__class__.__name__,
            )

        for describe_family in (
            lambda current: describe_http_or_validation_exception(current, allow_long_message=allow_long_message),
            describe_database_exception,
            describe_http_auth_cloud_exception,
            describe_cache_queue_exception,
            describe_data_tool_exception,
            lambda current: describe_python_exception(current, allow_long_message=allow_long_message),
        ):
            info = describe_family(exc)
            if info is not None:
                return info

        return ErrorInfo(
            status_code=500,
            code="internal_error",
            message=default_message(500),
            error_type=exc.__class__.__name__,
        )

    def log_exception(self, exc: Exception, request: Any = None) -> None:
        method = extract_request_value(request, "method")
        url = extract_request_value(request, "url")
        path = getattr(url, "path", None) if url else None
        request_id = extract_request_id(request)

        context_parts = ["Handled exception"]
        if method and path:
            context_parts.append(f"{method} {path}")
        if request_id:
            context_parts.append(f"request_id={request_id}")
        if isinstance(exc, ApiError) and exc.log_message:
            context_parts.append(exc.log_message)

        self.logger.error(" | ".join(context_parts), exc_info=exc)

    def build_payload(self, exc: Exception, request: Any = None, meta: Any | None = None) -> dict[str, Any]:
        info = self.describe(exc)
        response_meta = build_response_meta(request=request, meta=meta)
        error_block: dict[str, Any] = {
            "code": info.code,
            "type": info.error_type or exc.__class__.__name__,
            "message": info.message,
        }
        if info.retryable is not None:
            error_block["retryable"] = info.retryable

        payload: dict[str, Any] = {
            "success": False,
            "response_code": info.status_code,
            "error_message": info.message,
            "error_type": info.error_type or exc.__class__.__name__,
            "meta": response_meta,
            "error": error_block,
        }

        if info.details:
            payload["error"]["details"] = info.details
            payload["errors"] = info.details

        request_id = response_meta.get("request_id")
        if request_id:
            payload["request_id"] = request_id

        path = response_meta.get("path")
        if path:
            payload["path"] = path

        return payload

    def explain_error(self, exc: Exception) -> str:
        return self.describe(exc).message

    def get_status_code(self, exc: Exception) -> int:
        return self.describe(exc).status_code

    def handle_exception(self, exc: Exception, request: Any = None, meta: Any | None = None) -> Any:
        payload = self.build_payload(exc, request=request, meta=meta)
        if JSONResponse is None:
            return payload
        return JSONResponse(status_code=payload["response_code"], content=payload)

    async def unified_exception_handler(self, request: Any, exc: Exception) -> Any:
        self.log_exception(exc, request=request)
        return self.handle_exception(exc, request=request)

    def raise_http_exception(self, exc: Exception) -> None:
        if FastAPIHTTPException is None:
            raise RuntimeError("FastAPI must be installed to raise HTTPException objects.")

        self.log_exception(exc)
        info = self.describe(exc)
        detail: dict[str, Any] = {
            "code": info.code,
            "message": info.message,
            "error_type": info.error_type or exc.__class__.__name__,
        }
        if info.details:
            detail["details"] = info.details
        raise FastAPIHTTPException(status_code=info.status_code, detail=detail)


_default_handler = ErrorHandler()


def explain_error(exc: Exception) -> str:
    return _default_handler.explain_error(exc)


def get_status_code(exc: Exception) -> int:
    return _default_handler.get_status_code(exc)


def handle_exception(exc: Exception, request: Any = None, meta: Any | None = None) -> Any:
    return _default_handler.handle_exception(exc, request=request, meta=meta)


async def unified_exception_handler(request: Any, exc: Exception) -> Any:
    return await _default_handler.unified_exception_handler(request, exc)


def ExceptionHandler(exc: Exception) -> None:
    _default_handler.raise_http_exception(exc)
