"""
Tests for llm_utils module - Extended Edge Cases

Tests cover edge cases not covered by the main test_llm_utils.py:
- fetch_ollama_models with JSON decode errors (actual safe_get mocking)
- get_model initialization failures and edge cases
- Handling of malformed responses
"""

from unittest.mock import Mock, patch, MagicMock


from local_deep_research.utilities.llm_utils import (
    fetch_ollama_models,
    get_model,
)


class TestFetchOllamaModelsWithSafeGet:
    """Tests for fetch_ollama_models using the actual safe_get function."""

    def test_json_decode_error_returns_empty_list(self):
        """Should return empty list when JSON parsing fails."""
        with patch("local_deep_research.security.safe_get") as mock_safe_get:
            mock_response = Mock()
            mock_response.status_code = 200
            mock_response.json.side_effect = ValueError("Invalid JSON")
            mock_safe_get.return_value = mock_response

            result = fetch_ollama_models("http://localhost:11434")

            assert result == []

    def test_safe_get_called_with_correct_params(self):
        """Should call safe_get with localhost and private IP flags enabled."""
        with patch("local_deep_research.security.safe_get") as mock_safe_get:
            mock_response = Mock()
            mock_response.status_code = 200
            mock_response.json.return_value = {"models": []}
            mock_safe_get.return_value = mock_response

            fetch_ollama_models("http://localhost:11434", timeout=5.0)

            mock_safe_get.assert_called_once()
            call_kwargs = mock_safe_get.call_args.kwargs
            assert call_kwargs["allow_localhost"] is True
            assert call_kwargs["allow_private_ips"] is True
            assert call_kwargs["timeout"] == 5.0

    def test_handles_response_content_attribute(self):
        """Should handle responses with content attribute (like AIMessage)."""
        with patch("local_deep_research.security.safe_get") as mock_safe_get:
            mock_response = Mock()
            mock_response.status_code = 200
            mock_response.json.return_value = {"models": [{"name": "llama2"}]}
            mock_safe_get.return_value = mock_response

            result = fetch_ollama_models("http://localhost:11434")

            assert len(result) == 1
            assert result[0]["value"] == "llama2"

    def test_network_timeout_returns_empty_list(self):
        """Should return empty list on network timeout."""
        import requests

        with patch("local_deep_research.security.safe_get") as mock_safe_get:
            mock_safe_get.side_effect = requests.exceptions.Timeout(
                "Connection timed out"
            )

            result = fetch_ollama_models("http://localhost:11434")

            assert result == []

    def test_connection_refused_returns_empty_list(self):
        """Should return empty list when connection is refused."""
        import requests

        with patch("local_deep_research.security.safe_get") as mock_safe_get:
            mock_safe_get.side_effect = requests.exceptions.ConnectionError(
                "Connection refused"
            )

            result = fetch_ollama_models("http://localhost:11434")

            assert result == []

    def test_handles_list_response_format(self):
        """Should handle older API format where response is a list directly."""
        with patch("local_deep_research.security.safe_get") as mock_safe_get:
            mock_response = Mock()
            mock_response.status_code = 200
            # Older API format returns list directly
            mock_response.json.return_value = [
                {"name": "model1"},
                {"name": "model2"},
            ]
            mock_safe_get.return_value = mock_response

            result = fetch_ollama_models("http://localhost:11434")

            assert len(result) == 2
            assert result[0]["value"] == "model1"
            assert result[1]["value"] == "model2"

    def test_auth_headers_passed_to_safe_get(self):
        """Should pass auth headers to safe_get."""
        with patch("local_deep_research.security.safe_get") as mock_safe_get:
            mock_response = Mock()
            mock_response.status_code = 200
            mock_response.json.return_value = {"models": []}
            mock_safe_get.return_value = mock_response

            headers = {"Authorization": "Bearer test-token"}
            fetch_ollama_models("http://localhost:11434", auth_headers=headers)

            call_kwargs = mock_safe_get.call_args.kwargs
            assert call_kwargs["headers"] == headers

    def test_none_auth_headers_sends_empty_dict(self):
        """Should send empty dict when auth_headers is None."""
        with patch("local_deep_research.security.safe_get") as mock_safe_get:
            mock_response = Mock()
            mock_response.status_code = 200
            mock_response.json.return_value = {"models": []}
            mock_safe_get.return_value = mock_response

            fetch_ollama_models("http://localhost:11434", auth_headers=None)

            call_kwargs = mock_safe_get.call_args.kwargs
            assert call_kwargs["headers"] == {}

    def test_model_without_name_field_skipped(self):
        """Should skip models that don't have a name field."""
        with patch("local_deep_research.security.safe_get") as mock_safe_get:
            mock_response = Mock()
            mock_response.status_code = 200
            mock_response.json.return_value = {
                "models": [
                    {"name": "valid-model"},
                    {"size": "7B"},  # No name field
                    {"name": ""},  # Empty name
                    {"model": "wrong-field"},  # Wrong field name
                ]
            }
            mock_safe_get.return_value = mock_response

            result = fetch_ollama_models("http://localhost:11434")

            assert len(result) == 1
            assert result[0]["value"] == "valid-model"


class TestGetModelEdgeCases:
    """Tests for get_model function edge cases."""

    def test_none_model_name_uses_default(self):
        """Should use default model name when None is passed."""
        mock_chat_ollama = Mock()
        with patch(
            "local_deep_research.utilities.llm_utils.ChatOllama",
            mock_chat_ollama,
            create=True,
        ):
            with patch.dict(
                "sys.modules",
                {"langchain_ollama": MagicMock(ChatOllama=mock_chat_ollama)},
            ):
                get_model(model_name=None, model_type="ollama")

                call_kwargs = mock_chat_ollama.call_args.kwargs
                assert call_kwargs["model"] == "mistral"  # Default

    def test_none_model_type_defaults_to_ollama(self):
        """Should default to ollama when model_type is None."""
        mock_chat_ollama = Mock()
        with patch(
            "local_deep_research.utilities.llm_utils.ChatOllama",
            mock_chat_ollama,
            create=True,
        ):
            with patch.dict(
                "sys.modules",
                {"langchain_ollama": MagicMock(ChatOllama=mock_chat_ollama)},
            ):
                get_model(model_name="test", model_type=None)

                mock_chat_ollama.assert_called_once()

    def test_none_temperature_uses_default(self):
        """Should use default temperature when None is passed."""
        mock_chat_ollama = Mock()
        with patch(
            "local_deep_research.utilities.llm_utils.ChatOllama",
            mock_chat_ollama,
            create=True,
        ):
            with patch.dict(
                "sys.modules",
                {"langchain_ollama": MagicMock(ChatOllama=mock_chat_ollama)},
            ):
                get_model(
                    model_name="test", model_type="ollama", temperature=None
                )

                call_kwargs = mock_chat_ollama.call_args.kwargs
                assert call_kwargs["temperature"] == 0.7  # Default

    def test_model_name_and_type_both_none_uses_defaults(self):
        """Should use all defaults when both model_name and model_type are None."""
        mock_chat_ollama = Mock()
        with patch(
            "local_deep_research.utilities.llm_utils.ChatOllama",
            mock_chat_ollama,
            create=True,
        ):
            with patch.dict(
                "sys.modules",
                {"langchain_ollama": MagicMock(ChatOllama=mock_chat_ollama)},
            ):
                get_model(model_name=None, model_type=None, temperature=None)

                call_kwargs = mock_chat_ollama.call_args.kwargs
                # All should use defaults
                assert call_kwargs["model"] == "mistral"
                assert call_kwargs["temperature"] == 0.7
                assert call_kwargs["max_tokens"] == 30000

    def test_openai_model_with_valid_api_key(self):
        """Should create OpenAI model when API key is available."""
        mock_chat_openai = Mock()
        with patch(
            "local_deep_research.utilities.llm_utils.get_setting_from_snapshot"
        ) as mock_get:
            mock_get.return_value = "sk-valid-api-key"

            with patch.dict(
                "sys.modules",
                {"langchain_openai": MagicMock(ChatOpenAI=mock_chat_openai)},
            ):
                with patch(
                    "local_deep_research.utilities.llm_utils.ChatOpenAI",
                    mock_chat_openai,
                    create=True,
                ):
                    get_model(model_name="gpt-4", model_type="openai")

                    mock_chat_openai.assert_called_once()
                    call_kwargs = mock_chat_openai.call_args.kwargs
                    assert call_kwargs["api_key"] == "sk-valid-api-key"
                    assert call_kwargs["model"] == "gpt-4"

    def test_anthropic_model_with_valid_api_key(self):
        """Should create Anthropic model when API key is available."""
        mock_chat_anthropic = Mock()
        with patch(
            "local_deep_research.utilities.llm_utils.get_setting_from_snapshot"
        ) as mock_get:
            mock_get.return_value = "sk-ant-valid-key"

            with patch.dict(
                "sys.modules",
                {
                    "langchain_anthropic": MagicMock(
                        ChatAnthropic=mock_chat_anthropic
                    )
                },
            ):
                with patch(
                    "local_deep_research.utilities.llm_utils.ChatAnthropic",
                    mock_chat_anthropic,
                    create=True,
                ):
                    get_model(
                        model_name="claude-3-opus", model_type="anthropic"
                    )

                    mock_chat_anthropic.assert_called_once()
                    call_kwargs = mock_chat_anthropic.call_args.kwargs
                    assert (
                        call_kwargs["anthropic_api_key"] == "sk-ant-valid-key"
                    )

    def test_unknown_model_type_logs_warning_and_uses_ollama(self):
        """Should log warning and fall back to Ollama for unknown types."""
        mock_chat_ollama = Mock()
        with patch(
            "local_deep_research.utilities.llm_utils.ChatOllama",
            mock_chat_ollama,
            create=True,
        ):
            with patch.dict(
                "sys.modules",
                {"langchain_ollama": MagicMock(ChatOllama=mock_chat_ollama)},
            ):
                with patch(
                    "local_deep_research.utilities.llm_utils.logger"
                ) as mock_logger:
                    get_model(
                        model_name="some-model",
                        model_type="nonexistent_provider",
                    )

                    mock_logger.warning.assert_called()
                    mock_chat_ollama.assert_called_once()

    def test_custom_kwargs_passed_to_model(self):
        """Should pass custom kwargs to model constructor."""
        mock_chat_ollama = Mock()
        with patch(
            "local_deep_research.utilities.llm_utils.ChatOllama",
            mock_chat_ollama,
            create=True,
        ):
            with patch.dict(
                "sys.modules",
                {"langchain_ollama": MagicMock(ChatOllama=mock_chat_ollama)},
            ):
                get_model(
                    model_name="test",
                    model_type="ollama",
                    num_ctx=4096,
                    keep_alive="5m",
                )

                call_kwargs = mock_chat_ollama.call_args.kwargs
                assert call_kwargs["num_ctx"] == 4096
                assert call_kwargs["keep_alive"] == "5m"

    def test_max_tokens_from_kwargs(self):
        """Should use max_tokens from kwargs over default."""
        mock_chat_ollama = Mock()
        with patch(
            "local_deep_research.utilities.llm_utils.ChatOllama",
            mock_chat_ollama,
            create=True,
        ):
            with patch.dict(
                "sys.modules",
                {"langchain_ollama": MagicMock(ChatOllama=mock_chat_ollama)},
            ):
                get_model(
                    model_name="test", model_type="ollama", max_tokens=8192
                )

                call_kwargs = mock_chat_ollama.call_args.kwargs
                assert call_kwargs["max_tokens"] == 8192


class TestGetModelOpenAIEndpoint:
    """Tests for get_model with OpenAI endpoint provider edge cases."""

    def test_custom_endpoint_url_used(self):
        """Should use custom endpoint URL when provided."""
        mock_chat_openai = Mock()
        with patch(
            "local_deep_research.utilities.llm_utils.get_setting_from_snapshot"
        ) as mock_get:

            def side_effect(key, *args, **kwargs):
                if "api_key" in key:
                    return "test-key"
                return kwargs.get("default", None)

            mock_get.side_effect = side_effect

            with patch.dict(
                "sys.modules",
                {"langchain_openai": MagicMock(ChatOpenAI=mock_chat_openai)},
            ):
                with patch(
                    "local_deep_research.utilities.llm_utils.ChatOpenAI",
                    mock_chat_openai,
                    create=True,
                ):
                    get_model(
                        model_name="custom-model",
                        model_type="openai_endpoint",
                        OPENAI_ENDPOINT_URL="https://my-custom-endpoint.com/v1",
                    )

                    call_kwargs = mock_chat_openai.call_args.kwargs
                    assert (
                        call_kwargs["openai_api_base"]
                        == "https://my-custom-endpoint.com/v1"
                    )

    def test_default_endpoint_url_is_openrouter(self):
        """Should default to OpenRouter URL when no custom URL provided."""
        mock_chat_openai = Mock()
        with patch(
            "local_deep_research.utilities.llm_utils.get_setting_from_snapshot"
        ) as mock_get:

            def side_effect(key, *args, default=None, **kwargs):
                if "api_key" in key:
                    return "test-key"
                # Return the default for URL setting
                return default or "https://openrouter.ai/api/v1"

            mock_get.side_effect = side_effect

            with patch.dict(
                "sys.modules",
                {"langchain_openai": MagicMock(ChatOpenAI=mock_chat_openai)},
            ):
                with patch(
                    "local_deep_research.utilities.llm_utils.ChatOpenAI",
                    mock_chat_openai,
                    create=True,
                ):
                    get_model(
                        model_name="openrouter/model",
                        model_type="openai_endpoint",
                    )

                    call_kwargs = mock_chat_openai.call_args.kwargs
                    assert (
                        "openrouter"
                        in call_kwargs.get("openai_api_base", "").lower()
                    )
