"""FastAPI entrypoint."""

from __future__ import annotations

import threading
from contextlib import asynccontextmanager
from typing import AsyncIterator

from fastapi import FastAPI

from ocr_sprint import __version__
from ocr_sprint.api.errors import register_error_handlers
from ocr_sprint.api.metrics import MetricsMiddleware, metrics_endpoint
from ocr_sprint.api.routes import documents, ground_truth, health
from ocr_sprint.config import get_settings
from ocr_sprint.db import models as _models  # noqa: F401  (register ORM tables)
from ocr_sprint.db.base import Base, get_engine
from ocr_sprint.utils.logging import configure_logging, get_logger


_startup_logger = get_logger(__name__)


def _ensure_schema() -> None:
    """Create tables if they don't exist.

    Production deploys should run Alembic migrations explicitly; this is a
    convenience for local dev / tests so the API works without a manual
    `alembic upgrade head` step.
    """
    Base.metadata.create_all(bind=get_engine())


def _warmup_models_background() -> None:
    """Load PaddleOCR and PP-Structure models in a background thread.

    Running in a thread keeps the lifespan non-blocking so uvicorn can
    start accepting health-check requests immediately while the heavy models
    load (~5-15s on CPU). Requests that arrive before warmup completes will
    wait on the existing _lock in each module rather than racing to load.
    """
    from ocr_sprint.config import get_settings as _gs
    from ocr_sprint.pipeline import ocr as _ocr
    from ocr_sprint.pipeline import table as _table

    s = _gs()
    try:
        _ocr.warmup()
    except Exception as exc:
        _startup_logger.warning("paddleocr.warmup.failed", error=str(exc))

    if s.tables_enabled:
        try:
            _table.warmup()
        except Exception as exc:
            _startup_logger.warning("pp_structure.warmup.failed", error=str(exc))


@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
    """FastAPI lifespan: warm OCR models on startup in a background thread."""
    _startup_logger.info("startup.warmup.begin")
    t = threading.Thread(target=_warmup_models_background, name="ocr-warmup", daemon=True)
    t.start()
    yield
    # Shutdown: nothing to clean up (models are process-global singletons).
    _startup_logger.info("shutdown.complete")


def create_app() -> FastAPI:
    """Application factory — keeps top-level state easy to test."""
    settings = get_settings()
    configure_logging(settings.app_log_level)
    _ensure_schema()

    # Support for reverse proxy with path prefix (e.g., /ocr)
    root_path = getattr(settings, "root_path", "")

    app = FastAPI(
        lifespan=lifespan,
        title="OCR Sprint Service",
        version=__version__,
        description="OCR + structured extraction for Indonesian police 'surat sprint' documents.",
        docs_url="/docs",
        redoc_url="/redoc",
        openapi_url="/openapi.json",
        root_path=root_path,
    )

    register_error_handlers(app)

    # CORS — allow frontend dev servers and production origins
    from fastapi.middleware.cors import CORSMiddleware
    app.add_middleware(
        CORSMiddleware,
        allow_origins=["*"],
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )

    app.add_middleware(MetricsMiddleware)
    app.include_router(health.router, prefix="/api/v1")
    app.include_router(documents.router, prefix="/api/v1")
    app.include_router(ground_truth.router, prefix="/api/v1")
    app.add_api_route("/metrics", metrics_endpoint, methods=["GET"], include_in_schema=False)
    return app


app = create_app()


def run() -> None:
    """Console-script entrypoint (`ocr-sprint-api`)."""
    import uvicorn

    s = get_settings()
    uvicorn.run("ocr_sprint.main:app", host=s.app_host, port=s.app_port, reload=False)
