"""
Tests for Ollama LLM provider.
"""

import pytest
from unittest.mock import Mock, patch
import requests

from src.local_deep_research.llm.providers.implementations.ollama import (
    OllamaProvider,
    create_ollama_llm,
    is_ollama_available,
    register_ollama_provider,
)


class TestOllamaProviderMetadata:
    """Tests for OllamaProvider class metadata."""

    def test_provider_name(self):
        """Provider name is correct."""
        assert OllamaProvider.provider_name == "Ollama"

    def test_provider_key(self):
        """Provider key is correct."""
        assert OllamaProvider.provider_key == "OLLAMA"

    def test_is_not_cloud(self):
        """Ollama is a local provider."""
        assert OllamaProvider.is_cloud is False

    def test_region_is_local(self):
        """Region is local."""
        assert OllamaProvider.region == "Local"

    def test_data_location_is_local(self):
        """Data location is local."""
        assert OllamaProvider.data_location == "Local"

    def test_default_model(self):
        """Default model is set."""
        assert OllamaProvider.default_model is not None
        assert len(OllamaProvider.default_model) > 0


class TestOllamaGetAuthHeaders:
    """Tests for _get_auth_headers method."""

    def test_no_auth_headers_without_key(self):
        """Returns empty headers when no API key."""
        headers = OllamaProvider._get_auth_headers()
        assert headers == {}

    def test_auth_headers_with_key(self):
        """Returns Bearer token when API key provided."""
        headers = OllamaProvider._get_auth_headers(api_key="test-key")
        assert "Authorization" in headers
        assert headers["Authorization"] == "Bearer test-key"

    def test_auth_headers_from_settings(self):
        """Gets API key from settings snapshot."""
        with patch(
            "src.local_deep_research.llm.providers.implementations.ollama.get_setting_from_snapshot"
        ) as mock_get_setting:
            mock_get_setting.return_value = "settings-key"

            headers = OllamaProvider._get_auth_headers(
                settings_snapshot={"llm.ollama.api_key": "settings-key"}
            )

            assert "Authorization" in headers
            assert headers["Authorization"] == "Bearer settings-key"


class TestOllamaIsAvailable:
    """Tests for is_available method."""

    def test_not_available_without_url(self):
        """Returns False when URL not configured."""
        with patch(
            "src.local_deep_research.llm.providers.implementations.ollama.get_setting_from_snapshot"
        ) as mock_get_setting:
            mock_get_setting.return_value = None

            result = OllamaProvider.is_available()
            assert result is False

    def test_available_with_working_server(self):
        """Returns True when Ollama server responds."""
        with patch(
            "src.local_deep_research.llm.providers.implementations.ollama.get_setting_from_snapshot"
        ) as mock_get_setting:
            mock_get_setting.return_value = "http://localhost:11434"

            with patch("requests.get") as mock_get:
                mock_response = Mock()
                mock_response.status_code = 200
                mock_response.text = '{"models":[]}'
                mock_get.return_value = mock_response

                result = OllamaProvider.is_available()
                assert result is True

    def test_not_available_with_error_response(self):
        """Returns False when server returns error."""
        with patch(
            "src.local_deep_research.llm.providers.implementations.ollama.get_setting_from_snapshot"
        ) as mock_get_setting:
            mock_get_setting.return_value = "http://localhost:11434"

            with patch("requests.get") as mock_get:
                mock_response = Mock()
                mock_response.status_code = 500
                mock_get.return_value = mock_response

                result = OllamaProvider.is_available()
                assert result is False

    def test_not_available_with_connection_error(self):
        """Returns False when connection fails."""
        with patch(
            "src.local_deep_research.llm.providers.implementations.ollama.get_setting_from_snapshot"
        ) as mock_get_setting:
            mock_get_setting.return_value = "http://localhost:11434"

            with patch("requests.get") as mock_get:
                mock_get.side_effect = requests.exceptions.ConnectionError()

                result = OllamaProvider.is_available()
                assert result is False

    def test_not_available_with_timeout(self):
        """Returns False when request times out."""
        with patch(
            "src.local_deep_research.llm.providers.implementations.ollama.get_setting_from_snapshot"
        ) as mock_get_setting:
            mock_get_setting.return_value = "http://localhost:11434"

            with patch("requests.get") as mock_get:
                mock_get.side_effect = requests.exceptions.Timeout()

                result = OllamaProvider.is_available()
                assert result is False


class TestOllamaListModels:
    """Tests for list_models_for_api method."""

    def test_list_models_returns_list(self, mock_ollama_response):
        """Returns list of models."""
        with patch(
            "src.local_deep_research.utilities.db_utils.get_settings_manager"
        ) as mock_settings_manager:
            mock_manager = Mock()
            mock_manager.get_setting.return_value = "http://localhost:11434"
            mock_settings_manager.return_value = mock_manager

            with patch(
                "src.local_deep_research.utilities.llm_utils.fetch_ollama_models"
            ) as mock_fetch:
                mock_fetch.return_value = [
                    {"value": "llama2:latest", "label": "llama2 (Ollama)"},
                    {"value": "gemma:latest", "label": "gemma (Ollama)"},
                ]

                result = OllamaProvider.list_models_for_api()

                assert isinstance(result, list)
                assert len(result) == 2

    def test_list_models_empty_without_url(self):
        """Returns empty list when URL not configured."""
        with patch(
            "src.local_deep_research.utilities.db_utils.get_settings_manager"
        ) as mock_settings_manager:
            mock_manager = Mock()
            mock_manager.get_setting.return_value = None
            mock_settings_manager.return_value = mock_manager

            result = OllamaProvider.list_models_for_api()
            assert result == []

    def test_list_models_includes_provider_info(self, mock_ollama_response):
        """Model entries include provider information."""
        with patch(
            "src.local_deep_research.utilities.db_utils.get_settings_manager"
        ) as mock_settings_manager:
            mock_manager = Mock()
            mock_manager.get_setting.return_value = "http://localhost:11434"
            mock_settings_manager.return_value = mock_manager

            with patch(
                "src.local_deep_research.utilities.llm_utils.fetch_ollama_models"
            ) as mock_fetch:
                mock_fetch.return_value = [
                    {"value": "llama2:latest", "label": "llama2"},
                ]

                result = OllamaProvider.list_models_for_api()

                assert len(result) > 0
                assert "provider" in result[0]
                assert result[0]["provider"] == "OLLAMA"


class TestOllamaCreateLLM:
    """Tests for create_llm method."""

    def test_create_llm_raises_without_url(self):
        """Raises ValueError when URL not configured."""
        with patch(
            "src.local_deep_research.llm.providers.implementations.ollama.get_setting_from_snapshot"
        ) as mock_get_setting:
            mock_get_setting.return_value = None

            with pytest.raises(ValueError) as exc_info:
                OllamaProvider.create_llm()

            assert "url not configured" in str(exc_info.value).lower()

    def test_create_llm_raises_when_unavailable(self):
        """Raises ValueError when Ollama not available."""
        with patch(
            "src.local_deep_research.llm.providers.implementations.ollama.get_setting_from_snapshot"
        ) as mock_get_setting:
            mock_get_setting.return_value = "http://localhost:11434"

            with patch.object(
                OllamaProvider, "is_available", return_value=False
            ):
                with pytest.raises(ValueError) as exc_info:
                    OllamaProvider.create_llm()

                assert "not available" in str(exc_info.value).lower()

    def test_create_llm_success(self):
        """Successfully creates ChatOllama instance."""

        def mock_get_setting_side_effect(key, default=None, *args, **kwargs):
            settings_map = {
                "llm.ollama.url": "http://localhost:11434",
                "llm.local_context_window_size": 8192,
                "llm.supports_max_tokens": True,
                "llm.max_tokens": 4096,
            }
            return settings_map.get(key, default)

        with patch(
            "src.local_deep_research.llm.providers.implementations.ollama.get_setting_from_snapshot"
        ) as mock_get_setting:
            mock_get_setting.side_effect = mock_get_setting_side_effect

            with patch.object(
                OllamaProvider, "is_available", return_value=True
            ):
                with patch("requests.get") as mock_get:
                    # Mock model check response
                    mock_response = Mock()
                    mock_response.status_code = 200
                    mock_response.json.return_value = {
                        "models": [{"name": "gemma:latest"}]
                    }
                    mock_get.return_value = mock_response

                    with patch(
                        "src.local_deep_research.llm.providers.implementations.ollama.ChatOllama"
                    ) as mock_chat_ollama:
                        mock_llm = Mock()
                        mock_chat_ollama.return_value = mock_llm

                        result = OllamaProvider.create_llm()

                        assert result is mock_llm
                        mock_chat_ollama.assert_called_once()

    def test_create_llm_uses_default_model(self):
        """Uses default model when none specified."""

        def mock_get_setting_side_effect(key, default=None, *args, **kwargs):
            settings_map = {
                "llm.ollama.url": "http://localhost:11434",
                "llm.local_context_window_size": 8192,
                "llm.supports_max_tokens": True,
                "llm.max_tokens": 4096,
            }
            return settings_map.get(key, default)

        with patch(
            "src.local_deep_research.llm.providers.implementations.ollama.get_setting_from_snapshot"
        ) as mock_get_setting:
            mock_get_setting.side_effect = mock_get_setting_side_effect

            with patch.object(
                OllamaProvider, "is_available", return_value=True
            ):
                with patch("requests.get") as mock_get:
                    mock_response = Mock()
                    mock_response.status_code = 200
                    mock_response.json.return_value = {
                        "models": [{"name": "gemma:latest"}]
                    }
                    mock_get.return_value = mock_response

                    with patch(
                        "src.local_deep_research.llm.providers.implementations.ollama.ChatOllama"
                    ) as mock_chat_ollama:
                        OllamaProvider.create_llm()

                        call_kwargs = mock_chat_ollama.call_args[1]
                        assert (
                            call_kwargs["model"] == OllamaProvider.default_model
                        )

    def test_create_llm_with_custom_temperature(self):
        """Uses custom temperature."""

        def mock_get_setting_side_effect(key, default=None, *args, **kwargs):
            settings_map = {
                "llm.ollama.url": "http://localhost:11434",
                "llm.local_context_window_size": 8192,
                "llm.supports_max_tokens": True,
                "llm.max_tokens": 4096,
            }
            return settings_map.get(key, default)

        with patch(
            "src.local_deep_research.llm.providers.implementations.ollama.get_setting_from_snapshot"
        ) as mock_get_setting:
            mock_get_setting.side_effect = mock_get_setting_side_effect

            with patch.object(
                OllamaProvider, "is_available", return_value=True
            ):
                with patch("requests.get") as mock_get:
                    mock_response = Mock()
                    mock_response.status_code = 200
                    mock_response.json.return_value = {
                        "models": [{"name": "gemma:latest"}]
                    }
                    mock_get.return_value = mock_response

                    with patch(
                        "src.local_deep_research.llm.providers.implementations.ollama.ChatOllama"
                    ) as mock_chat_ollama:
                        OllamaProvider.create_llm(temperature=0.5)

                        call_kwargs = mock_chat_ollama.call_args[1]
                        assert call_kwargs["temperature"] == 0.5


class TestOllamaBackwardCompatibility:
    """Tests for backward compatibility functions."""

    def test_create_ollama_llm_function(self):
        """create_ollama_llm() delegates to OllamaProvider."""
        with patch.object(OllamaProvider, "create_llm") as mock_create:
            mock_llm = Mock()
            mock_create.return_value = mock_llm

            result = create_ollama_llm(model_name="test", temperature=0.5)

            mock_create.assert_called_once_with("test", 0.5)
            assert result is mock_llm

    def test_is_ollama_available_function(self):
        """is_ollama_available() delegates to OllamaProvider."""
        with patch.object(OllamaProvider, "is_available") as mock_available:
            mock_available.return_value = True

            result = is_ollama_available()

            mock_available.assert_called_once()
            assert result is True

    def test_register_ollama_provider_function(self):
        """register_ollama_provider() registers with registry."""
        with patch(
            "src.local_deep_research.llm.providers.implementations.ollama.register_llm"
        ) as mock_register:
            register_ollama_provider()

            mock_register.assert_called_once_with("ollama", create_ollama_llm)
