"""Busca modelos / LoRAs en Civitai via su API publica GET /api/v1/models.

Normaliza cada resultado a un dict pequeno y reutilizable
({name, type, base_model, version_id, download_url, nsfw}) tomando la primera
version del modelo. La busqueda publica funciona sin token; pasar un token solo
sube el rate limit y desbloquea modelos restringidos.

Impura: red (HTTP GET a civitai.com). Solo stdlib (urllib, json).
"""
import json
import urllib.error
import urllib.parse
import urllib.request

_API = "https://civitai.com/api/v1/models"
_TIMEOUT = 30.0


def comfyui_search_civitai_models(
    query: str,
    *,
    types: str = "Checkpoint",
    base_model: str | None = None,
    sort: str = "Highest Rated",
    limit: int = 20,
    token: str | None = None,
) -> dict:
    """Busca modelos en Civitai y devuelve resultados normalizados.

    Args:
        query: texto de busqueda (nombre del modelo, ej. "dreamshaper").
        types: tipo(s) de modelo, CSV. Valores Civitai: Checkpoint, LORA,
            TextualInversion, Controlnet, VAE, Upscaler, ... keyword-only.
        base_model: filtra por modelo base (ej. "SD 1.5", "SDXL 1.0"). None no
            filtra. keyword-only.
        sort: orden Civitai ("Highest Rated", "Most Downloaded", "Newest").
            keyword-only.
        limit: numero maximo de resultados (1-100). keyword-only.
        token: API token de Civitai (header Authorization Bearer). Opcional: la
            busqueda publica funciona sin el. No hardcodear: pasar desde
            pass/vault. keyword-only.

    Returns:
        dict {ok, items, count, error}. items es una lista de
        {name, type, base_model, version_id, download_url, nsfw} (la primera
        version de cada modelo). ok=False con error si la peticion falla; una
        busqueda sin resultados devuelve ok=True con items=[] (no es error).
    """
    params = [
        ("query", query),
        ("limit", str(max(1, min(int(limit), 100)))),
        ("sort", sort),
    ]
    for t in str(types).split(","):
        t = t.strip()
        if t:
            params.append(("types", t))
    if base_model:
        for bm in (base_model if isinstance(base_model, (list, tuple)) else [base_model]):
            params.append(("baseModels", str(bm)))

    url = f"{_API}?{urllib.parse.urlencode(params)}"
    headers = {"User-Agent": "fn-registry/comfyui_search_civitai_models"}
    if token:
        headers["Authorization"] = f"Bearer {token}"

    try:
        req = urllib.request.Request(url, headers=headers)
        with urllib.request.urlopen(req, timeout=_TIMEOUT) as resp:
            data = json.loads(resp.read())
    except urllib.error.HTTPError as exc:
        body = exc.read().decode(errors="replace")[:300]
        return {"ok": False, "items": [], "count": 0,
                "error": f"HTTP {exc.code} en {url}: {body}"}
    except urllib.error.URLError as exc:
        return {"ok": False, "items": [], "count": 0,
                "error": f"no se pudo conectar a civitai.com: {exc.reason}"}
    except json.JSONDecodeError as exc:
        return {"ok": False, "items": [], "count": 0,
                "error": f"respuesta no es JSON valido: {exc}"}

    items = []
    for model in data.get("items", []) or []:
        versions = model.get("modelVersions") or []
        v0 = versions[0] if versions else {}
        items.append({
            "name": model.get("name"),
            "type": model.get("type"),
            "base_model": v0.get("baseModel"),
            "version_id": v0.get("id"),
            "download_url": v0.get("downloadUrl"),
            "nsfw": bool(model.get("nsfw", False)),
        })
    return {"ok": True, "items": items, "count": len(items), "error": ""}


if __name__ == "__main__":
    out = comfyui_search_civitai_models("dreamshaper", limit=5)
    print(out["ok"], out["count"])
    for it in out["items"]:
        print(f"  {it['name']} [{it['base_model']}] v{it['version_id']} -> {it['download_url']}")
