"""Génération des réponses rédigées (ADR-004 §3) — OPT-IN par configuration.

- « off » (défaut) : mode extractif — pas de LLM, l'assistant montrera les
  extraits sourcés tels quels. La recette R5 passe entièrement dans ce mode.
- « api » : rédaction par Claude (SDK officiel), sur des extraits DÉJÀ
  anonymisés (D4), avec l'obligation de ne rien affirmer hors extraits (D2).
  Tout échec (refus, réseau, quota) retombe sur le mode extractif — jamais
  d'erreur utilisateur à cause du LLM.
"""

import logging
from typing import Protocol

from .recherche import ExtraitTrouve

journal = logging.getLogger("siop_ai.generation")

CONSIGNE = """Tu es l'assistant SIOP d'une société de maintenance d'ascenseurs.
Tu réponds en français, UNIQUEMENT à partir des extraits fournis (notices et
historiques d'intervention du parc, déjà anonymisés).
Règles absolues :
- chaque affirmation porte sa citation [n] renvoyant à un extrait fourni ;
- si les extraits ne portent pas la réponse, dis-le et n'invente RIEN ;
- reste bref et opérationnel : un technicien te lit sur le terrain ;
- termine toujours par le rappel que l'humain vérifie avant d'agir."""


class Generateur(Protocol):
    def rediger(self, question: str, extraits: list[ExtraitTrouve]) -> str | None: ...


class GenerateurExtractif:
    """Le contrat de base : pas de rédaction — l'appelant montre les extraits.
    `None` signifie « pas de texte généré », jamais une erreur."""

    def rediger(self, question: str, extraits: list[ExtraitTrouve]) -> str | None:
        return None


def construire_invite(question: str, extraits: list[ExtraitTrouve]) -> str:
    """L'invite utilisateur — fonction pure, testée : la question et les
    extraits numérotés, rien d'autre (les textes sont déjà anonymisés)."""
    blocs = [
        f"[{rang}] {e.titre} · {e.locator}\n{e.content}"
        for rang, e in enumerate(extraits, start=1)
    ]
    return "Extraits du corpus :\n\n" + "\n\n".join(blocs) + f"\n\nQuestion : {question}"


class GenerateurAPI:
    """Rédaction par Claude — import différé : la dépendance `anthropic` est
    optionnelle (groupe `generation`), absente des tests/CI."""

    def __init__(self, api_key: str, modele: str) -> None:
        from anthropic import Anthropic

        self._client = Anthropic(api_key=api_key)
        self._modele = modele

    def rediger(self, question: str, extraits: list[ExtraitTrouve]) -> str | None:
        import anthropic

        if not extraits:
            return None  # sourcé ou silencieux (D2) : rien à citer = rien à rédiger
        try:
            reponse = self._client.messages.create(
                model=self._modele,
                max_tokens=2048,  # réponses courtes et sourcées, par conception
                thinking={"type": "adaptive"},
                system=CONSIGNE,
                messages=[
                    {"role": "user", "content": construire_invite(question, extraits)}
                ],
            )
            if reponse.stop_reason == "refusal":
                journal.warning("Génération refusée par le modèle — repli extractif")
                return None
            texte = "".join(b.text for b in reponse.content if b.type == "text").strip()
            return texte or None
        except anthropic.RateLimitError:
            journal.warning("Quota API atteint — repli extractif")
            return None
        except anthropic.APIStatusError as e:
            journal.warning("API génération %s — repli extractif", e.status_code)
            return None
        except anthropic.APIConnectionError:
            journal.warning("API génération injoignable — repli extractif")
            return None


def construire_generateur(mode: str, api_key: str, modele: str) -> Generateur:
    if mode == "api":
        return GenerateurAPI(api_key, modele)
    return GenerateurExtractif()
