"""
Exception handlers for DNR-Back applications.

Provides centralized exception handling for:
- State machine transition errors
- Invariant violations
- Pydantic validation errors
- Custom 404 pages for site rendering

HTMX-aware: when HX-Request header is present, validation errors return
rendered HTML fragments with HX-Retarget instead of raw JSON.
"""

from pathlib import Path
from typing import Any

from fastapi import FastAPI
from fastapi.responses import Response


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

    Handles:
    - TransitionError: State machine transition failures (422)
    - InvariantViolationError: Business rule violations (422)
    - ValidationError: Pydantic validation errors (422)
    - ConstraintViolationError: Database constraint violations (422)

    For HTMX requests, validation errors return rendered HTML fragments
    targeted at #form-errors instead of raw JSON.

    Args:
        app: FastAPI application instance
    """
    from fastapi import Request
    from fastapi.responses import JSONResponse as _JSONResponse
    from pydantic import ValidationError

    from dazzle_back.runtime.htmx_response import is_htmx_request, json_or_htmx_error
    from dazzle_back.runtime.invariant_evaluator import InvariantViolationError
    from dazzle_back.runtime.repository import ConstraintViolationError
    from dazzle_back.runtime.state_machine import TransitionError

    @app.exception_handler(ConstraintViolationError)
    async def constraint_violation_handler(
        request: Request, exc: ConstraintViolationError
    ) -> Response:
        """Convert database constraint violations to 422 Unprocessable Entity."""
        if is_htmx_request(request):
            return json_or_htmx_error(
                request,
                [{"loc": [exc.field] if exc.field else [], "msg": str(exc)}],
                error_type="constraint_violation",
            )
        detail: dict[str, Any] = {
            "detail": str(exc),
            "type": "constraint_violation",
            "constraint_type": exc.constraint_type,
        }
        if exc.field:
            detail["field"] = exc.field
        return _JSONResponse(status_code=422, content=detail)

    @app.exception_handler(TransitionError)
    async def transition_error_handler(request: Request, exc: TransitionError) -> Response:
        """Convert state machine errors to 422 Unprocessable Entity."""
        if is_htmx_request(request):
            return json_or_htmx_error(
                request,
                [{"loc": [], "msg": str(exc)}],
                error_type="transition_error",
            )
        return _JSONResponse(
            status_code=422,
            content={"detail": str(exc), "type": "transition_error"},
        )

    @app.exception_handler(InvariantViolationError)
    async def invariant_error_handler(request: Request, exc: InvariantViolationError) -> Response:
        """Convert invariant violations to 422 Unprocessable Entity."""
        if is_htmx_request(request):
            return json_or_htmx_error(
                request,
                [{"loc": [], "msg": str(exc)}],
                error_type="invariant_violation",
            )
        return _JSONResponse(
            status_code=422,
            content={"detail": str(exc), "type": "invariant_violation"},
        )

    @app.exception_handler(ValidationError)
    async def validation_error_handler(request: Request, exc: ValidationError) -> Response:
        """Convert validation errors to 422 Unprocessable Entity with field details.

        For HTMX requests: renders form_errors.html fragment with HX-Retarget
        to #form-errors so the error displays in-place without destroying the form.

        For API requests: returns JSON with structured error details.
        """
        # Pydantic errors() can contain non-serializable objects in ctx
        # (e.g. raw ValueError instances from AfterValidator). Sanitize them.
        errors: list[dict[str, Any]] = []
        for err in exc.errors():
            clean: dict[str, Any] = {}
            for k, v in err.items():
                if k == "ctx" and isinstance(v, dict):
                    clean[k] = {ck: str(cv) for ck, cv in v.items()}
                else:
                    clean[k] = v
            errors.append(clean)

        return json_or_htmx_error(request, errors, error_type="validation_error")


def register_site_404_handler(
    app: FastAPI,
    sitespec_data: dict[str, Any],
    project_root: Path | None = None,
) -> None:
    """
    Register custom 404 handler for site pages.

    Returns HTML 404 pages for browser requests when a SiteSpec is configured.
    API requests still receive JSON responses.

    Args:
        app: FastAPI application instance
        sitespec_data: SiteSpec configuration dictionary
        project_root: Project root for detecting custom CSS
    """
    from starlette.exceptions import HTTPException as StarletteHTTPException

    from dazzle_ui.runtime.site_renderer import render_404_page_html

    has_custom_css = bool(
        project_root and (project_root / "static" / "css" / "custom.css").is_file()
    )

    @app.exception_handler(StarletteHTTPException)
    async def custom_404_handler(request: Any, exc: StarletteHTTPException) -> Any:
        """Custom 404 handler that renders HTML for browser requests."""
        if exc.status_code == 404:
            from fastapi.responses import HTMLResponse

            # Only serve HTML 404 for browser requests (not API calls)
            accept = request.headers.get("accept", "")
            if "text/html" in accept:
                return HTMLResponse(
                    content=render_404_page_html(
                        sitespec_data, str(request.url.path), custom_css=has_custom_css
                    ),
                    status_code=404,
                )

        # For non-404 or API requests, return default JSON response
        from fastapi.responses import JSONResponse

        return JSONResponse(
            status_code=exc.status_code,
            content={"detail": exc.detail},
        )
