"""Extended tests for llm_config provider paths (LlamaCpp, Ollama, OpenAI)."""

from unittest.mock import MagicMock, patch


from local_deep_research.config.llm_config import get_llm


def _settings_dict(overrides=None):
    """Base settings dict shared across tests."""
    base = {
        "llm.model": "test-model",
        "llm.temperature": 0.5,
        "llm.provider": "ollama",
        "llm.local_context_window_size": 4096,
        "llm.context_window_unrestricted": True,
        "llm.supports_max_tokens": True,
        "llm.max_tokens": 4096,
        "rate_limiting.llm_enabled": False,
    }
    if overrides:
        base.update(overrides)
    return base


def _mock_get_setting(settings):
    """Return a side_effect for get_setting_from_snapshot."""
    return lambda key, default=None, **kw: settings.get(key, default)


# ── LlamaCpp provider ──────────────────────────────────────────────


class TestLlamaCppProvider:
    """Tests for the llamacpp provider — now an HTTP wrapper around llama-server."""

    def test_uses_chat_openai_with_configured_url(self):
        """Verifies dispatch goes through ChatOpenAI with the URL from settings."""
        settings = _settings_dict(
            {
                "llm.provider": "llamacpp",
                "llm.llamacpp.url": "http://my-server:8080/v1",
            }
        )

        with (
            patch(
                "local_deep_research.config.llm_config.is_llm_registered",
                return_value=False,
            ),
            patch(
                "local_deep_research.config.llm_config.get_setting_from_snapshot",
                side_effect=_mock_get_setting(settings),
            ),
            patch(
                "local_deep_research.config.llm_config.ChatOpenAI"
            ) as mock_chat_openai,
        ):
            mock_chat_openai.return_value = MagicMock()
            get_llm(provider="llamacpp", model_name="my-model")

            mock_chat_openai.assert_called_once()
            call_kwargs = mock_chat_openai.call_args[1]
            assert call_kwargs["base_url"] == "http://my-server:8080/v1"
            assert call_kwargs["model"] == "my-model"

    def test_uses_default_url_when_unset(self):
        """When llm.llamacpp.url is unset, falls back to localhost:8080/v1."""
        settings = _settings_dict({"llm.provider": "llamacpp"})

        with (
            patch(
                "local_deep_research.config.llm_config.is_llm_registered",
                return_value=False,
            ),
            patch(
                "local_deep_research.config.llm_config.get_setting_from_snapshot",
                side_effect=_mock_get_setting(settings),
            ),
            patch(
                "local_deep_research.config.llm_config.ChatOpenAI"
            ) as mock_chat_openai,
        ):
            mock_chat_openai.return_value = MagicMock()
            get_llm(provider="llamacpp", model_name="my-model")

            call_kwargs = mock_chat_openai.call_args[1]
            assert call_kwargs["base_url"] == "http://localhost:8080/v1"


# ── Ollama provider ────────────────────────────────────────────────


class TestOllamaProvider:
    """Tests for the Ollama provider path in get_llm."""

    def _ollama_settings(self, overrides=None):
        s = _settings_dict(
            {
                "llm.provider": "ollama",
                "llm.ollama.url": "http://localhost:11434",
                "llm.ollama.enable_thinking": True,
            }
        )
        if overrides:
            s.update(overrides)
        return s

    def test_model_not_found_raises_error(self):
        """Model not in Ollama model list — now handled by ChatOllama itself."""
        settings = self._ollama_settings()

        mock_ollama_cls = MagicMock()
        mock_ollama_cls.return_value = MagicMock()

        with (
            patch(
                "local_deep_research.config.llm_config.is_llm_registered",
                return_value=False,
            ),
            patch(
                "local_deep_research.config.llm_config.get_setting_from_snapshot",
                side_effect=_mock_get_setting(settings),
            ),
            patch(
                "local_deep_research.config.llm_config.ChatOllama",
                mock_ollama_cls,
            ),
            patch(
                "local_deep_research.config.llm_config.wrap_llm_without_think_tags",
                return_value=MagicMock(),
            ),
        ):
            get_llm(provider="ollama", model_name="test-model")
            # No pre-flight check — ChatOllama is created regardless
            mock_ollama_cls.assert_called_once()

    def test_model_found_creates_client(self):
        """Model creates ChatOllama (no pre-flight model check)."""
        settings = self._ollama_settings()

        mock_ollama_cls = MagicMock()
        mock_ollama_instance = MagicMock()
        mock_ollama_cls.return_value = mock_ollama_instance

        with (
            patch(
                "local_deep_research.config.llm_config.is_llm_registered",
                return_value=False,
            ),
            patch(
                "local_deep_research.config.llm_config.get_setting_from_snapshot",
                side_effect=_mock_get_setting(settings),
            ),
            patch(
                "local_deep_research.config.llm_config.ChatOllama",
                mock_ollama_cls,
            ),
            patch(
                "local_deep_research.config.llm_config.wrap_llm_without_think_tags",
                return_value=mock_ollama_instance,
            ),
        ):
            result = get_llm(provider="ollama", model_name="test-model")
            mock_ollama_cls.assert_called_once()
            assert result is mock_ollama_instance

    def test_api_tags_exception_continues(self):
        """No pre-flight check means ChatOllama is always created directly."""
        settings = self._ollama_settings()

        mock_ollama_cls = MagicMock()
        mock_ollama_instance = MagicMock()
        mock_ollama_cls.return_value = mock_ollama_instance

        with (
            patch(
                "local_deep_research.config.llm_config.is_llm_registered",
                return_value=False,
            ),
            patch(
                "local_deep_research.config.llm_config.get_setting_from_snapshot",
                side_effect=_mock_get_setting(settings),
            ),
            patch(
                "local_deep_research.config.llm_config.ChatOllama",
                mock_ollama_cls,
            ),
            patch(
                "local_deep_research.config.llm_config.wrap_llm_without_think_tags",
                return_value=mock_ollama_instance,
            ),
        ):
            result = get_llm(provider="ollama", model_name="test-model")
            mock_ollama_cls.assert_called_once()
            assert result is mock_ollama_instance


# ── OpenAI provider optional params ────────────────────────────────


class TestOpenAIOptionalParams:
    """Tests for OpenAI provider optional parameters."""

    def _openai_settings(self, overrides=None):
        s = _settings_dict(
            {
                "llm.provider": "openai",
                "llm.openai.api_key": "sk-test",
                "llm.openai.api_base": None,
                "llm.openai.organization": None,
                "llm.streaming": None,
                "llm.max_retries": None,
                "llm.request_timeout": None,
            }
        )
        if overrides:
            s.update(overrides)
        return s

    def test_with_api_base(self):
        """api_base is passed through as openai_api_base."""
        from langchain_openai import ChatOpenAI

        settings = self._openai_settings(
            {"llm.openai.api_base": "https://custom.endpoint/v1"}
        )

        with (
            patch(
                "local_deep_research.config.llm_config.is_llm_registered",
                return_value=False,
            ),
            patch(
                "local_deep_research.config.llm_config.get_setting_from_snapshot",
                side_effect=_mock_get_setting(settings),
            ),
            patch.object(
                ChatOpenAI, "__init__", return_value=None
            ) as mock_init,
        ):
            get_llm(provider="openai")
            call_kwargs = mock_init.call_args.kwargs
            assert (
                call_kwargs["openai_api_base"] == "https://custom.endpoint/v1"
            )

    def test_with_organization(self):
        """organization is passed through as openai_organization."""
        from langchain_openai import ChatOpenAI

        settings = self._openai_settings(
            {"llm.openai.organization": "org-abc123"}
        )

        with (
            patch(
                "local_deep_research.config.llm_config.is_llm_registered",
                return_value=False,
            ),
            patch(
                "local_deep_research.config.llm_config.get_setting_from_snapshot",
                side_effect=_mock_get_setting(settings),
            ),
            patch.object(
                ChatOpenAI, "__init__", return_value=None
            ) as mock_init,
        ):
            get_llm(provider="openai")
            call_kwargs = mock_init.call_args.kwargs
            assert call_kwargs["openai_organization"] == "org-abc123"
