"""Unit tests for Ollama backend helpers."""

import httpx
import pytest

from navi.exceptions import LLMConnectionError
from navi.llm.fallback import FallbackOllamaBackend, ServerEntry
from navi.llm.ollama import _base_options, _classify_error, _resolve_think


def test_classify_read_timeout_as_connection_error():
    err = _classify_error(httpx.ReadTimeout("timed out"))

    assert isinstance(err, LLMConnectionError)


def test_classify_empty_timeout_message_as_connection_error():
    err = _classify_error(httpx.ReadTimeout(""))

    assert isinstance(err, LLMConnectionError)
    assert str(err) == "ReadTimeout"


def test_fallback_client_uses_expanded_timeout(monkeypatch):
    import navi.llm.fallback as fallback_mod
    import navi.config as config_mod
    from navi.config import Settings

    captured = {}

    class FakeOllamaBackend:
        def __init__(self, **kwargs):
            captured.update(kwargs)

    monkeypatch.setattr(fallback_mod, "OllamaBackend", FakeOllamaBackend)
    monkeypatch.setattr(
        config_mod, "settings",
        Settings(
            ollama_request_timeout=30,
            llm_complete_timeout=120,
            llm_stream_first_chunk_timeout=180,
        )
    )

    backend = FallbackOllamaBackend([ServerEntry(host="http://ollama.test")])
    backend._get_client(ServerEntry(host="http://ollama.test", api_key="secret"))

    assert captured["host"] == "http://ollama.test"
    assert captured["api_key"] == "secret"
    assert captured["timeout"] == 180


def test_think_resolves_from_global_setting(monkeypatch):
    import navi.config as config_mod
    from navi.config import Settings

    monkeypatch.setattr(config_mod, "settings", Settings(ollama_think=True))

    assert _resolve_think(None) is True
    assert _resolve_think(False) is False


def test_base_options_do_not_include_think(monkeypatch):
    import navi.llm.ollama as ollama_mod
    from navi.config import Settings

    monkeypatch.setattr(ollama_mod, "settings", Settings(ollama_num_ctx=1234))

    opts = _base_options(0.2, top_k=20, top_p=0.8)

    assert opts == {
        "temperature": 0.2,
        "num_ctx": 1234,
        "top_k": 20,
        "top_p": 0.8,
    }


def test_fallback_with_single_server(monkeypatch):
    import navi.llm.fallback as fallback_mod
    import navi.config as config_mod

    captured = {}

    class FakeOllamaBackend:
        def __init__(self, **kwargs):
            captured.update(kwargs)

    monkeypatch.setattr(fallback_mod, "OllamaBackend", FakeOllamaBackend)

    backend = FallbackOllamaBackend([ServerEntry(host="http://localhost:11434", api_key="")])
    client = backend._get_client(ServerEntry(host="http://localhost:11434"))

    assert client is not None


def test_load_servers_missing_file_returns_empty(tmp_path):
    from navi.llm.fallback import load_servers_from_file

    missing = tmp_path / "no_such_file.json"
    assert load_servers_from_file(str(missing)) == []


def test_load_servers_bad_json_returns_empty(tmp_path):
    from navi.llm.fallback import load_servers_from_file

    bad = tmp_path / "bad.json"
    bad.write_text("not json", encoding="utf-8")
    assert load_servers_from_file(str(bad)) == []


def test_load_servers_missing_host_skipped(tmp_path):
    from navi.llm.fallback import load_servers_from_file, ServerEntry

    f = tmp_path / "servers.json"
    f.write_text('[{"host": "http://a"}, {"api_key": "k"}]', encoding="utf-8")
    result = load_servers_from_file(str(f))
    assert result == [ServerEntry(host="http://a", api_key="")]


def test_clear_blacklists_resets_state(monkeypatch):
    import navi.llm.fallback as fallback_mod

    fallback_mod._dead_servers["http://dead"] = 0.0
    fallback_mod._dead_models[("http://dead", "model")] = 0.0

    fallback_mod.clear_blacklists()

    assert fallback_mod._dead_servers == {}
    assert fallback_mod._dead_models == {}


def test_single_server_not_blacklisted_on_connection_error(monkeypatch):
    import navi.llm.fallback as fallback_mod

    fallback_mod.clear_blacklists()

    class FailingOllamaBackend:
        def __init__(self, **kwargs):
            pass

        async def complete(self, *args, **kwargs):
            raise fallback_mod.LLMConnectionError("connection refused")

    monkeypatch.setattr(fallback_mod, "OllamaBackend", FailingOllamaBackend)

    backend = FallbackOllamaBackend([ServerEntry(host="http://localhost:11434")])

    import asyncio
    with pytest.raises(fallback_mod.LLMBackendError):
        asyncio.run(backend.complete([]))

    # The single server must NOT be blacklisted so the next request retries immediately
    assert fallback_mod._dead_servers == {}
    assert fallback_mod._dead_models == {}


def test_multi_server_blacklists_dead_server(monkeypatch):
    import navi.llm.fallback as fallback_mod

    fallback_mod.clear_blacklists()

    class FailingOllamaBackend:
        def __init__(self, **kwargs):
            pass

        async def complete(self, *args, **kwargs):
            raise fallback_mod.LLMConnectionError("connection refused")

    monkeypatch.setattr(fallback_mod, "OllamaBackend", FailingOllamaBackend)

    backend = FallbackOllamaBackend([
        ServerEntry(host="http://dead:11434"),
        ServerEntry(host="http://alive:11434"),
    ])

    import asyncio
    with pytest.raises(fallback_mod.LLMBackendError):
        asyncio.run(backend.complete([]))

    # Dead server should be blacklisted; alive server was never reached because
    # FallbackOllamaBackend breaks after the first server fails.
    # (Both failed, so both are blacklisted if they each raised LLMConnectionError.)
    assert "http://dead:11434" in fallback_mod._dead_servers
    assert "http://alive:11434" in fallback_mod._dead_servers


def test_single_server_model_not_blacklisted_on_model_not_found(monkeypatch):
    import navi.llm.fallback as fallback_mod

    fallback_mod.clear_blacklists()

    class FailingOllamaBackend:
        def __init__(self, **kwargs):
            pass

        async def complete(self, *args, **kwargs):
            raise fallback_mod.LLMModelNotFoundError("model missing")

    monkeypatch.setattr(fallback_mod, "OllamaBackend", FailingOllamaBackend)

    backend = FallbackOllamaBackend([ServerEntry(host="http://localhost:11434")])

    import asyncio
    with pytest.raises(fallback_mod.LLMBackendError):
        asyncio.run(backend.complete([]))

    # Single server should not blacklist the model either
    assert fallback_mod._dead_models == {}


def test_discover_backends_empty_file_fallback_to_host(monkeypatch):
    import navi.core.registry as registry_mod
    import navi.config as config_mod
    from navi.config import Settings

    monkeypatch.setattr(
        config_mod, "settings",
        Settings(
            ollama_host="http://fallback-ollama:11434",
            ollama_backends_file="/nonexistent/backends.json",
        )
    )

    backends = registry_mod._discover_backends()
    keys = [k for k, _ in backends]

    assert "ollama" in keys
