"""Mémoire long-terme sémantique (Qdrant).

Stocke les tours utilisateur comme vecteurs dans la collection `stt-memory` et retrouve
les souvenirs pertinents pour les injecter dans le prompt. Embeddings via Qwen3 (llama-server
gpu-01), comme le RAG. **Tout dégrade proprement** : si Qdrant ou l'endpoint d'embedding
est injoignable, `recall` renvoie [] et `remember` ne fait rien (la mémoire court-terme
de session continue de fonctionner).

> Caveat qualité : Qwen3 n'est pas un modèle d'embedding dédié (cosinus uniformément hauts) ;
> la recherche est approximative. Voir admin/ia/rag.md pour la piste nomic-embed-text.
"""

from __future__ import annotations

import time
import uuid

import httpx

from stt_server.config import settings


class LongTermMemory:
    def __init__(self) -> None:
        self.qdrant = settings.qdrant_url.rstrip("/")
        self.collection = settings.qdrant_collection
        self.embed_url = settings.embed_url
        self.embed_model = settings.embed_model
        self.top_k = settings.memory_top_k
        self._ready = False

    async def _embed(self, client: httpx.AsyncClient, text: str) -> list[float]:
        r = await client.post(
            self.embed_url,
            json={"model": self.embed_model, "input": text},
            timeout=30,
        )
        r.raise_for_status()
        return r.json()["data"][0]["embedding"]

    async def _ensure_collection(self, client: httpx.AsyncClient, dim: int) -> None:
        if self._ready:
            return
        r = await client.get(f"{self.qdrant}/collections/{self.collection}")
        if r.status_code == 404:
            await client.put(
                f"{self.qdrant}/collections/{self.collection}",
                json={"vectors": {"size": dim, "distance": "Cosine"}},
            )
        self._ready = True

    async def recall(self, text: str) -> list[str]:
        """Souvenirs pertinents (texte) ou [] si indisponible."""
        try:
            async with httpx.AsyncClient(timeout=20) as client:
                vec = await self._embed(client, text)
                r = await client.post(
                    f"{self.qdrant}/collections/{self.collection}/points/search",
                    json={"vector": vec, "limit": self.top_k, "with_payload": True},
                )
                if r.status_code == 404:  # collection pas encore créée
                    return []
                r.raise_for_status()
                pts = r.json().get("result", [])
                return [p["payload"]["text"] for p in pts if p.get("payload", {}).get("text")]
        except Exception:  # noqa: BLE001 — dégrade silencieusement
            return []

    async def health(self) -> dict:
        """Diagnostic actif : sonde embeddings + Qdrant + collection, sans rien avaler.

        Contrairement à recall/remember (qui dégradent en silence), expose les erreurs
        pour pouvoir déboguer la mémoire long-terme sans `kubectl exec`.
        """
        out: dict = {
            "enabled": True,
            "qdrant_url": self.qdrant,
            "embed_url": self.embed_url,
            "collection": self.collection,
            "embed": {"ok": False},
            "qdrant": {"ok": False},
        }
        async with httpx.AsyncClient(timeout=20) as client:
            # 1) embeddings
            try:
                vec = await self._embed(client, "ping mémoire")
                out["embed"] = {"ok": True, "dim": len(vec)}
            except Exception as e:  # noqa: BLE001 — on veut l'erreur
                out["embed"] = {"ok": False, "error": f"{type(e).__name__}: {e}"}
            # 2) Qdrant + collection
            try:
                r = await client.get(f"{self.qdrant}/collections")
                r.raise_for_status()
                names = [c["name"] for c in r.json().get("result", {}).get("collections", [])]
                exists = self.collection in names
                qdrant: dict = {"ok": True, "collections": names, "collection_exists": exists}
                if exists:
                    cr = await client.get(f"{self.qdrant}/collections/{self.collection}")
                    if cr.status_code == 200:
                        qdrant["points_count"] = cr.json().get("result", {}).get("points_count")
                out["qdrant"] = qdrant
            except Exception as e:  # noqa: BLE001
                out["qdrant"] = {"ok": False, "error": f"{type(e).__name__}: {e}"}
        return out

    async def remember(self, session_id: str, text: str) -> None:
        try:
            async with httpx.AsyncClient(timeout=20) as client:
                vec = await self._embed(client, text)
                await self._ensure_collection(client, len(vec))
                await client.put(
                    f"{self.qdrant}/collections/{self.collection}/points?wait=true",
                    json={
                        "points": [
                            {
                                "id": str(uuid.uuid4()),
                                "vector": vec,
                                "payload": {
                                    "text": text,
                                    "session_id": session_id,
                                    "ts": time.time(),
                                },
                            }
                        ]
                    },
                )
        except Exception:  # noqa: BLE001 — dégrade silencieusement
            pass
