"""
models.py - Fetch the available OpenRouter model list with pricing (cached).

The /models endpoint is public (no API key needed). Results are normalized to
per-1M-token USD costs for display and cached in memory for an hour; callers
degrade gracefully when OpenRouter is unreachable. Each entry also carries a
vision flag (accepts image input) so the UI can offer separate vision/text
model dropdowns.
"""

import time
from typing import List, Optional, Tuple

import httpx

from backend import config

_CACHE_TTL_SECONDS = 3600
_cache = {"at": 0.0, "models": None}


def _per_mtok(rate) -> float:
    """OpenRouter pricing is USD per token (as a string); display is per 1M."""
    try:
        return round(float(rate) * 1_000_000, 4)
    except (TypeError, ValueError):
        return 0.0


def _is_vision(item: dict) -> bool:
    """True when the model accepts image input and produces text output."""
    arch = item.get("architecture") or {}
    inputs = arch.get("input_modalities") or []
    outputs = arch.get("output_modalities") or []
    # Legacy string form: "text+image->text"
    modality = (arch.get("modality") or "").lower()
    has_image_in = ("image" in inputs) or ("image" in modality.split("->")[0])
    has_text_out = ("text" in outputs) or ("->text" in modality) or (not outputs and not modality)
    return has_image_in and has_text_out


def _fetch_openrouter_models() -> Optional[List[dict]]:
    """Raw GET of the OpenRouter model list; None on any failure."""
    try:
        response = httpx.get(f"{config.AI_BASE_URL.rstrip('/')}/models", timeout=10)
        response.raise_for_status()
        data = response.json().get("data")
        return data if isinstance(data, list) else None
    except Exception as e:
        print(f"[Models] OpenRouter /models fetch failed: {e}")
        return None


def fetch_models(force: bool = False) -> Optional[List[dict]]:
    """Normalized model list for the UI picker, or None when unavailable."""
    if (
        not force
        and _cache["models"] is not None
        and time.time() - _cache["at"] < _CACHE_TTL_SECONDS
    ):
        return _cache["models"]
    data = _fetch_openrouter_models()
    if data is None:
        return None
    models = [
        {
            "id": item.get("id") or "",
            "name": item.get("name") or item.get("id") or "",
            "prompt_usd_per_mtok": _per_mtok((item.get("pricing") or {}).get("prompt")),
            "completion_usd_per_mtok": _per_mtok((item.get("pricing") or {}).get("completion")),
            "context_length": item.get("context_length"),
            "vision": _is_vision(item),
        }
        for item in data
        if item.get("id")
    ]
    models.sort(key=lambda m: m["id"])
    _cache["models"] = models
    _cache["at"] = time.time()
    return models


def split_vision_text(models: List[dict]) -> Tuple[List[dict], List[dict]]:
    """Partition the normalized catalog into (vision, text) lists for the UI.

    Every catalog model takes text in/out, so vision models appear in both
    lists (same dicts, pricing included).
    """
    vision = [m for m in models if m.get("vision")]
    return vision, list(models)
