from pathlib import Path

from api import config


def _models_with_cfg(model_cfg=None, fallback_providers=None, custom_providers=None, active_provider=None):
    old_cfg = config.cfg
    old_mtime = config._cfg_mtime
    old_cache = config._available_models_cache
    old_cache_ts = config._available_models_cache_ts
    try:
        config._available_models_cache = None
        config._available_models_cache_ts = 0.0
        config._cfg_mtime = 0.0
        config.cfg = {
            "model": model_cfg or {"provider": "openai-codex", "default": "gpt-5.4"},
            "fallback_providers": fallback_providers or [],
            "providers": {},
        }
        if custom_providers is not None:
            config.cfg["custom_providers"] = custom_providers
        if active_provider:
            config.cfg["model"]["provider"] = active_provider
        return config.get_available_models()
    finally:
        config.cfg = old_cfg
        config._cfg_mtime = old_mtime
        config._available_models_cache = old_cache
        config._available_models_cache_ts = old_cache_ts


def test_available_models_exposes_primary_and_fallback_badges():
    result = _models_with_cfg(
        model_cfg={"provider": "openai-codex", "default": "gpt-5.4"},
        fallback_providers=[
            {"provider": "copilot", "model": "gpt-4.1"},
            {"provider": "anthropic", "model": "claude-haiku-4.5"},
        ],
    )

    badges = result.get("configured_model_badges")
    assert isinstance(badges, dict), (
        "get_available_models() deve expor configured_model_badges para o frontend "
        "marcar visualmente o dropdown com a cadeia primária + fallback configurada."
    )
    assert badges.get("@openai-codex:gpt-5.4", {}).get("role") == "primary"
    assert badges.get("@openai-codex:gpt-5.4", {}).get("label") == "Primary"
    assert badges.get("@copilot:gpt-4.1", {}).get("role") == "fallback"
    assert badges.get("@copilot:gpt-4.1", {}).get("label") == "Fallback 1"
    assert badges.get("anthropic/claude-haiku-4.5", {}).get("role") == "fallback"
    assert badges.get("anthropic/claude-haiku-4.5", {}).get("label") == "Fallback 2"


def test_duplicate_slash_id_primary_badge_sticks_to_matching_provider_only():
    import textwrap

    root = Path(__file__).resolve().parent.parent
    src = (root / "api" / "config.py").read_text(encoding="utf-8")
    start = src.index("def _build_configured_model_badges() -> dict[str, dict[str, str]]:")
    end = src.index("            return badges", start) + len("            return badges")
    fn_src = textwrap.dedent(src[start:end])

    scope = {
        "active_provider": "custom:beta",
        "default_model": "google/gemma-4-27b",
        "cfg": {"fallback_providers": []},
        "groups": [
            {"provider": "Alpha", "provider_id": "custom:alpha", "models": [{"id": "google/gemma-4-27b"}]},
            {"provider": "Beta", "provider_id": "custom:beta", "models": [{"id": "@custom:beta:google/gemma-4-27b"}]},
        ],
        "_resolve_provider_alias": lambda provider: provider,
    }
    exec(
        "def _norm_model_id(model_id):\n"
        "    s=str(model_id or '').strip().lower()\n"
        "    if s.startswith('@') and ':' in s: s=s.split(':',1)[1]\n"
        "    if '/' in s: s=s.split('/',1)[1]\n"
        "    return s.replace('-', '.')\n",
        scope,
    )
    exec(fn_src, scope)

    badges = scope["_build_configured_model_badges"]()
    assert badges.get("@custom:beta:google/gemma-4-27b", {}).get("role") == "primary"
    assert "google/gemma-4-27b" not in badges, (
        "When duplicate slash-qualified IDs are deduplicated across providers, "
        "the shared raw ID must not keep the PRIMARY badge for the wrong provider."
    )


def test_ui_badge_lookup_prefers_row_provider_for_duplicate_model_ids():
    root = Path(__file__).resolve().parent.parent
    js = (root / "static" / "ui.js").read_text(encoding="utf-8")

    assert "function _getConfiguredModelBadge(modelId,badgeMap,providerId){" in js
    assert "child.dataset&&child.dataset.provider?child.dataset.provider:''" in js
    assert "const providerMatch=matches.find(badge=>String(badge&&badge.provider||'').toLowerCase()===provider);" in js


def test_configured_model_group_label_has_i18n_key():
    """The Configured model group must not render the raw i18n key."""
    root = Path(__file__).resolve().parent.parent
    i18n = (root / "static" / "i18n.js").read_text(encoding="utf-8")

    locale_count = i18n.count("_lang:")
    key_count = i18n.count("model_group_configured:")
    assert key_count == locale_count, (
        "model_group_configured must be present in every locale block so "
        "t('model_group_configured') never falls back to the raw key."
    )


def test_get_available_models_cache_preserves_configured_model_badges(tmp_path, monkeypatch):
    cache_path = tmp_path / "models_cache.json"
    old_cfg = config.cfg
    old_mtime = config._cfg_mtime
    old_cache = config._available_models_cache
    old_cache_ts = config._available_models_cache_ts
    old_cache_path = config._models_cache_path
    try:
        monkeypatch.setattr(config, "_models_cache_path", cache_path)
        config._available_models_cache = None
        config._available_models_cache_ts = 0.0
        config._cfg_mtime = 0.0
        config.cfg = {
            "model": {"provider": "openai-codex", "default": "gpt-5.4"},
            "fallback_providers": [{"provider": "copilot", "model": "gpt-4.1"}],
            "providers": {},
        }

        cold = config.get_available_models()
        assert cold.get("configured_model_badges", {}).get("@copilot:gpt-4.1", {}).get("label") == "Fallback 1"

        config._available_models_cache = None
        config._available_models_cache_ts = 0.0
        warm = config.get_available_models()

        assert "configured_model_badges" in warm, (
            "O cache persistido de /api/models não pode descartar configured_model_badges, "
            "senão o deploy/servidor reiniciado perde as TAGS do dropdown mesmo com o código novo."
        )
        assert warm["configured_model_badges"].get("@copilot:gpt-4.1", {}).get("label") == "Fallback 1"
    finally:
        config.cfg = old_cfg
        config._cfg_mtime = old_mtime
        config._available_models_cache = old_cache
        config._available_models_cache_ts = old_cache_ts
        monkeypatch.setattr(config, "_models_cache_path", old_cache_path)



def test_ui_renders_model_badges_from_api_payload():
    root = Path(__file__).resolve().parent.parent
    js = (root / "static" / "ui.js").read_text(encoding="utf-8")
    html = (root / "static" / "index.html").read_text(encoding="utf-8")
    css = (root / "static" / "style.css").read_text(encoding="utf-8")

    assert "window._configuredModelBadges=data.configured_model_badges||{};" in js, (
        "populateModelDropdown() deve guardar configured_model_badges do /api/models "
        "para que o dropdown reflita a cadeia configurada atual."
    )
    assert "model-opt-badge" in js, (
        "renderModelDropdown() deve renderizar um badge visual por modelo quando houver "
        "metadata de primário/fallback no payload."
    )
    assert "_getConfiguredModelBadge" in js, (
        "A UI precisa de um helper de matching resiliente para religar badges mesmo quando "
        "o update do catálogo mudar prefixos/formas do model ID."
    )
    assert "model_group_configured" in js, (
        "renderModelDropdown() deve expor uma seção Configured no topo para destacar a "
        "cadeia primária + fallback antes dos providers completos."
    )
    assert "configuredRank" in js, (
        "A UI deve calcular uma prioridade estável (primary -> fallback 1 -> fallback N) "
        "para renderizar os modelos configurados no topo do dropdown."
    )
    assert "Object.entries(_badgeMap)" in js and "_normalizeConfiguredModelKey(existing.value)" in js, (
        "renderModelDropdown() deve sintetizar entradas para modelos configurados ausentes "
        "do catálogo atual, senão fallbacks locais/Ollama desaparecem da seção Configured."
    )
    # Chip-projected badge was removed in v0.50.243 (added too much width to the
    # composer chip; signal value low since the model name is right next to it).
    # Badges remain in the dropdown rows (model-opt-badge) for picker rows.
    assert 'id="composerModelBadge"' not in html, (
        "composer-model-badge chip projection was intentionally removed — "
        "do not re-add it to the composer chip."
    )
    assert "composer-model-badge" not in css, (
        "composer-model-badge CSS was intentionally removed alongside the chip span."
    )
    assert "composerModelBadge" not in js, (
        "syncModelChip() must not reference composerModelBadge — the chip-projected "
        "badge was removed because it added too much width to the composer chip."
    )
