"""
Tests for advanced_search_system/strategies/iterdrag_strategy.py

Tests cover:
- IterDRAGStrategy initialization
- _update_progress method
- _generate_subqueries method
- analyze_topic method
- Error handling and fallback behavior
"""

from unittest.mock import Mock, patch


class TestIterDRAGStrategyInit:
    """Tests for IterDRAGStrategy initialization."""

    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.CitationHandler"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.DecompositionQuestionGenerator"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.StandardKnowledge"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.FindingsRepository"
    )
    def test_init_with_defaults(
        self, mock_findings, mock_knowledge, mock_question, mock_citation
    ):
        """Test initialization with default values."""
        from local_deep_research.advanced_search_system.strategies.iterdrag_strategy import (
            IterDRAGStrategy,
        )

        mock_search = Mock()
        mock_model = Mock()

        strategy = IterDRAGStrategy(search=mock_search, model=mock_model)

        assert strategy.search is mock_search
        assert strategy.model is mock_model
        assert strategy.max_iterations == 3
        assert strategy.subqueries_per_iteration == 2
        assert strategy.progress_callback is None

    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.CitationHandler"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.DecompositionQuestionGenerator"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.StandardKnowledge"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.FindingsRepository"
    )
    def test_init_with_custom_values(
        self, mock_findings, mock_knowledge, mock_question, mock_citation
    ):
        """Test initialization with custom values."""
        from local_deep_research.advanced_search_system.strategies.iterdrag_strategy import (
            IterDRAGStrategy,
        )

        mock_search = Mock()
        mock_model = Mock()

        strategy = IterDRAGStrategy(
            search=mock_search,
            model=mock_model,
            max_iterations=5,
            subqueries_per_iteration=4,
            settings_snapshot={"key": "value"},
        )

        assert strategy.max_iterations == 5
        assert strategy.subqueries_per_iteration == 4
        assert strategy.settings_snapshot == {"key": "value"}

    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.CitationHandler"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.DecompositionQuestionGenerator"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.StandardKnowledge"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.FindingsRepository"
    )
    def test_init_creates_components(
        self, mock_findings, mock_knowledge, mock_question, mock_citation
    ):
        """Test that initialization creates required components."""
        from local_deep_research.advanced_search_system.strategies.iterdrag_strategy import (
            IterDRAGStrategy,
        )

        mock_search = Mock()
        mock_model = Mock()

        IterDRAGStrategy(search=mock_search, model=mock_model)

        mock_citation.assert_called_once_with(
            mock_model, settings_snapshot=None
        )
        mock_question.assert_called_once_with(mock_model)
        mock_knowledge.assert_called_once_with(mock_model)
        mock_findings.assert_called_once_with(mock_model)

    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.CitationHandler"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.DecompositionQuestionGenerator"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.StandardKnowledge"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.FindingsRepository"
    )
    def test_init_with_existing_links(
        self, mock_findings, mock_knowledge, mock_question, mock_citation
    ):
        """Test initialization with existing links."""
        from local_deep_research.advanced_search_system.strategies.iterdrag_strategy import (
            IterDRAGStrategy,
        )

        mock_search = Mock()
        mock_model = Mock()
        existing_links = ["http://example.com", "http://test.com"]

        strategy = IterDRAGStrategy(
            search=mock_search,
            model=mock_model,
            all_links_of_system=existing_links,
        )

        assert strategy.all_links_of_system == existing_links


class TestUpdateProgress:
    """Tests for _update_progress method."""

    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.CitationHandler"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.DecompositionQuestionGenerator"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.StandardKnowledge"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.FindingsRepository"
    )
    def test_update_progress_with_callback(
        self, mock_findings, mock_knowledge, mock_question, mock_citation
    ):
        """Test that progress callback is called when set."""
        from local_deep_research.advanced_search_system.strategies.iterdrag_strategy import (
            IterDRAGStrategy,
        )

        strategy = IterDRAGStrategy(search=Mock(), model=Mock())
        callback = Mock()
        strategy.progress_callback = callback

        strategy._update_progress("Test message", 50, {"key": "value"})

        callback.assert_called_once_with("Test message", 50, {"key": "value"})

    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.CitationHandler"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.DecompositionQuestionGenerator"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.StandardKnowledge"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.FindingsRepository"
    )
    def test_update_progress_without_callback(
        self, mock_findings, mock_knowledge, mock_question, mock_citation
    ):
        """Test that no error occurs when callback is not set."""
        from local_deep_research.advanced_search_system.strategies.iterdrag_strategy import (
            IterDRAGStrategy,
        )

        strategy = IterDRAGStrategy(search=Mock(), model=Mock())

        # Should not raise
        strategy._update_progress("Test message", 50, {"key": "value"})

    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.CitationHandler"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.DecompositionQuestionGenerator"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.StandardKnowledge"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.FindingsRepository"
    )
    def test_update_progress_with_none_metadata(
        self, mock_findings, mock_knowledge, mock_question, mock_citation
    ):
        """Test that None metadata is converted to empty dict."""
        from local_deep_research.advanced_search_system.strategies.iterdrag_strategy import (
            IterDRAGStrategy,
        )

        strategy = IterDRAGStrategy(search=Mock(), model=Mock())
        callback = Mock()
        strategy.progress_callback = callback

        strategy._update_progress("Test message", 50)

        callback.assert_called_once_with("Test message", 50, {})


class TestGenerateSubqueries:
    """Tests for _generate_subqueries method."""

    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.CitationHandler"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.DecompositionQuestionGenerator"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.StandardKnowledge"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.FindingsRepository"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.get_setting_from_snapshot"
    )
    def test_generate_subqueries_success(
        self,
        mock_get_setting,
        mock_findings,
        mock_knowledge,
        mock_question,
        mock_citation,
    ):
        """Test successful sub-query generation."""
        from local_deep_research.advanced_search_system.strategies.iterdrag_strategy import (
            IterDRAGStrategy,
        )

        mock_get_setting.return_value = 3

        mock_question_gen = Mock()
        mock_question_gen.generate_questions.return_value = [
            "Sub-query 1",
            "Sub-query 2",
            "Sub-query 3",
        ]
        mock_question.return_value = mock_question_gen

        strategy = IterDRAGStrategy(search=Mock(), model=Mock())

        result = strategy._generate_subqueries(
            "Main query", [{"title": "Result 1"}], "Current knowledge"
        )

        assert len(result) == 3
        assert "Sub-query 1" in result
        mock_question_gen.generate_questions.assert_called_once()

    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.CitationHandler"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.DecompositionQuestionGenerator"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.StandardKnowledge"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.FindingsRepository"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.get_setting_from_snapshot"
    )
    def test_generate_subqueries_handles_exception(
        self,
        mock_get_setting,
        mock_findings,
        mock_knowledge,
        mock_question,
        mock_citation,
    ):
        """Test that exceptions return empty list."""
        from local_deep_research.advanced_search_system.strategies.iterdrag_strategy import (
            IterDRAGStrategy,
        )

        mock_get_setting.return_value = 3

        mock_question_gen = Mock()
        mock_question_gen.generate_questions.side_effect = RuntimeError(
            "LLM error"
        )
        mock_question.return_value = mock_question_gen

        strategy = IterDRAGStrategy(search=Mock(), model=Mock())

        result = strategy._generate_subqueries("Main query", [], "")

        assert result == []


class TestAnalyzeTopic:
    """Tests for analyze_topic method."""

    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.CitationHandler"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.DecompositionQuestionGenerator"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.StandardKnowledge"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.FindingsRepository"
    )
    def test_analyze_topic_no_search_engine(
        self, mock_findings, mock_knowledge, mock_question, mock_citation
    ):
        """Test analyze_topic when search engine is None."""
        from local_deep_research.advanced_search_system.strategies.iterdrag_strategy import (
            IterDRAGStrategy,
        )

        strategy = IterDRAGStrategy(search=None, model=Mock())

        result = strategy.analyze_topic("Test query")

        assert "error" in result
        assert result["findings"] == []
        assert result["iterations"] == 0

    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.CitationHandler"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.DecompositionQuestionGenerator"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.StandardKnowledge"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.FindingsRepository"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.extract_links_from_search_results"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.get_setting_from_snapshot"
    )
    def test_analyze_topic_no_initial_results(
        self,
        mock_get_setting,
        mock_extract_links,
        mock_findings,
        mock_knowledge,
        mock_question,
        mock_citation,
    ):
        """Test analyze_topic when initial search returns no results."""
        from local_deep_research.advanced_search_system.strategies.iterdrag_strategy import (
            IterDRAGStrategy,
        )

        mock_get_setting.return_value = 3
        mock_extract_links.return_value = []

        mock_search = Mock()
        mock_search.run.return_value = []

        mock_question_gen = Mock()
        mock_question_gen.generate_questions.return_value = []
        mock_question.return_value = mock_question_gen

        mock_findings_repo = Mock()
        mock_findings.return_value = mock_findings_repo

        strategy = IterDRAGStrategy(search=mock_search, model=Mock())

        result = strategy.analyze_topic("Test query")

        assert "findings" in result
        mock_search.run.assert_called_once_with("Test query")

    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.CitationHandler"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.DecompositionQuestionGenerator"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.StandardKnowledge"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.FindingsRepository"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.extract_links_from_search_results"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.get_setting_from_snapshot"
    )
    def test_analyze_topic_with_subqueries(
        self,
        mock_get_setting,
        mock_extract_links,
        mock_findings,
        mock_knowledge,
        mock_question,
        mock_citation,
    ):
        """Test analyze_topic with sub-query generation."""
        from local_deep_research.advanced_search_system.strategies.iterdrag_strategy import (
            IterDRAGStrategy,
        )

        mock_get_setting.return_value = 3
        mock_extract_links.return_value = ["http://example.com"]

        mock_search = Mock()
        mock_search.run.return_value = [
            {"title": "Result 1", "snippet": "Content"}
        ]

        mock_question_gen = Mock()
        mock_question_gen.generate_questions.return_value = ["Sub-query 1"]
        mock_question.return_value = mock_question_gen

        mock_citation_handler = Mock()
        mock_citation_handler.analyze_followup.return_value = {
            "content": "Analysis result",
            "documents": [],
        }
        mock_citation.return_value = mock_citation_handler

        mock_findings_repo = Mock()
        mock_findings_repo.synthesize_findings.return_value = "Final synthesis"
        mock_findings_repo.format_findings_to_text.return_value = (
            "Formatted findings"
        )
        mock_findings.return_value = mock_findings_repo

        strategy = IterDRAGStrategy(search=mock_search, model=Mock())

        result = strategy.analyze_topic("Test query")

        assert "findings" in result
        assert "formatted_findings" in result
        assert result["iterations"] == 1
        mock_citation_handler.analyze_followup.assert_called()

    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.CitationHandler"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.DecompositionQuestionGenerator"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.StandardKnowledge"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.FindingsRepository"
    )
    def test_analyze_topic_calls_progress_callback(
        self, mock_findings, mock_knowledge, mock_question, mock_citation
    ):
        """Test that progress callback is called during analysis."""
        from local_deep_research.advanced_search_system.strategies.iterdrag_strategy import (
            IterDRAGStrategy,
        )

        mock_search = Mock()
        mock_search.run.return_value = []

        mock_question_gen = Mock()
        mock_question_gen.generate_questions.return_value = []
        mock_question.return_value = mock_question_gen

        mock_findings_repo = Mock()
        mock_findings.return_value = mock_findings_repo

        strategy = IterDRAGStrategy(search=mock_search, model=Mock())
        callback = Mock()
        strategy.progress_callback = callback

        strategy.analyze_topic("Test query")

        # Progress callback should be called multiple times
        assert callback.call_count > 0


class TestResultStructure:
    """Tests for analyze_topic result structure."""

    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.CitationHandler"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.DecompositionQuestionGenerator"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.StandardKnowledge"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.FindingsRepository"
    )
    def test_result_has_required_keys(
        self, mock_findings, mock_knowledge, mock_question, mock_citation
    ):
        """Test that result has all required keys."""
        from local_deep_research.advanced_search_system.strategies.iterdrag_strategy import (
            IterDRAGStrategy,
        )

        mock_search = Mock()
        mock_search.run.return_value = []

        mock_question_gen = Mock()
        mock_question_gen.generate_questions.return_value = []
        mock_question.return_value = mock_question_gen

        mock_findings_repo = Mock()
        mock_findings.return_value = mock_findings_repo

        strategy = IterDRAGStrategy(search=mock_search, model=Mock())

        result = strategy.analyze_topic("Test query")

        assert "findings" in result
        assert "iterations" in result
        assert "questions" in result
        assert "formatted_findings" in result
        assert "current_knowledge" in result


class TestKnowledgeCompression:
    """Tests for knowledge compression behavior."""

    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.CitationHandler"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.DecompositionQuestionGenerator"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.StandardKnowledge"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.FindingsRepository"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.extract_links_from_search_results"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.get_setting_from_snapshot"
    )
    def test_knowledge_compression_called_when_enabled(
        self,
        mock_get_setting,
        mock_extract_links,
        mock_findings,
        mock_knowledge,
        mock_question,
        mock_citation,
    ):
        """Test that knowledge compression is called when enabled."""
        from local_deep_research.advanced_search_system.strategies.iterdrag_strategy import (
            IterDRAGStrategy,
        )

        def setting_side_effect(key, *args, **kwargs):
            if "knowledge_accumulation" in key:
                return "ITERATION"
            return 3

        mock_get_setting.side_effect = setting_side_effect
        mock_extract_links.return_value = []

        mock_search = Mock()
        mock_search.run.return_value = []

        mock_question_gen = Mock()
        mock_question_gen.generate_questions.return_value = []
        mock_question.return_value = mock_question_gen

        mock_knowledge_gen = Mock()
        mock_knowledge_gen.compress_knowledge.return_value = "Compressed"
        mock_knowledge.return_value = mock_knowledge_gen

        mock_findings_repo = Mock()
        mock_findings_repo.format_findings_to_text.return_value = "Formatted"
        mock_findings.return_value = mock_findings_repo

        strategy = IterDRAGStrategy(search=mock_search, model=Mock())

        strategy.analyze_topic("Test query")

        mock_knowledge_gen.compress_knowledge.assert_called()


class TestSynthesisFallback:
    """Tests for synthesis fallback behavior."""

    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.CitationHandler"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.DecompositionQuestionGenerator"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.StandardKnowledge"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.FindingsRepository"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.extract_links_from_search_results"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.get_setting_from_snapshot"
    )
    def test_synthesis_error_triggers_fallback(
        self,
        mock_get_setting,
        mock_extract_links,
        mock_findings,
        mock_knowledge,
        mock_question,
        mock_citation,
    ):
        """Test that synthesis error triggers fallback behavior."""
        from local_deep_research.advanced_search_system.strategies.iterdrag_strategy import (
            IterDRAGStrategy,
        )

        mock_get_setting.return_value = 3
        mock_extract_links.return_value = []

        mock_search = Mock()
        mock_search.run.return_value = [{"title": "Result"}]

        mock_question_gen = Mock()
        mock_question_gen.generate_questions.return_value = ["Sub-query"]
        mock_question.return_value = mock_question_gen

        mock_citation_handler = Mock()
        mock_citation_handler.analyze_followup.return_value = {
            "content": "Finding content",
            "documents": [],
        }
        mock_citation.return_value = mock_citation_handler

        mock_findings_repo = Mock()
        mock_findings_repo.synthesize_findings.return_value = "Error: timeout"
        mock_findings_repo.format_findings_to_text.return_value = "Formatted"
        mock_findings.return_value = mock_findings_repo

        strategy = IterDRAGStrategy(search=mock_search, model=Mock())

        result = strategy.analyze_topic("Test query")

        # Should still return a result with findings
        assert "findings" in result
        assert len(result["findings"]) > 0


class TestInheritance:
    """Tests for IterDRAGStrategy inheritance."""

    def test_inherits_from_base_strategy(self):
        """Test that IterDRAGStrategy inherits from BaseSearchStrategy."""
        from local_deep_research.advanced_search_system.strategies.iterdrag_strategy import (
            IterDRAGStrategy,
        )
        from local_deep_research.advanced_search_system.strategies.base_strategy import (
            BaseSearchStrategy,
        )

        assert issubclass(IterDRAGStrategy, BaseSearchStrategy)

    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.CitationHandler"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.DecompositionQuestionGenerator"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.StandardKnowledge"
    )
    @patch(
        "local_deep_research.advanced_search_system.strategies.iterdrag_strategy.FindingsRepository"
    )
    def test_has_base_class_attributes(
        self, mock_findings, mock_knowledge, mock_question, mock_citation
    ):
        """Test that instance has base class attributes."""
        from local_deep_research.advanced_search_system.strategies.iterdrag_strategy import (
            IterDRAGStrategy,
        )

        strategy = IterDRAGStrategy(search=Mock(), model=Mock())

        assert hasattr(strategy, "all_links_of_system")
        assert hasattr(strategy, "questions_by_iteration")
        assert hasattr(strategy, "settings_snapshot")
