"""
Tests for search_engines_config module.

Tests the configuration loading and processing for search engines:
- _get_setting() - settings retrieval with fallbacks
- _extract_per_engine_config() - nested config extraction
- search_config() - full search engine configuration
- default_search_engine() - default engine retrieval
- local_search_engines() - local engine listing
"""

from unittest.mock import MagicMock, patch


class TestGetSetting:
    """Tests for _get_setting function."""

    def test_returns_value_from_snapshot(self):
        """Should return value from settings snapshot when available."""
        from local_deep_research.web_search_engines.search_engines_config import (
            _get_setting,
        )

        with patch(
            "local_deep_research.web_search_engines.search_engines_config.get_setting_from_snapshot",
            return_value="snapshot_value",
        ):
            result = _get_setting(
                "test.key",
                "default_value",
                settings_snapshot={"test.key": "snapshot_value"},
            )
            assert result == "snapshot_value"

    def test_returns_value_from_db_session(self):
        """Should return value from db session when snapshot not available."""
        from local_deep_research.web_search_engines.search_engines_config import (
            _get_setting,
        )

        mock_session = MagicMock()
        mock_settings_manager = MagicMock()
        mock_settings_manager.get_setting.return_value = "db_value"

        with patch(
            "local_deep_research.web_search_engines.search_engines_config.get_settings_manager",
            return_value=mock_settings_manager,
        ):
            result = _get_setting(
                "test.key",
                "default_value",
                db_session=mock_session,
            )
            assert result == "db_value"

    def test_returns_default_when_no_source_available(self):
        """Should return default value when no settings source available."""
        from local_deep_research.web_search_engines.search_engines_config import (
            _get_setting,
        )

        result = _get_setting("test.key", "default_value")
        assert result == "default_value"

    def test_prefers_snapshot_over_db_session(self):
        """Should prefer settings snapshot over database session."""
        from local_deep_research.web_search_engines.search_engines_config import (
            _get_setting,
        )

        mock_session = MagicMock()
        mock_settings_manager = MagicMock()
        mock_settings_manager.get_setting.return_value = "db_value"

        with (
            patch(
                "local_deep_research.web_search_engines.search_engines_config.get_setting_from_snapshot",
                return_value="snapshot_value",
            ),
            patch(
                "local_deep_research.web_search_engines.search_engines_config.get_settings_manager",
                return_value=mock_settings_manager,
            ),
        ):
            result = _get_setting(
                "test.key",
                "default_value",
                db_session=mock_session,
                settings_snapshot={"test.key": "snapshot_value"},
            )
            assert result == "snapshot_value"

    def test_handles_snapshot_exception(self):
        """Should fall back to db_session when snapshot raises exception."""
        from local_deep_research.web_search_engines.search_engines_config import (
            _get_setting,
        )

        mock_session = MagicMock()
        mock_settings_manager = MagicMock()
        mock_settings_manager.get_setting.return_value = "db_value"

        with (
            patch(
                "local_deep_research.web_search_engines.search_engines_config.get_setting_from_snapshot",
                side_effect=Exception("Snapshot error"),
            ),
            patch(
                "local_deep_research.web_search_engines.search_engines_config.get_settings_manager",
                return_value=mock_settings_manager,
            ),
        ):
            result = _get_setting(
                "test.key",
                "default_value",
                db_session=mock_session,
                settings_snapshot={"test.key": "value"},
            )
            assert result == "db_value"

    def test_handles_db_session_exception(self):
        """Should return default when db_session raises exception."""
        from local_deep_research.web_search_engines.search_engines_config import (
            _get_setting,
        )

        mock_session = MagicMock()

        with patch(
            "local_deep_research.web_search_engines.search_engines_config.get_settings_manager",
            side_effect=Exception("DB error"),
        ):
            result = _get_setting(
                "test.key",
                "default_value",
                db_session=mock_session,
            )
            assert result == "default_value"

    def test_passes_username_to_settings_manager(self):
        """Should pass username to settings manager."""
        from local_deep_research.web_search_engines.search_engines_config import (
            _get_setting,
        )

        mock_session = MagicMock()
        mock_settings_manager = MagicMock()
        mock_settings_manager.get_setting.return_value = "value"

        with patch(
            "local_deep_research.web_search_engines.search_engines_config.get_settings_manager",
            return_value=mock_settings_manager,
        ) as mock_get_sm:
            _get_setting(
                "test.key",
                "default",
                db_session=mock_session,
                username="testuser",
            )
            mock_get_sm.assert_called_once_with(mock_session, "testuser")


class TestExtractPerEngineConfig:
    """Tests for _extract_per_engine_config function."""

    def test_extracts_simple_flat_config(self):
        """Should return flat config as-is for non-dotted keys."""
        from local_deep_research.web_search_engines.search_engines_config import (
            _extract_per_engine_config,
        )

        raw_config = {"key1": "value1", "key2": "value2"}
        result = _extract_per_engine_config(raw_config)
        assert result == {"key1": "value1", "key2": "value2"}

    def test_extracts_single_level_nested_config(self):
        """Should convert single dotted keys to nested dict."""
        from local_deep_research.web_search_engines.search_engines_config import (
            _extract_per_engine_config,
        )

        raw_config = {
            "engine1.param1": "value1",
            "engine1.param2": "value2",
        }
        result = _extract_per_engine_config(raw_config)
        assert "engine1" in result
        assert result["engine1"]["param1"] == "value1"
        assert result["engine1"]["param2"] == "value2"

    def test_extracts_multiple_engines(self):
        """Should extract configs for multiple engines."""
        from local_deep_research.web_search_engines.search_engines_config import (
            _extract_per_engine_config,
        )

        raw_config = {
            "duckduckgo.api_key": "key1",
            "google.api_key": "key2",
            "google.cx": "cx123",
        }
        result = _extract_per_engine_config(raw_config)
        assert result["duckduckgo"]["api_key"] == "key1"
        assert result["google"]["api_key"] == "key2"
        assert result["google"]["cx"] == "cx123"

    def test_extracts_deeply_nested_config(self):
        """Should recursively extract deeply nested configs."""
        from local_deep_research.web_search_engines.search_engines_config import (
            _extract_per_engine_config,
        )

        raw_config = {
            "engine.nested.param1": "value1",
            "engine.nested.param2": "value2",
        }
        result = _extract_per_engine_config(raw_config)
        assert result["engine"]["nested"]["param1"] == "value1"
        assert result["engine"]["nested"]["param2"] == "value2"

    def test_handles_empty_config(self):
        """Should return empty dict for empty input."""
        from local_deep_research.web_search_engines.search_engines_config import (
            _extract_per_engine_config,
        )

        result = _extract_per_engine_config({})
        assert result == {}

    def test_mixes_flat_and_nested(self):
        """Should handle mix of flat and nested keys."""
        from local_deep_research.web_search_engines.search_engines_config import (
            _extract_per_engine_config,
        )

        raw_config = {
            "simple_key": "simple_value",
            "engine.param": "nested_value",
        }
        result = _extract_per_engine_config(raw_config)
        assert result["simple_key"] == "simple_value"
        assert result["engine"]["param"] == "nested_value"


class TestSearchConfig:
    """Tests for search_config function."""

    @patch(
        "local_deep_research.web_search_engines.retriever_registry.retriever_registry"
    )
    @patch(
        "local_deep_research.web_search_engines.search_engines_config._get_setting"
    )
    def test_returns_dict_of_search_engines(
        self, mock_get_setting, mock_registry
    ):
        """Should return dict containing search engine configs."""
        mock_get_setting.return_value = {}
        mock_registry.list_registered.return_value = []

        from local_deep_research.web_search_engines.search_engines_config import (
            search_config,
        )

        result = search_config()
        assert isinstance(result, dict)

    @patch(
        "local_deep_research.web_search_engines.retriever_registry.retriever_registry"
    )
    @patch(
        "local_deep_research.web_search_engines.search_engines_config._get_setting"
    )
    def test_excludes_removed_meta_engines(
        self, mock_get_setting, mock_registry
    ):
        """Should not include the removed 'auto'/'meta'/'parallel' engines."""
        mock_get_setting.return_value = {}
        mock_registry.list_registered.return_value = []

        from local_deep_research.web_search_engines.search_engines_config import (
            search_config,
        )

        result = search_config()
        assert "auto" not in result
        assert "meta" not in result
        assert "parallel" not in result
        assert "parallel_scientific" not in result

    @patch(
        "local_deep_research.web_search_engines.retriever_registry.retriever_registry"
    )
    @patch(
        "local_deep_research.web_search_engines.search_engines_config._get_setting"
    )
    def test_includes_registered_retrievers(
        self, mock_get_setting, mock_registry
    ):
        """Should include registered retrievers as search engines."""
        mock_get_setting.return_value = {}
        mock_registry.list_registered.return_value = ["custom_retriever"]

        from local_deep_research.web_search_engines.search_engines_config import (
            search_config,
        )

        result = search_config()
        assert "custom_retriever" in result
        assert result["custom_retriever"]["is_retriever"] is True
        assert (
            result["custom_retriever"]["class_name"] == "RetrieverSearchEngine"
        )

    @patch(
        "local_deep_research.web_search_engines.retriever_registry.retriever_registry"
    )
    @patch(
        "local_deep_research.web_search_engines.search_engines_config._get_setting"
    )
    def test_adds_library_search_engine_when_enabled(
        self, mock_get_setting, mock_registry
    ):
        """Should add library search engine when enabled."""
        mock_registry.list_registered.return_value = []

        def get_setting_side_effect(key, default, **kwargs):
            if key == "search.engine.library.enabled":
                return True
            return default

        mock_get_setting.side_effect = get_setting_side_effect

        from local_deep_research.web_search_engines.search_engines_config import (
            search_config,
        )

        result = search_config()
        assert "library" in result
        assert result["library"]["class_name"] == "LibraryRAGSearchEngine"

    @patch(
        "local_deep_research.web_search_engines.retriever_registry.retriever_registry"
    )
    @patch(
        "local_deep_research.web_search_engines.search_engines_config._get_setting"
    )
    def test_skips_library_when_disabled(self, mock_get_setting, mock_registry):
        """Should skip library search engine when disabled."""
        mock_registry.list_registered.return_value = []

        def get_setting_side_effect(key, default, **kwargs):
            if key == "search.engine.library.enabled":
                return False
            return default

        mock_get_setting.side_effect = get_setting_side_effect

        from local_deep_research.web_search_engines.search_engines_config import (
            search_config,
        )

        result = search_config()
        assert "library" not in result


class TestDefaultSearchEngine:
    """Tests for default_search_engine function."""

    @patch(
        "local_deep_research.web_search_engines.search_engines_config._get_setting"
    )
    def test_returns_configured_default(self, mock_get_setting):
        """Should return configured default search engine."""
        mock_get_setting.return_value = "google"

        from local_deep_research.web_search_engines.search_engines_config import (
            default_search_engine,
        )

        result = default_search_engine()
        assert result == "google"

    @patch(
        "local_deep_research.web_search_engines.search_engines_config._get_setting"
    )
    def test_returns_wikipedia_as_default(self, mock_get_setting):
        """Should return 'wikipedia' as default when not configured."""
        mock_get_setting.return_value = "wikipedia"

        from local_deep_research.web_search_engines.search_engines_config import (
            default_search_engine,
        )

        result = default_search_engine()
        assert result == "wikipedia"

    @patch(
        "local_deep_research.web_search_engines.search_engines_config._get_setting"
    )
    def test_uses_correct_setting_key(self, mock_get_setting):
        """Should query the correct setting key."""
        mock_get_setting.return_value = "duckduckgo"

        from local_deep_research.web_search_engines.search_engines_config import (
            default_search_engine,
        )

        default_search_engine()
        mock_get_setting.assert_called_once()
        call_args = mock_get_setting.call_args
        assert call_args[0][0] == "search.engine.DEFAULT_SEARCH_ENGINE"
        assert call_args[0][1] == "wikipedia"

    @patch(
        "local_deep_research.web_search_engines.search_engines_config._get_setting"
    )
    def test_passes_db_session(self, mock_get_setting):
        """Should pass db_session to _get_setting."""
        mock_get_setting.return_value = "searxng"
        mock_session = MagicMock()

        from local_deep_research.web_search_engines.search_engines_config import (
            default_search_engine,
        )

        default_search_engine(db_session=mock_session)
        call_kwargs = mock_get_setting.call_args[1]
        assert call_kwargs["db_session"] is mock_session

    @patch(
        "local_deep_research.web_search_engines.search_engines_config._get_setting"
    )
    def test_passes_settings_snapshot(self, mock_get_setting):
        """Should pass settings_snapshot to _get_setting."""
        mock_get_setting.return_value = "brave"
        snapshot = {"search.engine.DEFAULT_SEARCH_ENGINE": "brave"}

        from local_deep_research.web_search_engines.search_engines_config import (
            default_search_engine,
        )

        default_search_engine(settings_snapshot=snapshot)
        call_kwargs = mock_get_setting.call_args[1]
        assert call_kwargs["settings_snapshot"] is snapshot
