"""Extended tests for meta_search_engine.py - targeting untested paths.

Covers:
- analyze_query() LLM invocation path (lines 256-346)
- analyze_query() LLM response parsing (content attr vs str)
- analyze_query() SearXNG fallback when LLM omits it
- analyze_query() no valid engines from LLM → reliability sort
- analyze_query() exception → fallback
- analyze_query() no engines_info → reliability sort
- _get_search_config() from settings_snapshot
- _get_available_engines() API key filtering
- _get_full_content() exception in selected engine
- _get_previews() engine init failure → skip
"""

from unittest.mock import Mock, patch


def _make_engine(available_engines, llm=None, settings=None):
    """Helper to create MetaSearchEngine with mocked _get_available_engines."""
    from local_deep_research.web_search_engines.engines.meta_search_engine import (
        MetaSearchEngine,
    )

    if llm is None:
        llm = Mock()
    if settings is None:
        settings = {"search.max_results": {"value": 10}}

    with patch.object(
        MetaSearchEngine,
        "_get_available_engines",
        return_value=available_engines,
    ):
        engine = MetaSearchEngine(
            llm=llm,
            settings_snapshot=settings,
            programmatic_mode=True,
        )

    return engine


# ── analyze_query: LLM invocation path ──────────────────────────


class TestAnalyzeQueryAdvancedPaths:
    """Tests for analyze_query paths not covered by existing tests.

    Note: The LLM invocation path (lines 255-346) is currently unreachable
    because line 227 catches SearXNG-present case and line 242 catches
    SearXNG-absent case, both returning before LLM is invoked. These tests
    cover the reachable but untested paths.
    """

    def test_no_llm_no_searxng_reliability_sort(self):
        """When LLM is None and SearXNG unavailable, reliability sort is used."""
        engine = _make_engine(["brave", "wikipedia"], llm=None)

        engine._get_search_config = lambda: {
            "brave": {"reliability": 0.8},
            "wikipedia": {"reliability": 0.95},
        }

        result = engine.analyze_query("query without llm")

        assert result[0] == "wikipedia"
        assert result[1] == "brave"

    def test_truthy_llm_no_searxng_reliability_sort(self):
        """When LLM is truthy but SearXNG absent, reliability sort is used (line 242)."""
        mock_llm = Mock()

        engine = _make_engine(["brave", "wikipedia"], llm=mock_llm)
        engine._get_search_config = lambda: {
            "brave": {"reliability": 0.6},
            "wikipedia": {"reliability": 0.9},
        }

        result = engine.analyze_query("general question about history")

        # Line 242 catches this: "searxng" not in available_engines → True
        assert result[0] == "wikipedia"
        assert result[1] == "brave"
        mock_llm.invoke.assert_not_called()

    def test_searxng_present_reliability_sort_for_remaining(self):
        """SearXNG present → first, remaining sorted by reliability (line 227)."""
        engine = _make_engine(["searxng", "brave", "wikipedia"])
        engine._get_search_config = lambda: {
            "brave": {"reliability": 0.9},
            "wikipedia": {"reliability": 0.7},
        }

        result = engine.analyze_query("general web search")

        assert result[0] == "searxng"
        assert result[1] == "brave"
        assert result[2] == "wikipedia"

    def test_multiple_specialized_terms_first_match_wins(self):
        """First matching specialized term in dict order determines engines."""
        engine = _make_engine(["github", "pubmed", "searxng"])

        # "clinical" appears before "code" in the specialized_domains dict,
        # so "clinical" match wins
        result = engine.analyze_query("code for clinical analysis")

        assert result[0] == "pubmed"

    def test_specialized_match_skips_unavailable_engines(self):
        """Specialized match only includes available engines."""
        engine = _make_engine(["wikipedia", "searxng"])

        # "scientific paper" wants arxiv, pubmed, wikipedia
        result = engine.analyze_query("scientific paper on climate")

        # Only wikipedia is available from the specialized list
        assert result == ["wikipedia"]

    def test_exception_in_analyze_with_searxng_available(self):
        """Exception in try block with SearXNG → SearXNG first + reliability."""
        engine = _make_engine(["searxng", "brave"])
        engine._get_search_config = lambda: {
            "searxng": {"reliability": 0.7},
            "brave": {"reliability": 0.9},
        }

        # Cause exception by passing non-string query (no .lower() method)
        result = engine.analyze_query(None)

        assert result[0] == "searxng"
        assert result[1] == "brave"

    def test_exception_in_analyze_without_searxng(self):
        """Exception in try block without SearXNG → reliability sort."""
        engine = _make_engine(["brave", "wikipedia"])
        engine._get_search_config = lambda: {
            "brave": {"reliability": 0.6},
            "wikipedia": {"reliability": 0.9},
        }

        # Cause exception by passing non-string query
        result = engine.analyze_query(None)

        assert result[0] == "wikipedia"
        assert result[1] == "brave"

    def test_arxiv_keyword_puts_arxiv_first_includes_others(self):
        """'arxiv' keyword → arxiv first, all others follow."""
        engine = _make_engine(["arxiv", "wikipedia", "brave"])

        result = engine.analyze_query("find arxiv papers on transformers")

        assert result[0] == "arxiv"
        assert set(result[1:]) == {"wikipedia", "brave"}

    def test_pubmed_keyword_puts_pubmed_first_includes_others(self):
        """'pubmed' keyword → pubmed first, all others follow."""
        engine = _make_engine(["pubmed", "wikipedia", "brave"])

        result = engine.analyze_query("search pubmed for cancer treatment")

        assert result[0] == "pubmed"
        assert set(result[1:]) == {"wikipedia", "brave"}


# ── _get_search_config from settings_snapshot ────────────────────


class TestGetSearchConfigFromSnapshot:
    """Tests for _get_search_config (now delegates to get_available_engines)."""

    def test_delegates_to_get_available_engines(self):
        """_get_search_config returns the output of get_available_engines."""
        expected = {
            "brave": {"class_name": "BraveSearchEngine", "api_key": "key123"},
            "wikipedia": {"class_name": "WikipediaSearchEngine"},
        }

        engine = _make_engine(["brave", "wikipedia"])

        with patch(
            "local_deep_research.web_search_engines.engines.meta_search_engine.get_available_engines",
            return_value=expected,
        ):
            config = engine._get_search_config()

        assert config == expected

    def test_returns_empty_without_snapshot(self):
        """Returns empty dict when settings_snapshot is empty."""
        engine = _make_engine(["wikipedia"], settings={})
        config = engine._get_search_config()
        assert config == {}


# ── _get_available_engines: API key filtering ────────────────────


class TestGetAvailableEnginesFiltering:
    """Tests for _get_available_engines filtering (now via shared get_available_engines)."""

    def test_requires_api_key_but_missing_excluded(self):
        """Engine with requires_api_key=True but no key is excluded."""
        from local_deep_research.web_search_engines.engines.meta_search_engine import (
            MetaSearchEngine,
        )

        mock_llm = Mock()
        settings = {"search.max_results": {"value": 10}}

        # Shared function already filters out brave (no key)
        with patch(
            "local_deep_research.web_search_engines.engines.meta_search_engine.get_available_engines",
            return_value={"wikipedia": {}},
        ):
            engine = MetaSearchEngine(
                llm=mock_llm,
                settings_snapshot=settings,
                programmatic_mode=True,
            )

        assert "brave" not in engine.available_engines
        assert "wikipedia" in engine.available_engines

    def test_requires_api_key_with_key_present_included(self):
        """Engine with requires_api_key=True and key present is included."""
        from local_deep_research.web_search_engines.engines.meta_search_engine import (
            MetaSearchEngine,
        )

        mock_llm = Mock()
        settings = {"search.max_results": {"value": 10}}

        with patch(
            "local_deep_research.web_search_engines.engines.meta_search_engine.get_available_engines",
            return_value={
                "brave": {"requires_api_key": True, "api_key": "sk-123"},
            },
        ):
            engine = MetaSearchEngine(
                llm=mock_llm,
                settings_snapshot=settings,
                programmatic_mode=True,
            )

        assert "brave" in engine.available_engines

    def test_use_api_key_services_false_skips_api_engines(self):
        """When use_api_key_services=False, API key engines are excluded."""
        from local_deep_research.web_search_engines.engines.meta_search_engine import (
            MetaSearchEngine,
        )

        mock_llm = Mock()
        settings = {"search.max_results": {"value": 10}}

        # Shared function with use_api_key_services=False filters brave
        with patch(
            "local_deep_research.web_search_engines.engines.meta_search_engine.get_available_engines",
            return_value={"wikipedia": {}},
        ):
            engine = MetaSearchEngine(
                llm=mock_llm,
                use_api_key_services=False,
                settings_snapshot=settings,
                programmatic_mode=True,
            )

        assert "brave" not in engine.available_engines
        assert "wikipedia" in engine.available_engines


# ── _get_full_content: exception in selected engine ──────────────


class TestGetFullContentException:
    """Tests for _get_full_content exception handling."""

    def test_exception_in_selected_engine_returns_items(self):
        """Exception in selected engine → returns items without full content."""
        engine = _make_engine(["wikipedia"])

        mock_selected = Mock()
        mock_selected._get_full_content.side_effect = RuntimeError(
            "Fetch failed"
        )
        engine._selected_engine = mock_selected
        engine._selected_engine_name = "wikipedia"

        items = [{"title": "Result", "url": "http://example.com"}]

        with patch(
            "local_deep_research.web_search_engines.engines.meta_search_engine.get_setting_from_snapshot",
            return_value=False,  # Not snippets_only
        ):
            result = engine._get_full_content(items)

        assert result == items


# ── _get_previews: engine init failure ───────────────────────────


class TestGetPreviewsEngineInitFailure:
    """Tests for _get_previews when engine initialization fails."""

    def test_init_failure_skips_to_next_engine(self):
        """Failed engine init skips to next engine in list."""
        engine = _make_engine(["broken", "working"])

        mock_working = Mock()
        mock_working._get_previews.return_value = [
            {"title": "From working", "url": "http://ok.com"}
        ]

        def get_instance(name):
            if name == "broken":
                return None  # Init failed
            return mock_working

        engine._get_engine_instance = get_instance

        with patch(
            "local_deep_research.web_search_engines.engines.meta_search_engine.SocketIOService"
        ):
            results = engine._get_previews("test query")

        assert len(results) == 1
        assert results[0]["title"] == "From working"

    def test_all_engines_fail_uses_fallback(self):
        """All engines failing → Wikipedia fallback is used."""
        engine = _make_engine(["bad1", "bad2"])

        engine._get_engine_instance = lambda name: None  # All fail

        mock_fallback = Mock()
        mock_fallback._get_previews.return_value = [
            {"title": "Wiki fallback", "url": "http://wiki.com"}
        ]
        engine.fallback_engine = mock_fallback

        results = engine._get_previews("test query")

        assert results[0]["title"] == "Wiki fallback"
        assert engine._selected_engine_name == "wikipedia"

    def test_successful_engine_stores_selection(self):
        """Successful engine is stored as _selected_engine."""
        engine = _make_engine(["brave"])

        mock_brave = Mock()
        mock_brave._get_previews.return_value = [
            {"title": "Brave result", "url": "http://brave.com"}
        ]
        engine._get_engine_instance = lambda name: mock_brave

        with patch(
            "local_deep_research.web_search_engines.engines.meta_search_engine.SocketIOService"
        ):
            engine._get_previews("test query")

        assert engine._selected_engine is mock_brave
        assert engine._selected_engine_name == "brave"

    def test_socket_emit_failure_non_critical(self):
        """SocketIO emit failure doesn't affect results."""
        engine = _make_engine(["brave"])

        mock_brave = Mock()
        mock_brave._get_previews.return_value = [
            {"title": "Result", "url": "http://test.com"}
        ]
        engine._get_engine_instance = lambda name: mock_brave

        with patch(
            "local_deep_research.web_search_engines.engines.meta_search_engine.SocketIOService"
        ) as mock_sio:
            mock_sio.return_value.emit_socket_event.side_effect = RuntimeError(
                "Socket error"
            )
            results = engine._get_previews("test query")

        assert len(results) == 1
        assert results[0]["title"] == "Result"
