"""Extended tests for search_system.py - covering settings processing, programmatic mode,
strategy selection, and result processing."""

from unittest.mock import Mock, patch


def _make_strategy_mock():
    """Create a mock strategy with required attributes."""
    mock_strategy = Mock()
    mock_strategy.questions_by_iteration = []
    mock_strategy.all_links_of_system = []
    return mock_strategy


def _patch_system():
    """Return nested context managers for search system patching.

    Usage:
        with _create_patched():
            ...

    Returns a helper that patches both create_strategy and StandardCitationHandler.
    """

    class PatchContext:
        def __init__(self):
            self.strategy = _make_strategy_mock()
            self._p1 = patch(
                "local_deep_research.search_system_factory.create_strategy",
                return_value=self.strategy,
            )
            self._p2 = patch(
                "local_deep_research.citation_handlers.standard_citation_handler.StandardCitationHandler",
                return_value=Mock(
                    _create_documents=Mock(), _format_sources=Mock()
                ),
            )

        def __enter__(self):
            self.mock_create = self._p1.__enter__()
            self.mock_citation = self._p2.__enter__()
            return self

        def __exit__(self, *args):
            self._p2.__exit__(*args)
            self._p1.__exit__(*args)

    return PatchContext()


class TestAdvancedSearchSystemSettingsProcessing:
    """Tests for settings snapshot processing in AdvancedSearchSystem.__init__."""

    def test_max_iterations_from_dict_settings(self):
        """max_iterations should be extracted from dict-wrapped settings."""
        settings = {
            "search.iterations": {"value": 5, "ui_element": "number"},
            "search.questions_per_iteration": {
                "value": 4,
                "ui_element": "number",
            },
        }

        with _patch_system():
            from local_deep_research.search_system import AdvancedSearchSystem

            system = AdvancedSearchSystem(
                llm=Mock(), search=Mock(), settings_snapshot=settings
            )

        assert system.max_iterations == 5
        assert system.questions_per_iteration == 4

    def test_max_iterations_from_direct_value_settings(self):
        """max_iterations should work with direct (non-dict) settings values."""
        settings = {
            "search.iterations": 3,
            "search.questions_per_iteration": 2,
        }

        with _patch_system():
            from local_deep_research.search_system import AdvancedSearchSystem

            system = AdvancedSearchSystem(
                llm=Mock(), search=Mock(), settings_snapshot=settings
            )

        assert system.max_iterations == 3
        assert system.questions_per_iteration == 2

    def test_max_iterations_defaults_when_missing(self):
        """max_iterations should default to 1 when not in settings."""
        with _patch_system():
            from local_deep_research.search_system import AdvancedSearchSystem

            system = AdvancedSearchSystem(
                llm=Mock(), search=Mock(), settings_snapshot={}
            )

        assert system.max_iterations == 1
        assert system.questions_per_iteration == 3

    def test_explicit_max_iterations_overrides_settings(self):
        """Explicit max_iterations parameter should override settings snapshot."""
        settings = {"search.iterations": {"value": 10}}

        with _patch_system():
            from local_deep_research.search_system import AdvancedSearchSystem

            system = AdvancedSearchSystem(
                llm=Mock(),
                search=Mock(),
                max_iterations=7,
                settings_snapshot=settings,
            )

        assert system.max_iterations == 7


class TestAdvancedSearchSystemProgrammaticMode:
    """Tests for programmatic mode behavior."""

    def test_programmatic_mode_sets_flag(self):
        """programmatic_mode flag should be stored."""
        with _patch_system():
            from local_deep_research.search_system import AdvancedSearchSystem

            system = AdvancedSearchSystem(
                llm=Mock(), search=Mock(), programmatic_mode=True
            )

        assert system.programmatic_mode is True

    def test_non_programmatic_mode_default(self):
        """programmatic_mode should default to False."""
        with _patch_system():
            from local_deep_research.search_system import AdvancedSearchSystem

            system = AdvancedSearchSystem(llm=Mock(), search=Mock())

        assert system.programmatic_mode is False


class TestAdvancedSearchSystemResearchContext:
    """Tests for research context handling."""

    def test_stores_research_context(self):
        """research_context should be stored on the system."""
        context = {"delegate_strategy": "rapid", "some_key": "some_value"}

        with _patch_system():
            from local_deep_research.search_system import AdvancedSearchSystem

            system = AdvancedSearchSystem(
                llm=Mock(), search=Mock(), research_context=context
            )

        assert system.research_context == context

    def test_none_research_context(self):
        """None research_context should not cause errors."""
        with _patch_system():
            from local_deep_research.search_system import AdvancedSearchSystem

            system = AdvancedSearchSystem(
                llm=Mock(), search=Mock(), research_context=None
            )

        assert system.research_context is None

    def test_research_id_stored(self):
        """research_id should be stored on the system."""
        with _patch_system():
            from local_deep_research.search_system import AdvancedSearchSystem

            system = AdvancedSearchSystem(
                llm=Mock(), search=Mock(), research_id="test-123"
            )

        assert system.research_id == "test-123"


class TestAdvancedSearchSystemContextualFollowUp:
    """Tests for contextual follow-up strategy initialization."""

    def test_contextual_followup_uses_delegate(self):
        """Contextual follow-up strategy should create a delegate strategy."""
        context = {"delegate_strategy": "rapid"}

        with _patch_system() as ctx:
            with patch(
                "local_deep_research.search_system.EnhancedContextualFollowUpStrategy"
            ) as mock_followup_cls:
                mock_followup_cls.return_value = Mock()

                from local_deep_research.search_system import (
                    AdvancedSearchSystem,
                )

                AdvancedSearchSystem(
                    llm=Mock(),
                    search=Mock(),
                    strategy_name="enhanced-contextual-followup",
                    research_context=context,
                )

                # Verify delegate strategy was created with rapid
                ctx.mock_create.assert_called()
                call_kwargs = ctx.mock_create.call_args
                assert call_kwargs.kwargs.get("strategy_name") == "rapid" or (
                    call_kwargs[1].get("strategy_name") == "rapid"
                )

    def test_contextual_followup_defaults_delegate_to_source_based(self):
        """Contextual follow-up without delegate_strategy should default to source-based."""
        with _patch_system() as ctx:
            with patch(
                "local_deep_research.search_system.EnhancedContextualFollowUpStrategy"
            ) as mock_followup_cls:
                mock_followup_cls.return_value = Mock()

                from local_deep_research.search_system import (
                    AdvancedSearchSystem,
                )

                AdvancedSearchSystem(
                    llm=Mock(),
                    search=Mock(),
                    strategy_name="contextual-followup",
                    research_context={},
                )

                call_kwargs = ctx.mock_create.call_args
                strategy_name = call_kwargs.kwargs.get(
                    "strategy_name"
                ) or call_kwargs[1].get("strategy_name")
                assert strategy_name == "source-based"

    def test_contextual_followup_name_variants(self):
        """All contextual follow-up name variants should be recognized."""
        variants = [
            "enhanced-contextual-followup",
            "enhanced_contextual_followup",
            "contextual-followup",
            "contextual_followup",
        ]

        for variant in variants:
            with _patch_system():
                with patch(
                    "local_deep_research.search_system.EnhancedContextualFollowUpStrategy"
                ) as mock_followup_cls:
                    mock_followup_cls.return_value = Mock()

                    from local_deep_research.search_system import (
                        AdvancedSearchSystem,
                    )

                    AdvancedSearchSystem(
                        llm=Mock(),
                        search=Mock(),
                        strategy_name=variant,
                    )
                    # Should use EnhancedContextualFollowUpStrategy
                    mock_followup_cls.assert_called_once()


class TestAdvancedSearchSystemSearchOriginalQuery:
    """Tests for search_original_query parameter."""

    def test_search_original_query_default_true(self):
        """search_original_query should default to True."""
        with _patch_system():
            from local_deep_research.search_system import AdvancedSearchSystem

            system = AdvancedSearchSystem(llm=Mock(), search=Mock())

        assert system.search_original_query is True

    def test_search_original_query_can_be_disabled(self):
        """search_original_query can be set to False for news searches."""
        with _patch_system():
            from local_deep_research.search_system import AdvancedSearchSystem

            system = AdvancedSearchSystem(
                llm=Mock(), search=Mock(), search_original_query=False
            )

        assert system.search_original_query is False


class TestAdvancedSearchSystemPerformSearch:
    """Tests for _perform_search result processing."""

    def test_result_includes_query(self):
        """Result should include the query if not returned by strategy."""
        with _patch_system() as ctx:
            ctx.strategy.analyze_topic.return_value = {
                "current_knowledge": "some results"
            }
            ctx.strategy.questions_by_iteration = {1: ["q1"]}

            from local_deep_research.search_system import AdvancedSearchSystem

            system = AdvancedSearchSystem(llm=Mock(), search=Mock())

            with patch(
                "local_deep_research.news.core.search_integration.NewsSearchCallback"
            ):
                result = system.analyze_topic("test query")

        assert result["query"] == "test query"

    def test_result_includes_search_system_reference(self):
        """Result should include reference to the search system."""
        with _patch_system() as ctx:
            ctx.strategy.analyze_topic.return_value = {
                "current_knowledge": "results"
            }
            ctx.strategy.questions_by_iteration = {}

            from local_deep_research.search_system import AdvancedSearchSystem

            system = AdvancedSearchSystem(llm=Mock(), search=Mock())

            with patch(
                "local_deep_research.news.core.search_integration.NewsSearchCallback"
            ):
                result = system.analyze_topic("test query")

        assert result["search_system"] is system

    def test_settings_snapshot_extraction_in_perform_search(self):
        """_perform_search should extract LLM/search info from settings snapshot."""
        settings = {
            "llm.provider": {"value": "openai"},
            "llm.model": {"value": "gpt-4"},
            "search.tool": {"value": "searxng"},
        }

        progress_calls = []

        def capture_progress(msg, progress, metadata):
            progress_calls.append((msg, progress, metadata))

        with _patch_system() as ctx:
            ctx.strategy.analyze_topic.return_value = {"current_knowledge": "r"}
            ctx.strategy.questions_by_iteration = {}

            from local_deep_research.search_system import AdvancedSearchSystem

            system = AdvancedSearchSystem(
                llm=Mock(), search=Mock(), settings_snapshot=settings
            )
            system.set_progress_callback(capture_progress)

            with patch(
                "local_deep_research.news.core.search_integration.NewsSearchCallback"
            ):
                system.analyze_topic("test")

        # Should have progress callbacks with LLM and search info
        assert len(progress_calls) >= 2
        assert "openai" in progress_calls[0][0]
        assert "gpt-4" in progress_calls[0][0]

    def test_links_deduplication(self):
        """all_links_of_system should not be duplicated when same object."""
        shared_links = [{"title": "link1", "url": "http://example.com"}]

        with _patch_system() as ctx:
            ctx.strategy.analyze_topic.return_value = {"current_knowledge": "r"}
            ctx.strategy.questions_by_iteration = {}
            ctx.strategy.all_links_of_system = shared_links

            from local_deep_research.search_system import AdvancedSearchSystem

            system = AdvancedSearchSystem(llm=Mock(), search=Mock())
            system.all_links_of_system = shared_links

            with patch(
                "local_deep_research.news.core.search_integration.NewsSearchCallback"
            ):
                system.analyze_topic("test")

        # Should NOT have duplicated the links
        assert len(system.all_links_of_system) == 1


class TestAdvancedSearchSystemProgressCallback:
    """Tests for progress callback mechanism."""

    def test_set_progress_callback_propagates_to_strategy(self):
        """set_progress_callback should propagate to the strategy."""
        with _patch_system() as ctx:
            from local_deep_research.search_system import AdvancedSearchSystem

            system = AdvancedSearchSystem(llm=Mock(), search=Mock())

        def noop_callback(msg, pct, meta):
            return None

        system.set_progress_callback(noop_callback)

        ctx.strategy.set_progress_callback.assert_called_with(noop_callback)

    def test_progress_callback_before_strategy_exists(self):
        """Setting callback before strategy is created should not crash."""
        with _patch_system():
            from local_deep_research.search_system import AdvancedSearchSystem

            system = AdvancedSearchSystem(llm=Mock(), search=Mock())

        # Default callback should be a no-op lambda
        system.progress_callback("test", 50, {})
