from __future__ import annotations

import json
import logging
from contextlib import asynccontextmanager
from functools import lru_cache

from fastapi import FastAPI, Header, HTTPException, Request
from starlette.datastructures import UploadFile

from . import __version__
from .config import Settings
from .engine import Engine, InputDocument
from .ollama import OllamaClient, OllamaError

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
logger = logging.getLogger("pataniscai")


@asynccontextmanager
async def lifespan(_: FastAPI):
    try:
        engine().client.load()
        logger.info("model preload completed")
    except OllamaError:
        logger.warning("model preload failed; health remains unavailable until Ollama recovers")
    yield


@lru_cache(maxsize=1)
def settings() -> Settings:
    return Settings.from_env()


@lru_cache(maxsize=1)
def engine() -> Engine:
    cfg = settings()
    return Engine(OllamaClient(cfg.ollama_url, cfg.model, cfg.request_timeout))


app = FastAPI(
    title="pataniscAI", version=__version__, docs_url=None, redoc_url=None, lifespan=lifespan,
)


def _authorize(request: Request, token: str | None) -> None:
    cfg = settings()
    client_ip = request.client.host if request.client else ""
    if client_ip not in cfg.allowed_ips:
        raise HTTPException(status_code=403, detail="Cliente nao autorizado")
    if not cfg.token or token != cfg.token:
        raise HTTPException(status_code=401, detail="Token invalido")


@app.get("/health")
def health(request: Request) -> dict:
    cfg = settings()
    client_ip = request.client.host if request.client else ""
    if client_ip not in cfg.allowed_ips:
        raise HTTPException(status_code=403, detail="Cliente nao autorizado")
    try:
        backend = engine().client.health()
        ready = bool(backend["model_available"])
    except OllamaError:
        backend = {"version": "", "model_available": False, "models_count": 0}
        ready = False
    return {
        "service": "pataniscAI",
        "version": __version__,
        "engine_version": f"pataniscAI-{__version__}",
        "model": cfg.model,
        "ready": ready,
        "gpu": backend.get("gpu", {"model_loaded": False, "vram_bytes": 0}),
        "backend": backend,
    }


@app.post("/v1/extract")
async def extract(
    request: Request,
    x_pataniscai_token: str | None = Header(default=None),
) -> dict:
    _authorize(request, x_pataniscai_token)
    cfg = settings()
    form = await request.form()
    manifest = form.get("manifest")
    if not isinstance(manifest, str):
        raise HTTPException(status_code=422, detail="Manifesto em falta")
    try:
        decoded = json.loads(manifest)
    except json.JSONDecodeError as exc:
        raise HTTPException(status_code=422, detail="Manifesto JSON invalido") from exc
    definitions = decoded.get("documents", []) if isinstance(decoded, dict) else []
    if not isinstance(definitions, list):
        raise HTTPException(status_code=422, detail="Documentos invalidos no manifesto")
    if not definitions:
        raise HTTPException(status_code=422, detail="O manifesto nao contem documentos")
    if len(definitions) > cfg.max_files:
        raise HTTPException(status_code=413, detail="Demasiados ficheiros")

    documents: list[InputDocument] = []
    for definition in definitions:
        if not isinstance(definition, dict):
            raise HTTPException(status_code=422, detail="Documento invalido no manifesto")
        upload = form.get(str(definition.get("upload_key", "")))
        if not isinstance(upload, UploadFile):
            raise HTTPException(status_code=422, detail="Manifesto nao corresponde aos ficheiros")
        content = await upload.read(cfg.max_file_bytes + 1)
        await upload.close()
        if not content or len(content) > cfg.max_file_bytes:
            raise HTTPException(status_code=413, detail="Ficheiro vazio ou demasiado grande")
        if upload.content_type and not upload.content_type.startswith("image/"):
            raise HTTPException(status_code=415, detail="A API aceita apenas paginas rasterizadas")
        documents.append(
            InputDocument(
                document_id=str(definition.get("id", "")),
                name=str(definition.get("name", upload.filename or "pagina")),
                document_type=str(definition.get("type", "")),
                content=content,
            )
        )

    logger.info("extract start client=%s documents=%d", request.client.host if request.client else "", len(documents))
    result = engine().extract(documents)
    logger.info("extract end success=%s documents=%d duration_ms=%d", result["success"], len(documents), result["duration_ms"])
    return result


def main() -> None:
    import uvicorn

    uvicorn.run("pataniscai.api:app", host="0.0.0.0", port=8000, log_level="info")
