from fastapi import FastAPI, HTTPException, status
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from sqlalchemy.exc import IntegrityError, OperationalError, SQLAlchemyError, DBAPIError

from src.core.errors import (
    AccessDeniedException,
    BasicAppException,
    UserNotFoundException,
    UsernameConflictException,
    ValidationException,
    ValidationsError,
)
from src.core.CONSTANTS import (
    VALIDATION_PREFIXES,
    PG_404_ERRORS,
    PG_409_ERRORS,
    PG_400_ERRORS,
    PG_403_ERRORS,
)

def _error_payload(code: int, message: str, field: str = "") -> dict:
    """Формирует структурированный payload ошибки по спеке."""
    return {
        "code": code,
        "field": field,
        "message": message,
    }


def _parse_prefixed_validation_error(message: str) -> str:
    for prefix in VALIDATION_PREFIXES:
        if message.lower().startswith(prefix):
            field = message.split(":", 1)[1].strip() if ":" in message else ""
            return field or ""
    return ""


def _first_db_message_line(message: str) -> str:
    """Возвращает первую непустую строку текста ошибки БД."""
    for line in message.splitlines():
        line = line.strip()
        if line:
            return line
    return message.strip()


def map_sqlalchemy_error(exc: SQLAlchemyError) -> tuple[int, int, str, str]:
    """Маппинг ошибок SQLAlchemy в HTTP-статус + code/message/field."""
    raw = str(getattr(exc, "orig", exc) or exc).strip()
    message = _first_db_message_line(raw) or "Database error"
    low = message.lower()

    if any(low.startswith(prefix) for prefix in VALIDATION_PREFIXES):
        status_code = status.HTTP_422_UNPROCESSABLE_ENTITY
        field = _parse_prefixed_validation_error(message)
        return status_code, status_code, message, field
    if any(error in low for error in PG_404_ERRORS):
        status_code = status.HTTP_404_NOT_FOUND
        return status_code, status_code, message, ""
    if any(error in low for error in PG_403_ERRORS):
        status_code = status.HTTP_403_FORBIDDEN
        return status_code, status_code, message, ""

    if isinstance(exc, IntegrityError):
        if any(error in low for error in PG_409_ERRORS):
            status_code = status.HTTP_409_CONFLICT
            return status_code, status_code, message, ""
        if any(error in low for error in PG_400_ERRORS):
            status_code = status.HTTP_400_BAD_REQUEST
            return status_code, status_code, message, ""
        status_code = status.HTTP_400_BAD_REQUEST
        return status_code, status_code, message, ""

    if isinstance(exc, OperationalError):
        status_code = status.HTTP_503_SERVICE_UNAVAILABLE
        return status_code, status_code, "Database unavailable", ""

    status_code = status.HTTP_400_BAD_REQUEST
    return status_code, status_code, message, ""


def map_http_exception(exc: HTTPException) -> tuple[int, int, str, str]:
    """Маппинг HTTPException в единый формат ошибки API."""
    status_code = int(exc.status_code)
    detail = exc.detail

    if isinstance(detail, dict):
        message = str(detail.get("message") or detail.get("detail") or "HTTP error")
        field = detail.get("field")
        field_str = str(field) if field is not None else ""
        return status_code, status_code, message, field_str

    message = str(detail) if detail else "HTTP error"
    return status_code, status_code, message, ""


def map_request_validation_error(exc: RequestValidationError) -> tuple[int, int, str, str]:
    status_code = status.HTTP_422_UNPROCESSABLE_ENTITY
    errors = exc.errors()
    if not errors:
        return status_code, status_code, "Validation error", ""

    first_error = errors[0]
    message = str(first_error.get("msg") or "Validation error")
    loc = first_error.get("loc") or ()
    field = ".".join(str(part) for part in loc if part not in ("body", "query", "path", "header", "cookie"))
    return status_code, status_code, message, field


def register_exception_handlers(app: FastAPI) -> None:
    @app.exception_handler(RequestValidationError)
    async def request_validation_exception_handler(request, exc: RequestValidationError):
        status_code, code, message, field = map_request_validation_error(exc)
        return JSONResponse(
            status_code=status_code,
            content=_error_payload(code, message, field=field),
        )

    @app.exception_handler(HTTPException)
    async def http_exception_handler(request, exc: HTTPException):
        status_code, code, message, field = map_http_exception(exc)
        return JSONResponse(
            status_code=status_code,
            content=_error_payload(code, message, field=field),
            headers=exc.headers,
        )

    @app.exception_handler(AccessDeniedException)
    async def access_denied_exception_handler(request, exc: AccessDeniedException):
        status_code = status.HTTP_403_FORBIDDEN
        return JSONResponse(
            status_code=status_code,
            content=_error_payload(status_code, exc.description),
        )

    @app.exception_handler(ValidationException)
    async def validation_exception_handler(request, exc: ValidationException):
        status_code = status.HTTP_400_BAD_REQUEST
        return JSONResponse(
            status_code=status_code,
            content=_error_payload(status_code, exc.description, field=exc.field or ""),
        )

    @app.exception_handler(UserNotFoundException)
    async def user_not_found_exception_handler(request, exc: UserNotFoundException):
        status_code = status.HTTP_404_NOT_FOUND
        return JSONResponse(
            status_code=status_code,
            content=_error_payload(status_code, exc.description),
        )

    @app.exception_handler(UsernameConflictException)
    async def username_conflict_exception_handler(request, exc: UsernameConflictException):
        status_code = status.HTTP_409_CONFLICT
        return JSONResponse(
            status_code=status_code,
            content=_error_payload(status_code, exc.description),
        )

    @app.exception_handler(BasicAppException)
    async def basic_app_exception_handler(request, exc: BasicAppException):
        message = exc.description or "Application error"
        status_code = status.HTTP_400_BAD_REQUEST
        return JSONResponse(
            status_code=status_code,
            content=_error_payload(status_code, message),
        )

    @app.exception_handler(SQLAlchemyError)
    @app.exception_handler(DBAPIError)
    async def sqlalchemy_exception_handler(request, exc: SQLAlchemyError):
        status_code, code, message, field = map_sqlalchemy_error(exc)
        return JSONResponse(
            status_code=status_code,
            content=_error_payload(code, message, field=field),
        )

    @app.exception_handler(ValidationsError)
    async def validations_error_handler(request, exc: ValidationsError):
        return JSONResponse(
            status_code=status.HTTP_400_BAD_REQUEST,
            content=exc.errors,
        )
