"""Routes d'import : envoi, suivi de l'analyse, vérification humaine, application, annulation."""
from __future__ import annotations

import uuid
from typing import Any, Literal

from fastapi import APIRouter, Depends, File, Query, Request, UploadFile
from fastapi.responses import Response
from pydantic import BaseModel, Field
from sqlalchemy import select
from sqlalchemy.orm import Session

from ..core.db import get_db
from ..imports import detect, service
from ..models import ImportFile, ImportSource, User
from ..services.auth import Principal
from ..services import settings as settings_service
from .deps import ApiError, client_ip, require

router = APIRouter(prefix="/api/imports", tags=["imports"])


def _wrap(fn: Any, *args: Any, **kwargs: Any) -> Any:
    try:
        return fn(*args, **kwargs)
    except service.ImportError_ as e:
        raise ApiError(e.status, e.message, e.code, e.extra) from None


def _get(db: Session, import_id: uuid.UUID) -> ImportFile:
    imp = db.get(ImportFile, import_id)
    if imp is None:
        raise ApiError(404, "Import introuvable.", "not_found")
    return imp


class ProposalPatch(BaseModel):
    indicator_id: int | None = None
    value: int | str | None = None
    observation: str | None = Field(default=None, max_length=4000)
    details: list[str] | None = Field(default=None, max_length=60)
    decision: Literal["accepte", "ignore", "a_verifier"] | None = None


class BulkIn(BaseModel):
    decision: Literal["accepte", "ignore"]
    min_confidence: float = Field(default=0.9, ge=0, le=1)


class ApplyIn(BaseModel):
    iso_year: int = Field(ge=2000, le=2100)
    iso_week: int = Field(ge=1, le=53)


class ReanalyseIn(BaseModel):
    rotation: Literal[0, 90, 180, 270] | None = None
    contrast: bool = False


@router.get("/limits")
def limits(db: Session = Depends(get_db), p: Principal = Depends(require("import.manage"))) -> dict[str, Any]:
    from ..core.config import get_config
    return {"max_mb": settings_service.get(db, "import_max_mb"), "extensions": detect.ACCEPTED_EXTENSIONS,
            "antivirus": get_config().av_mode if get_config().clamav_socket else "indisponible"}


@router.post("")
async def upload(request: Request, file: UploadFile = File(...), db: Session = Depends(get_db),
                 p: Principal = Depends(require("import.manage"))) -> dict[str, Any]:
    max_bytes = int(settings_service.get(db, "import_max_mb")) * 1024 * 1024
    data = await file.read(max_bytes + 1)
    imp = _wrap(service.create_import, db, p.user, file.filename, data, file.content_type, client_ip(request))
    return service.import_summary(db, imp)


@router.get("")
def list_imports(limit: int = Query(default=50, ge=1, le=200), db: Session = Depends(get_db),
                 p: Principal = Depends(require("import.manage"))) -> dict[str, Any]:
    rows = list(db.execute(select(ImportFile).order_by(ImportFile.uploaded_at.desc()).limit(limit)).scalars())
    names = {u.id: u.display_name for u in db.execute(select(User)).scalars()}
    return {"imports": [service.import_summary(db, i, names) for i in rows]}


@router.get("/{import_id}")
def detail(import_id: uuid.UUID, db: Session = Depends(get_db),
           p: Principal = Depends(require("import.manage"))) -> dict[str, Any]:
    return service.import_detail(db, _get(db, import_id))


@router.get("/{import_id}/sources/{source_id}/grid")
def grid(import_id: uuid.UUID, source_id: int, db: Session = Depends(get_db),
         p: Principal = Depends(require("import.manage"))) -> dict[str, Any]:
    src = db.get(ImportSource, source_id)
    if src is None or src.import_id != import_id or src.grid is None:
        raise ApiError(404, "Feuille introuvable.", "not_found")
    return {"name": src.name, "grid": [[c.get("t", "") for c in row] for row in src.grid],
            "formulas": [[bool(c.get("f")) for c in row] for row in src.grid]}


@router.get("/{import_id}/sources/{source_id}/image")
def image(import_id: uuid.UUID, source_id: int, db: Session = Depends(get_db),
          p: Principal = Depends(require("import.manage"))) -> Response:
    """Seule la version réencodée par l'application est servie, jamais le fichier d'origine."""
    imp = _get(db, import_id)
    src = db.get(ImportSource, source_id)
    if src is None or src.import_id != imp.id or not src.image_name or imp.purged_at is not None:
        raise ApiError(404, "Image indisponible.", "not_found")
    path = (service.import_dir(imp.id) / src.image_name).resolve()
    if path.parent != service.import_dir(imp.id).resolve() or not path.is_file():
        raise ApiError(404, "Image indisponible.", "not_found")
    return Response(path.read_bytes(), media_type="image/png",
                    headers={"Cache-Control": "private, no-store", "Content-Disposition": "inline",
                             "Content-Security-Policy": "default-src 'none'; sandbox"})


@router.patch("/{import_id}/proposals/{proposal_id}")
def patch_proposal(import_id: uuid.UUID, proposal_id: int, body: ProposalPatch, request: Request,
                   db: Session = Depends(get_db), p: Principal = Depends(require("import.manage"))) -> dict[str, Any]:
    changes = body.model_dump(exclude_unset=True)
    prop = _wrap(service.update_proposal, db, _get(db, import_id), proposal_id, changes, p.user, client_ip(request))
    return service.proposal_dict(prop)


@router.post("/{import_id}/proposals/bulk")
def bulk(import_id: uuid.UUID, body: BulkIn, db: Session = Depends(get_db),
         p: Principal = Depends(require("import.manage"))) -> dict[str, Any]:
    n = _wrap(service.bulk_decide, db, _get(db, import_id), body.decision, body.min_confidence, p.user)
    return {"updated": n, **service.import_detail(db, _get(db, import_id))}


@router.post("/{import_id}/sources/{source_id}/select")
def select_source(import_id: uuid.UUID, source_id: int, db: Session = Depends(get_db),
                  p: Principal = Depends(require("import.manage"))) -> dict[str, Any]:
    _wrap(service.select_source, db, _get(db, import_id), source_id, p.user)
    return service.import_detail(db, _get(db, import_id))


@router.post("/{import_id}/reanalyse")
def reanalyse(import_id: uuid.UUID, body: ReanalyseIn, request: Request, db: Session = Depends(get_db),
              p: Principal = Depends(require("import.manage"))) -> dict[str, Any]:
    _wrap(service.reanalyse, db, _get(db, import_id), p.user, body.rotation, body.contrast, client_ip(request))
    return service.import_summary(db, _get(db, import_id))


@router.post("/{import_id}/apply")
def apply(import_id: uuid.UUID, body: ApplyIn, request: Request, db: Session = Depends(get_db),
          p: Principal = Depends(require("import.manage", "bilan.edit"))) -> dict[str, Any]:
    report = _wrap(service.apply_import, db, _get(db, import_id), body.iso_year, body.iso_week, p.user,
                   client_ip(request))
    return {"report": report, "import": service.import_summary(db, _get(db, import_id))}


@router.post("/{import_id}/cancel")
def cancel(import_id: uuid.UUID, request: Request, db: Session = Depends(get_db),
           p: Principal = Depends(require("import.manage", "bilan.edit"))) -> dict[str, Any]:
    result = _wrap(service.cancel_import, db, _get(db, import_id), p.user, client_ip(request))
    return {"result": result, "import": service.import_summary(db, _get(db, import_id))}
