"""Centralized exception handlers for FastAPI."""

import os

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

from identity_plan_kit.auth.domain.exceptions import (
    AuthError,
    TokenExpiredError,
    TokenInvalidError,
    UserInactiveError,
    UserNotFoundError,
)
from identity_plan_kit.plans.domain.exceptions import (
    FeatureNotAvailableError,
    PlanExpiredError,
    PlanNotFoundError,
    QuotaExceededError,
    UserPlanNotFoundError,
)
from identity_plan_kit.rbac.domain.exceptions import (
    PermissionDeniedError,
    RoleNotFoundError,
)
from identity_plan_kit.shared.exceptions import (
    BaseError,
)
from identity_plan_kit.shared.logging import get_logger

logger = get_logger(__name__)


def create_error_response(
    status_code: int,
    code: str,
    message: str,
    details: dict | None = None,
) -> JSONResponse:
    """
    Create a standardized error response.

    Args:
        status_code: HTTP status code
        code: Error code (e.g., "AUTH_ERROR")
        message: Human-readable error message
        details: Additional error details

    Returns:
        JSONResponse with error body
    """
    content = {
        "error": {
            "code": code,
            "message": message,
        }
    }
    if details:
        content["error"]["details"] = details

    return JSONResponse(status_code=status_code, content=content)


async def base_error_handler(request: Request, exc: BaseError) -> JSONResponse:
    """Handle all BaseError exceptions."""
    logger.warning(
        "application_error",
        error_code=exc.code,
        message=exc.message,
        path=request.url.path,
    )
    return create_error_response(
        status_code=exc.status_code,
        code=exc.code,
        message=exc.message,
        details=exc.details,
    )


async def auth_error_handler(request: Request, exc: AuthError) -> JSONResponse:
    """Handle authentication errors."""
    logger.warning(
        "auth_error",
        error_code=exc.code,
        path=request.url.path,
    )
    return create_error_response(
        status_code=401,
        code=exc.code,
        message=exc.message,
        details=exc.details,
    )


async def token_expired_handler(request: Request, exc: TokenExpiredError) -> JSONResponse:  # noqa: ARG001
    """Handle expired token errors."""
    return create_error_response(
        status_code=401,
        code=exc.code,
        message="Your session has expired. Please log in again.",
    )


async def token_invalid_handler(request: Request, exc: TokenInvalidError) -> JSONResponse:  # noqa: ARG001
    """Handle invalid token errors."""
    return create_error_response(
        status_code=401,
        code=exc.code,
        message="Invalid authentication token.",
    )


async def user_inactive_handler(request: Request, exc: UserInactiveError) -> JSONResponse:  # noqa: ARG001
    """Handle inactive user errors."""
    return create_error_response(
        status_code=403,
        code=exc.code,
        message="Your account has been deactivated.",
    )


async def user_not_found_handler(request: Request, exc: UserNotFoundError) -> JSONResponse:  # noqa: ARG001
    """Handle user not found errors."""
    return create_error_response(
        status_code=404,
        code=exc.code,
        message="User not found.",
    )


async def permission_denied_handler(request: Request, exc: PermissionDeniedError) -> JSONResponse:
    """Handle permission denied errors."""
    logger.warning(
        "permission_denied",
        permission=exc.permission_code,
        path=request.url.path,
    )
    return create_error_response(
        status_code=403,
        code=exc.code,
        message=exc.message,
        details={"permission": exc.permission_code} if exc.permission_code else None,
    )


async def role_not_found_handler(request: Request, exc: RoleNotFoundError) -> JSONResponse:  # noqa: ARG001
    """Handle role not found errors."""
    return create_error_response(
        status_code=404,
        code=exc.code,
        message=exc.message,
    )


async def quota_exceeded_handler(request: Request, exc: QuotaExceededError) -> JSONResponse:
    """Handle quota exceeded errors."""
    logger.info(
        "quota_exceeded",
        feature=exc.feature_code,
        path=request.url.path,
    )
    return create_error_response(
        status_code=429,
        code=exc.code,
        message=exc.message,
        details={
            "feature": exc.feature_code,
            "limit": exc.limit,
            "used": exc.used,
            "period": exc.period,
        },
    )


async def plan_expired_handler(request: Request, exc: PlanExpiredError) -> JSONResponse:  # noqa: ARG001
    """Handle plan expired errors."""
    return create_error_response(
        status_code=403,
        code=exc.code,
        message="Your subscription plan has expired.",
    )


async def feature_not_available_handler(
    request: Request,  # noqa: ARG001
    exc: FeatureNotAvailableError,
) -> JSONResponse:
    """Handle feature not available errors."""
    return create_error_response(
        status_code=403,
        code=exc.code,
        message=exc.message,
        details={
            "feature": exc.feature_code,
            "plan": exc.plan_code,
        },
    )


async def user_plan_not_found_handler(request: Request, exc: UserPlanNotFoundError) -> JSONResponse:  # noqa: ARG001
    """Handle user plan not found errors."""
    return create_error_response(
        status_code=404,
        code=exc.code,
        message="No active subscription plan found.",
    )


async def plan_not_found_handler(request: Request, exc: PlanNotFoundError) -> JSONResponse:  # noqa: ARG001
    """Handle plan not found errors."""
    return create_error_response(
        status_code=404,
        code=exc.code,
        message=exc.message,
    )


async def http_exception_handler(request: Request, exc: HTTPException) -> JSONResponse:
    """
    Handle FastAPI HTTPException errors.

    Converts standard HTTPException to use the library's error structure.
    """
    logger.warning(
        "http_exception",
        status_code=exc.status_code,
        detail=exc.detail,
        path=request.url.path,
    )

    # Map status codes to error codes
    code_mapping = {
        400: "BAD_REQUEST",
        401: "UNAUTHORIZED",
        403: "FORBIDDEN",
        404: "NOT_FOUND",
        405: "METHOD_NOT_ALLOWED",
        409: "CONFLICT",
        422: "VALIDATION_ERROR",
        429: "RATE_LIMIT_EXCEEDED",
        500: "INTERNAL_ERROR",
        502: "BAD_GATEWAY",
        503: "SERVICE_UNAVAILABLE",
        504: "GATEWAY_TIMEOUT",
    }

    error_code = code_mapping.get(exc.status_code, "HTTP_ERROR")

    # Extract details if the detail is a dict
    details = None
    message = str(exc.detail)
    if isinstance(exc.detail, dict):
        message = exc.detail.get("message", str(exc.detail))
        details = {k: v for k, v in exc.detail.items() if k != "message"}

    return create_error_response(
        status_code=exc.status_code,
        code=error_code,
        message=message,
        details=details if details else None,
    )


async def validation_exception_handler(
    request: Request, exc: RequestValidationError
) -> JSONResponse:
    """
    Handle Pydantic validation errors.

    Converts validation errors into a user-friendly format with clear messages.
    """
    logger.warning(
        "validation_error",
        error_count=len(exc.errors()),
        path=request.url.path,
    )

    # Format validation errors into clear messages
    errors = []
    for error in exc.errors():
        # Get the field path (e.g., ["body", "email"] -> "email")
        field_path = " -> ".join(
            str(loc) for loc in error["loc"] if loc not in ["body", "query", "path"]
        )
        field_name = field_path or "request"

        # Get the error message
        error_msg = error["msg"]
        error_type = error["type"]

        # Create user-friendly message
        if error_type == "missing":
            message = f"Field '{field_name}' is required"
        elif error_type == "value_error":
            message = f"Invalid value for '{field_name}': {error_msg}"
        elif error_type == "type_error":
            message = f"Invalid type for '{field_name}': {error_msg}"
        else:
            message = f"{field_name}: {error_msg}"

        errors.append(
            {
                "field": field_name,
                "message": message,
                "type": error_type,
            }
        )

    # Create a summary message
    if len(errors) == 1:
        summary = errors[0]["message"]
    else:
        summary = f"Validation failed for {len(errors)} field(s)"

    return create_error_response(
        status_code=422,
        code="VALIDATION_ERROR",
        message=summary,
        details={"errors": errors},
    )


async def generic_exception_handler(request: Request, exc: Exception) -> JSONResponse:
    """
    Handle all unhandled exceptions.

    This is a catch-all handler for any exceptions that don't have
    a specific handler. Logs the full error for debugging while
    returning a safe error message to the client.
    """
    logger.error(
        "unhandled_exception",
        exc_type=type(exc).__name__,
        exc_message=str(exc),
        path=request.url.path,
        exc_info=exc,
    )

    # In development, include more details
    # In production, keep it generic for security
    environment = os.getenv("IPK_ENVIRONMENT", "production")

    if environment == "development":
        # SECURITY FIX: Sanitize exception message to avoid leaking sensitive data
        exc_message = str(exc)
        # Patterns that may contain sensitive data
        sensitive_patterns = [
            (r"password[=:]\s*['\"]?[^'\";\s]+", "password=***"),
            (r"token[=:]\s*['\"]?[^'\";\s]+", "token=***"),
            (r"secret[=:]\s*['\"]?[^'\";\s]+", "secret=***"),
            (r"key[=:]\s*['\"]?[^'\";\s]+", "key=***"),
            (r"Bearer\s+[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+", "Bearer ***"),
        ]
        import re  # noqa: PLC0415

        for pattern, replacement in sensitive_patterns:
            exc_message = re.sub(pattern, replacement, exc_message, flags=re.IGNORECASE)

        details = {
            "type": type(exc).__name__,
            "message": exc_message,
        }
    else:
        details = None

    return create_error_response(
        status_code=500,
        code="INTERNAL_ERROR",
        message="An unexpected error occurred. Please try again later.",
        details=details,
    )


def register_exception_handlers(app: FastAPI) -> None:
    """
    Register all exception handlers with a FastAPI application.

    Args:
        app: FastAPI application instance

    Usage:
        ```python
        app = FastAPI()
        register_exception_handlers(app)
        ```
    """
    # Auth errors
    app.add_exception_handler(TokenExpiredError, token_expired_handler)
    app.add_exception_handler(TokenInvalidError, token_invalid_handler)
    app.add_exception_handler(UserInactiveError, user_inactive_handler)
    app.add_exception_handler(UserNotFoundError, user_not_found_handler)
    app.add_exception_handler(AuthError, auth_error_handler)

    # RBAC errors
    app.add_exception_handler(PermissionDeniedError, permission_denied_handler)
    app.add_exception_handler(RoleNotFoundError, role_not_found_handler)

    # Plan errors
    app.add_exception_handler(QuotaExceededError, quota_exceeded_handler)
    app.add_exception_handler(PlanExpiredError, plan_expired_handler)
    app.add_exception_handler(FeatureNotAvailableError, feature_not_available_handler)
    app.add_exception_handler(UserPlanNotFoundError, user_plan_not_found_handler)
    app.add_exception_handler(PlanNotFoundError, plan_not_found_handler)

    # Base error (catch-all for domain errors)
    app.add_exception_handler(BaseError, base_error_handler)

    # FastAPI built-in exceptions
    app.add_exception_handler(HTTPException, http_exception_handler)
    app.add_exception_handler(RequestValidationError, validation_exception_handler)

    # Generic exception handler (must be last - catch-all)
    app.add_exception_handler(Exception, generic_exception_handler)

    logger.debug("exception_handlers_registered")
