"""A local selection must survive session.info, a new chat and stored-session resume."""
from types import SimpleNamespace

import pytest

from hermes_cli import runtime_provider as rp
from hermes_cli.local_runtime import endpoint
from tui_gateway import server


@pytest.fixture
def local_route(tmp_path, monkeypatch):
    cfg = {"model": {"provider": "anthropic", "default": "claude-test"},
           "local_runtime": {"enabled": True}}
    monkeypatch.setattr(rp, "load_config", lambda: cfg)
    monkeypatch.setattr(rp, "_get_model_config", lambda: cfg["model"])
    monkeypatch.setattr("hermes_cli.config.load_config", lambda: cfg)
    monkeypatch.setattr(server, "_load_cfg", lambda: cfg)
    route = {"base_url": "http://127.0.0.1:18434/v1", "api_key": "local-test-key"}
    monkeypatch.setattr(endpoint, "_state_endpoint", lambda: route)
    monkeypatch.setattr(endpoint, "resolve_llamacpp_endpoint", lambda **kw: route)
    monkeypatch.setattr(server, "_probe_credentials", lambda agent: None)
    monkeypatch.setattr("hermes_cli.banner.get_update_result", lambda **kw: None)
    monkeypatch.setattr("hermes_cli.banner.get_available_skills", dict)
    return route, {"cwd": str(tmp_path), "session_key": "local-identity"}


def test_live_local_identity_survives_new_chat_and_resume(local_route):
    route, session = local_route
    model = "Local.Model-Q4_K_M"
    runtime = rp.resolve_runtime_provider(requested="llamacpp", target_model=model)
    agent = SimpleNamespace(model=model, provider=runtime["provider"], base_url=runtime["base_url"],
                            api_mode=runtime["api_mode"], reasoning_config=None, service_tier=None,
                            session_id=session["session_key"])
    # The renderer carries these two fields into the next session.create.
    info = server._session_info(agent, session)
    assert info["provider"] == "llamacpp"
    assert info["model"] == model
    next_model, next_runtime = server._resolve_agent_model_runtime(
        {"model": info["model"], "provider": info["provider"]}, None)
    assert next_model == model and next_runtime["base_url"] == route["base_url"]
    assert next_runtime["api_key"] == route["api_key"]
    persisted = server._runtime_model_config(agent)
    assert persisted["provider"] == "llamacpp"
    assert "api_key" not in persisted
    # Legacy rows kept the local endpoint but lost the provider slug.
    for provider in ("custom", "llamacpp"):
        row = {"model": model, "model_config": {**persisted, "provider": provider}}
        overrides = server._stored_session_runtime_overrides(row)
        restored_model, restored = server._resolve_agent_model_runtime(
            overrides["model_override"], overrides.get("provider_override"))
        assert restored_model == model
        assert restored["base_url"] == route["base_url"]
        assert restored["api_key"] == route["api_key"]
    # Pending picks and compute-host mirrors still own the reported identity.
    session["pending_model_switch"] = {"display_model": "claude-test", "display_provider": "anthropic"}
    assert server._session_info(agent, session)["provider"] == "anthropic"


def test_resume_follows_live_managed_port_not_snapshot(local_route):
    """A llamacpp session that stored last boot's loopback URL must follow the
    live supervisor after the managed server moves to another port."""
    route, _session = local_route
    model = "Local.Model-Q4_K_M"
    stale = "http://127.0.0.1:51489/v1"
    row = {"model": model, "model_config": {
        "model": model, "provider": "llamacpp", "base_url": stale,
        "api_mode": "chat_completions"}}
    overrides = server._stored_session_runtime_overrides(row)
    restored_model, restored = server._resolve_agent_model_runtime(
        overrides["model_override"], overrides.get("provider_override"))
    assert restored_model == model
    assert restored["base_url"] == route["base_url"]
    assert restored["base_url"] != stale
    assert restored["api_key"] == route["api_key"]


def test_session_info_recovers_identity_from_the_owning_profile(tmp_path, monkeypatch):
    import json
    from pathlib import Path

    from hermes_constants import get_hermes_home

    launch = tmp_path / "launch"
    secondary = launch / "profiles" / "secondary"
    secondary.mkdir(parents=True)
    url = "https://session-endpoint.invalid/v1"
    for home, name in ((launch, "launch-route"), (secondary, "secondary-route")):
        config = {"model": {"provider": "anthropic", "default": "claude-test"},
                  "providers": {name: {"api": url, "models": ["same-model"]}}}
        (home / "config.yaml").write_text(json.dumps(config), encoding="utf-8")
    monkeypatch.setenv("HERMES_HOME", str(launch))
    monkeypatch.setattr(Path, "home", lambda: tmp_path)
    monkeypatch.setattr(server, "_hermes_home", launch)
    monkeypatch.setattr(server, "_probe_credentials", lambda agent: None)
    monkeypatch.setattr("hermes_cli.banner.get_update_result", lambda **kw: None)
    monkeypatch.setattr("hermes_cli.banner.get_available_skills", dict)
    agent = SimpleNamespace(model="same-model", provider="custom", base_url=url,
                            reasoning_config=None, service_tier=None, session_id="profile-identity")
    session = {"cwd": str(tmp_path), "session_key": "profile-identity", "profile_home": str(secondary)}
    # Broadcast/resume can publish metadata outside the session's profile scope.
    assert server._session_info(agent, session)["provider"] == "custom:secondary-route"
    assert get_hermes_home() == launch
    # A launch-profile session must also ignore an ambient secondary-profile scope.
    with server._profile_build_scope(secondary):
        assert server._session_info(agent, {**session, "profile_home": None})["provider"] == "custom:launch-route"
        assert get_hermes_home() == secondary
    assert get_hermes_home() == launch
    # Remote compute metadata remains authoritative; never reinterpret it using local profiles.
    session["_metadata_mirror"] = {"model": "remote-model", "provider": "custom:remote-route"}
    assert server._session_info(agent, session)["provider"] == "custom:remote-route"


def test_local_identity_never_claims_an_unrelated_endpoint(local_route):
    route, _ = local_route
    assert rp.canonical_custom_identity(base_url=route["base_url"]) == "llamacpp"
    assert rp.canonical_custom_identity(base_url="http://127.0.0.1:18435/v1") is None
    assert rp.canonical_custom_identity(base_url="https://api.anthropic.com") is None
    assert rp.canonical_custom_identity(model="Local.Model-Q4_K_M") is None


def test_resume_recovers_a_local_chat_that_recorded_only_its_billing_endpoint(local_route):
    """A chat that started on the profile's local default never ran /model, so its row holds only
    the bare "custom" billing class and the endpoint that served it. Resume routes it back to the
    managed server instead of pairing the local model with the profile's default provider."""
    route, _session = local_route
    model = "Local.Model-Q4_K_M"
    row = {"model": model, "model_config": {"max_iterations": 60},
           "billing_provider": "custom", "billing_base_url": route["base_url"]}

    overrides = server._stored_session_runtime_overrides(row)
    restored_model, restored = server._resolve_agent_model_runtime(
        overrides["model_override"], overrides.get("provider_override"))

    assert overrides.get("provider_override") == "llamacpp"
    assert restored_model == model
    assert restored["base_url"] == route["base_url"]


GLM_OLD, GLM_NEW = "http://10.0.0.5:8000/v1", "http://10.0.0.6:8000/v1"


def _billing_only_row(base_url):
    return {"model": "GLM-5.3", "model_config": {"max_iterations": 60, "api_mode": "chat_completions"},
            "billing_provider": "custom", "billing_base_url": base_url}


def _custom_config(monkeypatch, *entries):
    config = {"model": {"provider": "anthropic", "default": "claude-opus-5-5"}, "custom_providers": list(entries)}
    monkeypatch.setattr(rp, "load_config", lambda: config)
    monkeypatch.setattr(rp, "_get_model_config", lambda: config["model"])


def test_resume_follows_a_custom_entry_that_moved_since_the_chat(monkeypatch):
    _custom_config(monkeypatch, {"name": "glm", "base_url": GLM_NEW, "api_key": "glm-key", "model": "GLM-5.3"})

    overrides = server._stored_session_runtime_overrides(_billing_only_row(GLM_OLD))

    assert overrides["provider_override"] == "custom:glm"
    assert overrides["model_override"]["base_url"] is None


def test_resume_never_sends_the_default_provider_to_an_endpoint_no_entry_serves(monkeypatch):
    """The stored endpoint belonged to a route that no longer exists. Pinning it on the profile's
    default provider would send that provider's credentials to it."""
    _custom_config(monkeypatch)

    overrides = server._stored_session_runtime_overrides(_billing_only_row(GLM_OLD))

    assert "provider_override" not in overrides
    assert overrides["model_override"]["base_url"] is None
    assert overrides["model_override"]["api_mode"] is None
