"""Extra coverage tests for config/llm_config.py — provider availability and context window.

Targets uncovered branches:
- is_*_available() Exception (non-ImportError) paths
- is_llamacpp_available() path configurations
- _get_context_window_for_provider() all branches
- get_available_providers() with various provider combinations
- get_selected_llm_provider()
"""

from unittest.mock import MagicMock, patch


MODULE = "local_deep_research.config.llm_config"


# ===========================================================================
# is_*_available — Exception (non-ImportError) paths
# ===========================================================================


class TestProviderAvailabilityExceptionPaths:
    """Each is_*_available catches both ImportError and Exception.
    Test the Exception branch (provider imported but is_available raises)."""

    def test_openai_exception_returns_false(self):
        from local_deep_research.config.llm_config import is_openai_available

        with patch(
            f"{MODULE}.OpenAIProvider",
            create=True,
        ):
            # Patch the import to succeed but is_available to raise
            mock_provider = MagicMock()
            mock_provider.is_available.side_effect = RuntimeError("API error")

            with patch.dict(
                "sys.modules",
                {
                    "local_deep_research.llm.providers.implementations.openai": MagicMock(
                        OpenAIProvider=mock_provider
                    )
                },
            ):
                result = is_openai_available()

        assert result is False

    def test_anthropic_exception_returns_false(self):
        from local_deep_research.config.llm_config import is_anthropic_available

        mock_mod = MagicMock()
        mock_mod.AnthropicProvider.is_available.side_effect = RuntimeError(
            "err"
        )

        with patch.dict(
            "sys.modules",
            {
                "local_deep_research.llm.providers.implementations.anthropic": mock_mod
            },
        ):
            assert is_anthropic_available() is False

    def test_ollama_exception_returns_false(self):
        from local_deep_research.config.llm_config import is_ollama_available

        mock_mod = MagicMock()
        mock_mod.OllamaProvider.is_available.side_effect = ConnectionError(
            "refused"
        )

        with patch.dict(
            "sys.modules",
            {
                "local_deep_research.llm.providers.implementations.ollama": mock_mod
            },
        ):
            assert is_ollama_available() is False

    def test_openai_endpoint_exception_returns_false(self):
        from local_deep_research.config.llm_config import (
            is_openai_endpoint_available,
        )

        mock_mod = MagicMock()
        mock_mod.CustomOpenAIEndpointProvider.is_available.side_effect = (
            ValueError("bad config")
        )

        with patch.dict(
            "sys.modules",
            {
                "local_deep_research.llm.providers.implementations.custom_openai_endpoint": mock_mod
            },
        ):
            assert is_openai_endpoint_available() is False

    def test_lmstudio_exception_returns_false(self):
        from local_deep_research.config.llm_config import is_lmstudio_available

        mock_mod = MagicMock()
        mock_mod.LMStudioProvider.is_available.side_effect = TimeoutError()

        with patch.dict(
            "sys.modules",
            {
                "local_deep_research.llm.providers.implementations.lmstudio": mock_mod
            },
        ):
            assert is_lmstudio_available() is False

    def test_google_exception_returns_false(self):
        from local_deep_research.config.llm_config import is_google_available

        mock_mod = MagicMock()
        mock_mod.GoogleProvider.is_available.side_effect = RuntimeError("auth")

        with patch.dict(
            "sys.modules",
            {"local_deep_research.llm.providers.google": mock_mod},
        ):
            assert is_google_available() is False

    def test_openrouter_exception_returns_false(self):
        from local_deep_research.config.llm_config import (
            is_openrouter_available,
        )

        mock_mod = MagicMock()
        mock_mod.OpenRouterProvider.is_available.side_effect = RuntimeError()

        with patch.dict(
            "sys.modules",
            {"local_deep_research.llm.providers.openrouter": mock_mod},
        ):
            assert is_openrouter_available() is False


# ===========================================================================
# is_llamacpp_available
# ===========================================================================


class TestIsLlamacppAvailable:
    """is_llamacpp_available now probes llama-server's HTTP endpoint."""

    LLAMACPP_PROVIDER = "local_deep_research.llm.providers.implementations.llamacpp.LlamaCppProvider"

    def test_returns_true_when_provider_available(self):
        from local_deep_research.config.llm_config import is_llamacpp_available

        with patch(f"{self.LLAMACPP_PROVIDER}.is_available", return_value=True):
            assert is_llamacpp_available(settings_snapshot={}) is True

    def test_returns_false_when_provider_unavailable(self):
        from local_deep_research.config.llm_config import is_llamacpp_available

        with patch(
            f"{self.LLAMACPP_PROVIDER}.is_available", return_value=False
        ):
            assert is_llamacpp_available(settings_snapshot={}) is False

    def test_returns_false_on_unexpected_exception(self):
        from local_deep_research.config.llm_config import is_llamacpp_available

        with patch(
            f"{self.LLAMACPP_PROVIDER}.is_available",
            side_effect=RuntimeError("boom"),
        ):
            assert is_llamacpp_available(settings_snapshot={}) is False


# ===========================================================================
# _get_context_window_for_provider
# ===========================================================================


class TestGetContextWindowForProvider:
    def test_local_provider_default(self):
        from local_deep_research.config.llm_config import (
            _get_context_window_for_provider,
        )

        with patch(f"{MODULE}.get_setting_from_snapshot", return_value=4096):
            result = _get_context_window_for_provider("ollama")

        assert result == 4096

    def test_local_provider_custom_size(self):
        from local_deep_research.config.llm_config import (
            _get_context_window_for_provider,
        )

        with patch(f"{MODULE}.get_setting_from_snapshot", return_value=8192):
            result = _get_context_window_for_provider("llamacpp")

        assert result == 8192

    def test_local_provider_none_returns_default(self):
        from local_deep_research.config.llm_config import (
            _get_context_window_for_provider,
        )

        with patch(f"{MODULE}.get_setting_from_snapshot", return_value=None):
            result = _get_context_window_for_provider("lmstudio")

        assert result == 8192

    def test_cloud_provider_unrestricted(self):
        from local_deep_research.config.llm_config import (
            _get_context_window_for_provider,
        )

        with patch(f"{MODULE}.get_setting_from_snapshot", return_value=True):
            result = _get_context_window_for_provider("openai")

        assert result is None

    def test_cloud_provider_restricted_custom_size(self):
        from local_deep_research.config.llm_config import (
            _get_context_window_for_provider,
        )

        call_count = 0

        def setting_side_effect(key, default=None, **kwargs):
            nonlocal call_count
            call_count += 1
            if call_count == 1:
                return False  # unrestricted = False
            return 64000  # custom window size

        with patch(
            f"{MODULE}.get_setting_from_snapshot",
            side_effect=setting_side_effect,
        ):
            result = _get_context_window_for_provider("anthropic")

        assert result == 64000

    def test_cloud_provider_restricted_none_returns_default(self):
        from local_deep_research.config.llm_config import (
            _get_context_window_for_provider,
        )

        call_count = 0

        def setting_side_effect(key, default=None, **kwargs):
            nonlocal call_count
            call_count += 1
            if call_count == 1:
                return False  # unrestricted = False
            return None  # no custom size

        with patch(
            f"{MODULE}.get_setting_from_snapshot",
            side_effect=setting_side_effect,
        ):
            result = _get_context_window_for_provider("openrouter")

        assert result == 128000

    def test_local_provider_string_converted(self):
        from local_deep_research.config.llm_config import (
            _get_context_window_for_provider,
        )

        with patch(f"{MODULE}.get_setting_from_snapshot", return_value="16384"):
            result = _get_context_window_for_provider("ollama")

        assert result == 16384


# ===========================================================================
# get_available_providers
# ===========================================================================


class TestGetAvailableProviders:
    def test_no_providers_returns_none_entry(self):
        from local_deep_research.config.llm_config import (
            get_available_providers,
        )

        get_available_providers.cache_clear()

        with (
            patch(f"{MODULE}.is_ollama_available", return_value=False),
            patch(f"{MODULE}.is_openai_available", return_value=False),
            patch(f"{MODULE}.is_anthropic_available", return_value=False),
            patch(f"{MODULE}.is_google_available", return_value=False),
            patch(f"{MODULE}.is_openrouter_available", return_value=False),
            patch(f"{MODULE}.is_openai_endpoint_available", return_value=False),
            patch(f"{MODULE}.is_lmstudio_available", return_value=False),
            patch(f"{MODULE}.is_llamacpp_available", return_value=False),
        ):
            result = get_available_providers()

        assert "none" in result
        get_available_providers.cache_clear()

    def test_multiple_providers_available(self):
        from local_deep_research.config.llm_config import (
            get_available_providers,
        )

        get_available_providers.cache_clear()

        with (
            patch(f"{MODULE}.is_ollama_available", return_value=True),
            patch(f"{MODULE}.is_openai_available", return_value=True),
            patch(f"{MODULE}.is_anthropic_available", return_value=False),
            patch(f"{MODULE}.is_google_available", return_value=False),
            patch(f"{MODULE}.is_openrouter_available", return_value=False),
            patch(f"{MODULE}.is_openai_endpoint_available", return_value=False),
            patch(f"{MODULE}.is_lmstudio_available", return_value=False),
            patch(f"{MODULE}.is_llamacpp_available", return_value=False),
        ):
            result = get_available_providers()

        assert "ollama" in result
        assert "openai" in result
        assert "none" not in result
        get_available_providers.cache_clear()

    def test_all_providers_available(self):
        from local_deep_research.config.llm_config import (
            get_available_providers,
        )

        get_available_providers.cache_clear()

        with (
            patch(f"{MODULE}.is_ollama_available", return_value=True),
            patch(f"{MODULE}.is_openai_available", return_value=True),
            patch(f"{MODULE}.is_anthropic_available", return_value=True),
            patch(f"{MODULE}.is_google_available", return_value=True),
            patch(f"{MODULE}.is_openrouter_available", return_value=True),
            patch(f"{MODULE}.is_openai_endpoint_available", return_value=True),
            patch(f"{MODULE}.is_lmstudio_available", return_value=True),
            patch(f"{MODULE}.is_llamacpp_available", return_value=True),
        ):
            result = get_available_providers()

        assert len(result) == 8
        get_available_providers.cache_clear()


# ===========================================================================
# get_selected_llm_provider
# ===========================================================================


class TestGetSelectedLlmProvider:
    def test_returns_lowercase(self):
        from local_deep_research.config.llm_config import (
            get_selected_llm_provider,
        )

        with patch(
            f"{MODULE}.get_setting_from_snapshot", return_value="OLLAMA"
        ):
            assert get_selected_llm_provider() == "ollama"

    def test_default_ollama(self):
        from local_deep_research.config.llm_config import (
            get_selected_llm_provider,
        )

        result = get_selected_llm_provider(
            settings_snapshot={"llm.provider": "anthropic"}
        )
        assert result == "anthropic"
