"""RAG documentaire — ancre les réponses dans la doc Funk (collection `funk-docs`).

Recherche sémantique dans Qdrant pour injecter des passages de la documentation `admin/`
dans le prompt → le cerveau répond à partir de la **vraie doc du homelab** plutôt que de
connaissances générales (sinon il hallucine : « stockage décentralisé », « worker nodes »…).

Même embedder que la mémoire long-terme (`nomic-embed-text`, gpu-01 `:1238`, dim 768) → le
vecteur de requête déjà calculé par `LongTermMemory.recall` est **réutilisé** ici (pas de 2ᵉ
embed). Dégrade proprement : si Qdrant/embeddings sont injoignables, renvoie `[]` et le cerveau
répond sans contexte doc plutôt que d'échouer.
"""

from __future__ import annotations

import httpx

from stt_server.config import settings


class Knowledge:
    def __init__(self) -> None:
        self.qdrant = settings.qdrant_url.rstrip("/")
        self.collection = settings.docs_collection
        self.embed_url = settings.embed_url
        self.embed_model = settings.embed_model
        self.top_k = settings.docs_top_k
        self.min_score = settings.docs_min_score
        self.timeout = settings.docs_timeout
        # Client persistant : pooling + keep-alive (évite un handshake TCP par appel).
        self._http: httpx.AsyncClient | None = None

    def _client(self) -> httpx.AsyncClient:
        if self._http is None or self._http.is_closed:
            self._http = httpx.AsyncClient()
        return self._http

    async def aclose(self) -> None:
        if self._http is not None and not self._http.is_closed:
            await self._http.aclose()

    async def search(self, text: str, vec: list[float] | None = None) -> list[str]:
        """Passages de doc pertinents pour `text`.

        `vec` : embedding nomic de la requête, réutilisé depuis le recall mémoire (même
        modèle/dim) → évite un 2ᵉ embed. Absent (mémoire off / recall échoué) → on embed ici.
        Timeout serré, dégrade en `[]` (jamais d'exception sur le chemin de réponse).
        """
        try:
            client = self._client()
            if vec is None:
                r = await client.post(
                    self.embed_url,
                    json={"model": self.embed_model, "input": text},
                    timeout=self.timeout,
                )
                r.raise_for_status()
                vec = r.json()["data"][0]["embedding"]
            r = await client.post(
                f"{self.qdrant}/collections/{self.collection}/points/search",
                json={
                    "vector": vec,
                    "limit": self.top_k,
                    "with_payload": True,
                    "score_threshold": self.min_score,
                },
                timeout=self.timeout,
            )
            if r.status_code == 404:  # collection absente (RAG pas ingéré) → pas de contexte
                return []
            r.raise_for_status()
            out: list[str] = []
            for p in r.json().get("result", []):
                payload = p.get("payload", {})
                txt = payload.get("text")
                if not txt:
                    continue
                src = payload.get("file", "")
                out.append(f"[{src}] {txt}" if src else txt)
            return out
        except Exception:  # noqa: BLE001 — dégrade silencieusement (réponse sans doc)
            return []
