"""Tests for Ollama discovery and keyless provider support."""

from __future__ import annotations

import json
import os
from unittest.mock import MagicMock, patch

import pytest
from icarus.config.model_config import (
    KEYLESS_PROVIDERS,
    _has_env_credentials,
    clear_caches,
    get_available_models,
    get_provider_registry,
    has_provider_credentials,
)
from icarus.config.ollama import (
    discover_ollama_models,
    get_ollama_base_url,
    is_ollama_available,
)

# All tests that call into model_config functions need the config cache cleared
# and the ModelConfig.load patched to avoid reading the real config file.

def _fresh_empty_config():
    """Return a fresh MagicMock that looks like an empty ModelConfig."""
    return MagicMock(
        default_model=None,
        recent_model=None,
        providers={},
        get_base_url=MagicMock(return_value=None),
        get_api_key_env=MagicMock(return_value=None),
        has_credentials=MagicMock(return_value=None),
    )


# ---------------------------------------------------------------------------
# KEYLESS_PROVIDERS constant
# ---------------------------------------------------------------------------


class TestKeylessProviders:
    def test_ollama_in_keyless(self):
        assert "ollama" in KEYLESS_PROVIDERS

    def test_frozenset_immutable(self):
        with pytest.raises(AttributeError):
            KEYLESS_PROVIDERS.add("test")  # type: ignore[attr-defined]


# ---------------------------------------------------------------------------
# _has_env_credentials
# ---------------------------------------------------------------------------


class TestHasEnvCredentials:
    def test_keyless_provider_returns_true(self):
        assert _has_env_credentials("ollama") is True

    def test_unknown_provider_returns_false(self):
        assert _has_env_credentials("nonexistent_provider") is False

    def test_cloud_provider_without_key_returns_false(self):
        with patch.dict(os.environ, {}, clear=True):
            clear_caches()
            assert _has_env_credentials("openai") is False


# ---------------------------------------------------------------------------
# has_provider_credentials
# ---------------------------------------------------------------------------


class TestHasProviderCredentials:
    def setup_method(self):
        clear_caches()

    def test_keyless_returns_true(self):
        assert has_provider_credentials("ollama") is True

    def test_cloud_without_key_returns_false(self):
        with patch.dict(os.environ, {}, clear=True):
            clear_caches()
            assert has_provider_credentials("openai") is False

    def test_cloud_with_key_returns_true(self):
        with patch.dict(os.environ, {"OPENAI_API_KEY": "sk-test"}, clear=False):
            clear_caches()
            assert has_provider_credentials("openai") is True


# ---------------------------------------------------------------------------
# is_ollama_available
# ---------------------------------------------------------------------------


class TestIsOllamaAvailable:
    def test_returns_true_on_200(self):
        mock_resp = MagicMock()
        mock_resp.status = 200
        mock_resp.__enter__ = lambda s: s
        mock_resp.__exit__ = MagicMock(return_value=False)

        with patch("icarus.config.ollama.urllib.request.urlopen", return_value=mock_resp):
            assert is_ollama_available("http://localhost:11434") is True

    def test_returns_false_on_connection_error(self):
        with patch(
            "icarus.config.ollama.urllib.request.urlopen",
            side_effect=ConnectionRefusedError,
        ):
            assert is_ollama_available("http://localhost:11434") is False

    def test_returns_false_on_timeout(self):
        with patch(
            "icarus.config.ollama.urllib.request.urlopen",
            side_effect=TimeoutError,
        ):
            assert is_ollama_available("http://localhost:11434") is False


# ---------------------------------------------------------------------------
# discover_ollama_models
# ---------------------------------------------------------------------------


_MOCK_TAGS_RESPONSE = {
    "models": [
        {
            "name": "llama3:latest",
            "size": 4_700_000_000,
            "details": {
                "parameter_size": "8B",
                "quantization_level": "Q4_0",
            },
        },
        {
            "name": "mistral:latest",
            "size": 4_100_000_000,
            "details": {
                "parameter_size": "7B",
                "quantization_level": "Q4_K_M",
            },
        },
    ]
}


class TestDiscoverOllamaModels:
    def _mock_urlopen(self, data: dict):
        mock_resp = MagicMock()
        mock_resp.read.return_value = json.dumps(data).encode("utf-8")
        mock_resp.__enter__ = lambda s: s
        mock_resp.__exit__ = MagicMock(return_value=False)
        return mock_resp

    def test_returns_model_list(self):
        with patch(
            "icarus.config.ollama.urllib.request.urlopen",
            return_value=self._mock_urlopen(_MOCK_TAGS_RESPONSE),
        ):
            models = discover_ollama_models("http://localhost:11434")

        assert len(models) == 2
        assert models[0]["name"] == "llama3:latest"
        assert models[0]["parameter_size"] == "8B"
        assert models[1]["name"] == "mistral:latest"
        assert models[1]["quantization_level"] == "Q4_K_M"

    def test_returns_empty_on_error(self):
        with patch(
            "icarus.config.ollama.urllib.request.urlopen",
            side_effect=ConnectionRefusedError,
        ):
            models = discover_ollama_models("http://localhost:11434")
        assert models == []

    def test_returns_empty_on_bad_json(self):
        mock_resp = MagicMock()
        mock_resp.read.return_value = b"not json"
        mock_resp.__enter__ = lambda s: s
        mock_resp.__exit__ = MagicMock(return_value=False)

        with patch(
            "icarus.config.ollama.urllib.request.urlopen",
            return_value=mock_resp,
        ):
            models = discover_ollama_models("http://localhost:11434")
        assert models == []

    def test_returns_empty_on_missing_models_key(self):
        with patch(
            "icarus.config.ollama.urllib.request.urlopen",
            return_value=self._mock_urlopen({"something_else": []}),
        ):
            models = discover_ollama_models("http://localhost:11434")
        assert models == []


# ---------------------------------------------------------------------------
# get_ollama_base_url
# ---------------------------------------------------------------------------


class TestGetOllamaBaseUrl:
    def setup_method(self):
        clear_caches()

    def test_default_url(self):
        with (
            patch.dict(os.environ, {}, clear=True),
            patch(
                "icarus.config.model_config.ModelConfig.load",
                return_value=_fresh_empty_config(),
            ),
        ):
            assert get_ollama_base_url() == "http://localhost:11434"

    def test_env_var_override(self):
        with (
            patch.dict(
                os.environ, {"OLLAMA_HOST": "http://remote:11434"}, clear=True,
            ),
            patch(
                "icarus.config.model_config.ModelConfig.load",
                return_value=_fresh_empty_config(),
            ),
        ):
            assert get_ollama_base_url() == "http://remote:11434"

    def test_config_takes_priority(self):
        config_with_url = _fresh_empty_config()
        config_with_url.get_base_url.return_value = "http://config:11434"
        with (
            patch.dict(
                os.environ, {"OLLAMA_HOST": "http://env:11434"}, clear=True,
            ),
            patch(
                "icarus.config.model_config.ModelConfig.load",
                return_value=config_with_url,
            ),
        ):
            assert get_ollama_base_url() == "http://config:11434"

    def test_trailing_slash_stripped(self):
        with (
            patch.dict(
                os.environ, {"OLLAMA_HOST": "http://remote:11434/"}, clear=True,
            ),
            patch(
                "icarus.config.model_config.ModelConfig.load",
                return_value=_fresh_empty_config(),
            ),
        ):
            assert get_ollama_base_url() == "http://remote:11434"


# ---------------------------------------------------------------------------
# get_available_models — Ollama integration
# ---------------------------------------------------------------------------


class TestGetAvailableModelsOllama:
    def setup_method(self):
        clear_caches()

    def test_includes_ollama_when_discovered(self):
        clear_caches()
        with (
            patch(
                "icarus.config.model_config.ModelConfig.load",
                return_value=_fresh_empty_config(),
            ),
            patch(
                "icarus.config.ollama.discover_ollama_models",
                return_value=[
                    {"name": "llama3:latest"},
                    {"name": "mistral:latest"},
                ],
            ),
            patch(
                "icarus.config.ollama.get_ollama_base_url",
                return_value="http://localhost:11434",
            ),
        ):
            models = get_available_models()

        assert "ollama" in models
        assert "llama3:latest" in models["ollama"]
        assert "mistral:latest" in models["ollama"]

    def test_no_ollama_when_not_running(self):
        clear_caches()
        with (
            patch(
                "icarus.config.model_config.ModelConfig.load",
                return_value=_fresh_empty_config(),
            ),
            patch(
                "icarus.config.ollama.discover_ollama_models",
                return_value=[],
            ),
            patch(
                "icarus.config.ollama.get_ollama_base_url",
                return_value="http://localhost:11434",
            ),
        ):
            models = get_available_models()

        assert "ollama" not in models or models.get("ollama") == []


# ---------------------------------------------------------------------------
# get_provider_registry — Ollama entry
# ---------------------------------------------------------------------------


class TestGetProviderRegistryOllama:
    def setup_method(self):
        clear_caches()

    def test_ollama_entry_shape(self):
        clear_caches()
        with (
            patch(
                "icarus.config.ollama.discover_ollama_models",
                return_value=[{"name": "llama3:latest"}],
            ),
            patch(
                "icarus.config.ollama.get_ollama_base_url",
                return_value="http://localhost:11434",
            ),
        ):
            registry = get_provider_registry()

        ollama_entry = next((p for p in registry if p["name"] == "ollama"), None)
        assert ollama_entry is not None
        assert ollama_entry["is_keyless"] is True
        assert ollama_entry["is_available"] is True
        assert ollama_entry["env_vars"] == []
        assert "llama3:latest" in ollama_entry["available_models"]
        assert ollama_entry["base_url"] == "http://localhost:11434"

    def test_ollama_not_running(self):
        clear_caches()
        with (
            patch(
                "icarus.config.ollama.discover_ollama_models",
                return_value=[],
            ),
            patch(
                "icarus.config.ollama.get_ollama_base_url",
                return_value="http://localhost:11434",
            ),
        ):
            registry = get_provider_registry()

        ollama_entry = next((p for p in registry if p["name"] == "ollama"), None)
        assert ollama_entry is not None
        assert ollama_entry["is_available"] is False
        assert ollama_entry["available_models"] == []
