""" 错误处理 middleware for the Pixel API.

This middleware provides centralized exception handling with:
- Standardized error response format
- Request ID tracking and timestamp
- Comprehensive error logging with severity levels
- Support for custom application exceptions
- Automatic conversion of domain exceptions to HTTP responses
""" 
import logging
import traceback
import uuid
from datetime import datetime, timezone
from typing import Callable
from fastapi import Request, status
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException

from src.utils.errors import AppException, BusinessException, SystemException, ErrorCode

logger = logging.getLogger(__name__)


class ErrorResponse:
    """ 标准ized error response format

    Follows the design specification:
    {
        "code": "4001",
        "message": "Model not found or not configured",
        "details": {...},
        "request_id": "req_abc123",
        "timestamp": "2024-01-15T10:30:00Z"
    }
    """

    def __init__(
        self,
        code: ErrorCode,
        message: str,
        details: dict = None,
        request_id: str = None,
        timestamp: str = None
    ):
        self.code = code.value if isinstance(code, ErrorCode) else code
        self.message = message
        self.details = details or {}
        self.request_id = request_id or str(uuid.uuid4())
        self.timestamp = timestamp or datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
    
    def to_dict(self) -> dict:
        """ 转换 to dictionary for JSON response""" 
        return {
            "code": self.code,
            "message": self.message,
            "details": self.details,
            "request_id": self.request_id,
            "timestamp": self.timestamp
        }


async def error_handler_middleware(request: Request, call_next: Callable):
    """ 全局的 error handling middleware.
    
    Catches all exceptions and returns standardized error responses.
    Logs all errors with contextual information including request ID and timestamp.
    Uses appropriate severity levels: ERROR for system exceptions (5xx), WARNING for business exceptions (4xx).
    """
    # 生成 unique request ID and timestamp
    request_id = str(uuid.uuid4())
    timestamp = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
    request.state.request_id = request_id
    request.state.timestamp = timestamp
    
    # 提取 user context if available
    user_id = getattr(request.state, "user_id", None)
    project_id = request.query_params.get("project_id")
    
    try:
        response = await call_next(request)
        # Add request ID and timestamp to response headers
        response.headers["X-Request-ID"] = request_id
        response.headers["X-Timestamp"] = timestamp
        return response
        
    except RequestValidationError as e:
        # Handle Pydantic validation errors (4xx - client error)
        logger.warning(
            f"Validation error: {e}",
            extra={
                "request_id": request_id,
                "timestamp": timestamp,
                "path": request.url.path,
                "method": request.method,
                "user_id": user_id,
                "project_id": project_id,
                "errors": e.errors()
            }
        )
        
        error_response = ErrorResponse(
            code=ErrorCode.INVALID_PARAMETER,
            message="Request validation failed",
            details={"errors": e.errors()},
            request_id=request_id,
            timestamp=timestamp
        )
        
        return JSONResponse(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            content=error_response.to_dict(),
            headers={"X-Request-ID": request_id, "X-Timestamp": timestamp}
        )
    
    except AppException as e:
        # Handle custom application exceptions
        # Use WARNING for business exceptions (4xx), ERROR for system exceptions (5xx)
        log_level = logging.WARNING if isinstance(e, BusinessException) else logging.ERROR
        
        logger.log(
            log_level,
            f"Application error: {e.message}",
            extra={
                "request_id": request_id,
                "timestamp": timestamp,
                "path": request.url.path,
                "method": request.method,
                "user_id": user_id,
                "project_id": project_id,
                "error_code": e.code.value if isinstance(e.code, ErrorCode) else e.code,
                "details": e.details,
                "exception_type": type(e).__name__
            },
            exc_info=isinstance(e, SystemException)  # Include stack trace for system exceptions
        )
        
        error_response = ErrorResponse(
            code=e.code,
            message=e.message,
            details=e.details,
            request_id=request_id,
            timestamp=timestamp
        )
        
        return JSONResponse(
            status_code=e.status_code,
            content=error_response.to_dict(),
            headers={"X-Request-ID": request_id, "X-Timestamp": timestamp}
        )
    
    except StarletteHTTPException as e:
        # Handle Starlette HTTP exceptions (typically from FastAPI)
        logger.warning(
            f"HTTP exception: {e.detail}",
            extra={
                "request_id": request_id,
                "timestamp": timestamp,
                "path": request.url.path,
                "method": request.method,
                "user_id": user_id,
                "project_id": project_id,
                "status_code": e.status_code
            }
        )
        
        # 映射 HTTP status codes to error codes
        error_code_map = {
            404: ErrorCode.NOT_FOUND,
            401: ErrorCode.UNAUTHORIZED,
            403: ErrorCode.FORBIDDEN,
            429: ErrorCode.RATE_LIMIT_EXCEEDED,
            503: ErrorCode.UNKNOWN_ERROR,
            504: ErrorCode.UNKNOWN_ERROR,
        }
        
        error_code = error_code_map.get(e.status_code, ErrorCode.UNKNOWN_ERROR)
        
        error_response = ErrorResponse(
            code=error_code,
            message=str(e.detail),
            details={},
            request_id=request_id,
            timestamp=timestamp
        )
        
        return JSONResponse(
            status_code=e.status_code,
            content=error_response.to_dict(),
            headers={"X-Request-ID": request_id, "X-Timestamp": timestamp}
        )
    
    except Exception as e:
        # Handle unexpected exceptions (5xx - server error)
        logger.error(
            f"Unhandled exception: {str(e)}",
            extra={
                "request_id": request_id,
                "timestamp": timestamp,
                "path": request.url.path,
                "method": request.method,
                "user_id": user_id,
                "project_id": project_id,
                "exception_type": type(e).__name__,
                "traceback": traceback.format_exc()
            },
            exc_info=True
        )
        
        # Only include error details in debug mode
        error_details = {"error": str(e)} if logger.level == logging.DEBUG else {}
        
        error_response = ErrorResponse(
            code=ErrorCode.UNKNOWN_ERROR,
            message="An internal error occurred",
            details=error_details,
            request_id=request_id,
            timestamp=timestamp
        )
        
        return JSONResponse(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            content=error_response.to_dict(),
            headers={"X-Request-ID": request_id, "X-Timestamp": timestamp}
        )


def setup_error_handler(app):
    """ Setup error handler middleware for the FastAPI application.
    
    Args:
        app: FastAPI application instance
    """
    # Register middleware
    app.middleware("http")(error_handler_middleware)
    
    # Register exception handlers for specific exception types
    # This ensures they are caught before the default FastAPI handlers
    
    @app.exception_handler(StarletteHTTPException)
    async def http_exception_handler(request: Request, exc: StarletteHTTPException):
        """Handle HTTP exceptions with standardized format""" 
        request_id = getattr(request.state, "request_id", str(uuid.uuid4()))
        timestamp = getattr(request.state, "timestamp", datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"))
        user_id = getattr(request.state, "user_id", None)
        project_id = request.query_params.get("project_id")
        
        logger.warning(
            f"HTTP exception: {exc.detail}",
            extra={
                "request_id": request_id,
                "timestamp": timestamp,
                "path": request.url.path,
                "method": request.method,
                "user_id": user_id,
                "project_id": project_id,
                "status_code": exc.status_code
            }
        )
        
        # 映射 HTTP status codes to error codes
        error_code_map = {
            404: ErrorCode.NOT_FOUND,
            401: ErrorCode.UNAUTHORIZED,
            403: ErrorCode.FORBIDDEN,
            429: ErrorCode.RATE_LIMIT_EXCEEDED,
            503: ErrorCode.UNKNOWN_ERROR,
            504: ErrorCode.UNKNOWN_ERROR,
        }
        
        error_code = error_code_map.get(exc.status_code, ErrorCode.UNKNOWN_ERROR)
        
        error_response = ErrorResponse(
            code=error_code,
            message=str(exc.detail),
            details={},
            request_id=request_id,
            timestamp=timestamp
        )
        
        return JSONResponse(
            status_code=exc.status_code,
            content=error_response.to_dict(),
            headers={"X-Request-ID": request_id, "X-Timestamp": timestamp}
        )
    
    @app.exception_handler(RequestValidationError)
    async def validation_exception_handler(request: Request, exc: RequestValidationError):
        """Handle validation errors with standardized format""" 
        request_id = getattr(request.state, "request_id", str(uuid.uuid4()))
        timestamp = getattr(request.state, "timestamp", datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"))
        user_id = getattr(request.state, "user_id", None)
        project_id = request.query_params.get("project_id")
        
        logger.warning(
            f"Validation error: {exc}",
            extra={
                "request_id": request_id,
                "timestamp": timestamp,
                "path": request.url.path,
                "method": request.method,
                "user_id": user_id,
                "project_id": project_id,
                "errors": exc.errors()
            }
        )
        
        error_response = ErrorResponse(
            code=ErrorCode.INVALID_PARAMETER,
            message="Request validation failed",
            details={"errors": exc.errors()},
            request_id=request_id,
            timestamp=timestamp
        )
        
        return JSONResponse(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            content=error_response.to_dict(),
            headers={"X-Request-ID": request_id, "X-Timestamp": timestamp}
        )
    
    @app.exception_handler(AppException)
    async def app_exception_handler(request: Request, exc: AppException):
        """Handle custom application exceptions with standardized format""" 
        request_id = getattr(request.state, "request_id", str(uuid.uuid4()))
        timestamp = getattr(request.state, "timestamp", datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"))
        user_id = getattr(request.state, "user_id", None)
        project_id = request.query_params.get("project_id")
        
        # Use WARNING for business exceptions (4xx), ERROR for system exceptions (5xx)
        log_level = logging.WARNING if isinstance(exc, BusinessException) else logging.ERROR
        
        logger.log(
            log_level,
            f"Application error: {exc.message}",
            extra={
                "request_id": request_id,
                "timestamp": timestamp,
                "path": request.url.path,
                "method": request.method,
                "user_id": user_id,
                "project_id": project_id,
                "error_code": exc.code.value if isinstance(exc.code, ErrorCode) else exc.code,
                "details": exc.details,
                "exception_type": type(exc).__name__
            },
            exc_info=isinstance(exc, SystemException)  # Include stack trace for system exceptions
        )
        
        error_response = ErrorResponse(
            code=exc.code,
            message=exc.message,
            details=exc.details,
            request_id=request_id,
            timestamp=timestamp
        )
        
        return JSONResponse(
            status_code=exc.status_code,
            content=error_response.to_dict(),
            headers={"X-Request-ID": request_id, "X-Timestamp": timestamp}
        )
    
    @app.exception_handler(Exception)
    async def general_exception_handler(request: Request, exc: Exception):
        """Handle unexpected exceptions with standardized format""" 
        request_id = getattr(request.state, "request_id", str(uuid.uuid4()))
        timestamp = getattr(request.state, "timestamp", datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"))
        user_id = getattr(request.state, "user_id", None)
        project_id = request.query_params.get("project_id")
        
        logger.error(
            f"Unhandled exception: {str(exc)}",
            extra={
                "request_id": request_id,
                "timestamp": timestamp,
                "path": request.url.path,
                "method": request.method,
                "user_id": user_id,
                "project_id": project_id,
                "exception_type": type(exc).__name__,
                "traceback": traceback.format_exc()
            },
            exc_info=True
        )
        
        # Only include error details in debug mode
        error_details = {"error": str(exc)} if logger.level == logging.DEBUG else {}
        
        error_response = ErrorResponse(
            code=ErrorCode.UNKNOWN_ERROR,
            message="An internal error occurred",
            details=error_details,
            request_id=request_id,
            timestamp=timestamp
        )
        
        return JSONResponse(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            content=error_response.to_dict(),
            headers={"X-Request-ID": request_id, "X-Timestamp": timestamp}
        )
    
    logger.info("Error handler middleware and exception handlers registered")
