"""Scan automatique des modèles auprès de chaque fournisseur, et écriture d'un
cache JSON (`app/data/catalog_scan.json`) exploité par le catalogue dynamique.

Usage (cron O2Switch, ex. toutes les 6 h) :
    /opt/alt/python311/bin/python3.11 app/catalog_scan.py

Résultat :
    {provider_id: {"free": [ids...], "paid": [ids...]}, ...}

Est stratégiquement conservateur : il INTERROGE les endpoints publics /models et
ne fait qu'ENRICHIR le registre curé à la main (app/providers.py). Une catégorie
"auto" sépare les modèles trouvés par le scan de ceux listés manuellement, pour
éviter qu'un scan raté ne casse les modèles de confiance.
"""
import json
import os
import sys
import urllib.request
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional

DATA_DIR = Path(__file__).resolve().parent / "data"
DATA_DIR.mkdir(parents=True, exist_ok=True)
OUT = DATA_DIR / "catalog_scan.json"

TIMEOUT = 20


def _get_json(url: str, headers: Optional[dict] = None, timeout: int = TIMEOUT) -> dict:
    req = urllib.request.Request(url, headers={"User-Agent": "oai-console-scan/1.0", **(headers or {})})
    with urllib.request.urlopen(req, timeout=timeout) as r:
        return json.load(r)


def _models_openai_compat(base: str, key: Optional[str]) -> dict:
    """Liste /models style OpenAI -> {display : id}.Filtre très grossier, ne garde que les LLM de chat."""
    try:
        headers = {"Authorization": f"Bearer {key}"} if key else {}
        data = _get_json(base.rstrip("/") + "/models", headers=headers)
    except Exception as e:
        return {"error": str(e)}
    out = {}
    skip = ("whisper", "tts", "transcription", "embed", "guard", "rerank", "audio", "-tts", "stt-")
    for m in data.get("data", []):
        mid = m.get("id", "")
        if any(s in mid.lower() for s in skip):
            continue
        nice = mid.split("/")[-1]
        out[mid] = nice
    return out if out else {"error": "aucun modèle de chat détecté"}


def _models_gemini(key: Optional[str]) -> dict:
    url = "https://generativelanguage.googleapis.com/v1beta/models"
    if key:
        url += f"?key={key}"
    try:
        data = _get_json(url)
    except Exception as e:
        return {"error": str(e)}
    out = {}
    for m in data.get("models", []):
        name = m.get("name", "").replace("models/", "")
        if "generateContent" not in m.get("supportedGenerationMethods", []):
            continue
        out[name] = name
    return out


def _filter_free_openrouter(models: dict) -> tuple:
    free, paid = [], []
    for mid, nice in models.items():
        if mid.endswith(":free"):
            free.append(mid)
        else:
            paid.append(mid)
    return free, paid


def run() -> dict:
    res: dict = {"generated_at": datetime.now(timezone.utc).isoformat(), "providers": {}}

    # OpenRouter : gratuit vs payant par suffixe :free
    or_models = _models_openai_compat("https://openrouter.ai/api/v1", None)
    if "error" not in or_models:
        free, paid = _filter_free_openrouter(or_models)
        res["providers"]["openrouter"] = {"free": free, "paid": paid}
    else:
        res["providers"]["openrouter"] = {"error": or_models["error"]}

    # Groq : catalogue actuel (modèles chat)
    gq = _models_openai_compat("https://api.groq.com/openai/v1", os.getenv("GROQ_API_KEY"))
    if "error" not in gq:
        res["providers"]["groq"] = {"free": [], "paid": sorted(gq)}
    else:
        res["providers"]["groq"] = {"error": gq["error"]}

    # Gemini
    gm = _models_gemini(os.getenv("GEMINI_API_KEY"))
    if "error" not in gm:
        res["providers"]["gemini"] = {"free": [], "paid": sorted(gm)}
    else:
        res["providers"]["gemini"] = {"error": gm["error"]}

    # NVIDIA NIM
    nv = _models_openai_compat("https://integrate.api.nvidia.com/v1", os.getenv("NVIDIA_API_KEY"))
    if "error" not in nv:
        res["providers"]["nvidia"] = {"free": [], "paid": sorted(nv)}
    else:
        res["providers"]["nvidia"] = {"error": nv["error"]}

    # Mistral
    mi = _models_openai_compat("https://api.mistral.ai/v1", os.getenv("MISTRAL_API_KEY"))
    if "error" not in mi:
        res["providers"]["mistral"] = {"free": [], "paid": sorted(mi)}
    else:
        res["providers"]["mistral"] = {"error": mi["error"]}

    # Cohere (v2 utilise GET /v2/models)
    try:
        data = _get_json("https://api.cohere.com/v2/models",
                         headers={"Authorization": f"Bearer {os.getenv('COHERE_API_KEY','')}"})
        co = {m.get("name", ""): m.get("name", "") for m in data.get("models", []) if m.get("name")}
        res["providers"]["cohere"] = {"free": [], "paid": sorted(co)}
    except Exception as e:
        res["providers"]["cohere"] = {"error": str(e)}

    # HuggingFace
    hf = _models_openai_compat("https://router.huggingface.co/v1", os.getenv("HF_API_KEY"))
    if "error" not in hf:
        res["providers"]["huggingface"] = {"free": [], "paid": sorted(hf)}
    else:
        res["providers"]["huggingface"] = {"error": hf["error"]}

    OUT.write_text(json.dumps(res, indent=2, ensure_ascii=False))
    print(f"[scan] {len(res['providers'])} fournisseurs scannés -> {OUT}")
    return res


if __name__ == "__main__":
    try:
        run()
        sys.exit(0)
    except Exception as e:
        print(f"[scan] ERREUR globale : {e}", file=sys.stderr)
        sys.exit(1)