"""Service IA — JAMAIS exposé publiquement (ADR-004 §4) : seule l'API NestJS
le contacte, avec le secret partagé `X-Service-Token`. Les permissions des
utilisateurs restent l'affaire de l'API — ici, un seul appelant de confiance.
"""

from contextlib import asynccontextmanager
from dataclasses import asdict

import asyncpg
from fastapi import Depends, FastAPI, Header, HTTPException, UploadFile
from pydantic import BaseModel, Field

from .assistant import repondre, suggerer_bilan
from .config import Reglages, charger_reglages
from .embeddings import construire_embeddeur
from .generation import construire_generateur
from .ingestion import reindexer_tout
from .recherche import chercher
from .transcription import construire_transcripteur


@asynccontextmanager
async def cycle_de_vie(app: FastAPI):
    reglages = charger_reglages()
    app.state.reglages = reglages
    app.state.embeddeur = construire_embeddeur(reglages.ai_embeddings)
    app.state.generateur = construire_generateur(
        reglages.ai_generation, reglages.ai_api_key, reglages.ai_model
    )
    app.state.transcripteur = construire_transcripteur(
        reglages.ai_transcription, reglages.ai_transcription_model
    )
    app.state.pool = await asyncpg.create_pool(reglages.database_url, min_size=1, max_size=5)
    yield
    await app.state.pool.close()


app = FastAPI(title="SIOP V2 — service IA (R5)", lifespan=cycle_de_vie)


def verifier_jeton(
    x_service_token: str = Header(default=""),
) -> None:
    reglages: Reglages = app.state.reglages
    if x_service_token != reglages.ai_service_token:
        raise HTTPException(status_code=401, detail="Jeton de service invalide")


@app.get("/healthz")
async def sante() -> dict:
    """Sonde interne (compose/Dokploy) — ne révèle ni corpus ni secret."""
    reglages = getattr(app.state, "reglages", None) or charger_reglages()
    return {
        "status": "ok",
        "service": "siop2-ai",
        "generation": reglages.ai_generation,  # « off » = extractif — jamais la clé
        "transcription": reglages.ai_transcription,
    }


@app.post("/internal/reindex", dependencies=[Depends(verifier_jeton)])
async def reindexer() -> dict:
    async with app.state.pool.acquire() as cnx:
        resultat = await reindexer_tout(cnx, app.state.reglages, app.state.embeddeur)
    return asdict(resultat)


class RequeteRecherche(BaseModel):
    question: str = Field(min_length=3, max_length=500)
    limite: int = Field(default=5, ge=1, le=10)


@app.post("/internal/search", dependencies=[Depends(verifier_jeton)])
async def rechercher(corps: RequeteRecherche) -> dict:
    async with app.state.pool.acquire() as cnx:
        extraits = await chercher(cnx, app.state.embeddeur, corps.question, corps.limite)
    return {"extraits": [asdict(e) for e in extraits]}


@app.post("/internal/ask", dependencies=[Depends(verifier_jeton)])
async def demander(corps: RequeteRecherche) -> dict:
    """L'assistant D2 : extraits sourcés au-dessus du seuil, ou refus honnête
    (ce qui a été cherché) — la rédaction n'existe qu'en mode génératif."""
    async with app.state.pool.acquire() as cnx:
        reponse = await repondre(
            cnx,
            app.state.embeddeur,
            app.state.generateur,
            corps.question,
            corps.limite,
            seuil_pertinence=app.state.reglages.ai_seuil_pertinence,
        )
    return {
        "mode": reponse.mode,
        "answer": reponse.answer,
        "extraits": [asdict(e) for e in reponse.extraits],
        "corpus": {
            "documents": reponse.documents_corpus,
            "bilans": reponse.bilans_corpus,
        },
    }


class RequeteSuggestion(BaseModel):
    description: str = Field(min_length=10, max_length=2000)


@app.post("/internal/suggest", dependencies=[Depends(verifier_jeton)])
async def suggerer(corps: RequeteSuggestion) -> dict:
    """Suggestion de codes de bilan (D1) : uniquement des codes EXISTANTS,
    avec confiance et « N bilans similaires » — l'humain applique, ou pas."""
    async with app.state.pool.acquire() as cnx:
        suggestions = await suggerer_bilan(
            cnx,
            app.state.embeddeur,
            corps.description,
            seuil_suggestion=app.state.reglages.ai_seuil_suggestion,
            seuil_confiance_forte=app.state.reglages.ai_seuil_confiance_forte,
        )
    return {"suggestions": [asdict(s) for s in suggestions]}


@app.post("/internal/transcrire", dependencies=[Depends(verifier_jeton)])
async def transcrire(fichier: UploadFile) -> dict:
    """Dictée opt-in (R5 D5, loi 09-08) : l'audio ne persiste JAMAIS — un
    fichier temporaire le temps de l'inférence, supprimé aussitôt (voir
    transcription.py). 503 propre si AI_TRANSCRIPTION=off (défaut)."""
    if app.state.transcripteur is None:
        raise HTTPException(status_code=503, detail="Transcription non activée (AI_TRANSCRIPTION=off)")
    audio = await fichier.read()
    extension = (fichier.filename or "audio.m4a").rsplit(".", 1)[-1]
    texte = app.state.transcripteur.transcrire(audio, extension)
    return {"texte": texte}
