"""Tests for API research functions."""

from unittest.mock import MagicMock, patch

from local_deep_research.api.research_functions import (
    _init_search_system,
    quick_summary,
    generate_report,
    detailed_research,
    analyze_documents,
)


class TestInitSearchSystem:
    """Tests for _init_search_system function."""

    def test_returns_search_system(self, mock_get_llm, mock_get_search):
        """Test that function returns an AdvancedSearchSystem."""
        with patch(
            "local_deep_research.api.research_functions.AdvancedSearchSystem"
        ) as mock_class:
            mock_system = MagicMock()
            mock_class.return_value = mock_system

            result = _init_search_system()

            assert result == mock_system
            mock_class.assert_called_once()

    def test_passes_custom_temperature(self, mock_get_llm, mock_get_search):
        """Test that custom temperature is passed to get_llm."""
        with (
            patch(
                "local_deep_research.api.research_functions.AdvancedSearchSystem"
            ),
            patch(
                "local_deep_research.api.research_functions.get_llm"
            ) as mock_llm,
        ):
            _init_search_system(temperature=0.5)
            mock_llm.assert_called_once()
            assert mock_llm.call_args[1]["temperature"] == 0.5

    def test_passes_model_name(self, mock_get_llm, mock_get_search):
        """Test that model_name is passed to get_llm."""
        with (
            patch(
                "local_deep_research.api.research_functions.AdvancedSearchSystem"
            ),
            patch(
                "local_deep_research.api.research_functions.get_llm"
            ) as mock_llm,
        ):
            _init_search_system(model_name="gpt-4")
            assert mock_llm.call_args[1]["model_name"] == "gpt-4"

    def test_passes_provider(self, mock_get_llm, mock_get_search):
        """Test that provider is passed to get_llm."""
        with (
            patch(
                "local_deep_research.api.research_functions.AdvancedSearchSystem"
            ),
            patch(
                "local_deep_research.api.research_functions.get_llm"
            ) as mock_llm,
        ):
            _init_search_system(provider="openai")
            assert mock_llm.call_args[1]["provider"] == "openai"

    def test_creates_search_engine(self, mock_get_llm, mock_get_search):
        """Test that search engine is created when search_tool specified."""
        with (
            patch(
                "local_deep_research.api.research_functions.AdvancedSearchSystem"
            ),
            patch(
                "local_deep_research.api.research_functions.get_search"
            ) as mock_search,
        ):
            _init_search_system(search_tool="wikipedia")
            mock_search.assert_called_once()
            assert mock_search.call_args[0][0] == "wikipedia"

    def test_sets_iterations(self, mock_get_llm, mock_get_search):
        """Test that max_iterations is set on system."""
        with patch(
            "local_deep_research.api.research_functions.AdvancedSearchSystem"
        ) as mock_class:
            mock_system = MagicMock()
            mock_class.return_value = mock_system

            _init_search_system(iterations=5)

            assert mock_system.max_iterations == 5

    def test_sets_questions_per_iteration(self, mock_get_llm, mock_get_search):
        """Test that questions_per_iteration is set on system."""
        with patch(
            "local_deep_research.api.research_functions.AdvancedSearchSystem"
        ) as mock_class:
            mock_system = MagicMock()
            mock_class.return_value = mock_system

            _init_search_system(questions_per_iteration=10)

            assert mock_system.questions_per_iteration == 10

    def test_sets_progress_callback(self, mock_get_llm, mock_get_search):
        """Test that progress callback is set."""
        callback = MagicMock()
        with patch(
            "local_deep_research.api.research_functions.AdvancedSearchSystem"
        ) as mock_class:
            mock_system = MagicMock()
            mock_class.return_value = mock_system

            _init_search_system(progress_callback=callback)

            mock_system.set_progress_callback.assert_called_once_with(callback)

    def test_registers_retrievers(self, mock_get_llm, mock_get_search):
        """Test that custom retrievers are registered."""
        retriever = MagicMock()
        with (
            patch(
                "local_deep_research.api.research_functions.AdvancedSearchSystem"
            ),
            patch(
                "local_deep_research.web_search_engines.retriever_registry.retriever_registry"
            ) as mock_registry,
        ):
            _init_search_system(retrievers={"custom": retriever})
            mock_registry.register_multiple.assert_called_once_with(
                {"custom": retriever}
            )

    def test_registers_llms(self, mock_get_llm, mock_get_search):
        """Test that custom LLMs are registered."""
        custom_llm = MagicMock()
        with (
            patch(
                "local_deep_research.api.research_functions.AdvancedSearchSystem"
            ),
            patch("local_deep_research.llm.register_llm") as mock_register,
        ):
            _init_search_system(llms={"custom_llm": custom_llm})
            mock_register.assert_called_once_with("custom_llm", custom_llm)

    def test_uses_settings_snapshot(
        self, mock_get_llm, mock_get_search, sample_settings_snapshot
    ):
        """Test that settings_snapshot is passed through."""
        with patch(
            "local_deep_research.api.research_functions.AdvancedSearchSystem"
        ) as mock_class:
            _init_search_system(settings_snapshot=sample_settings_snapshot)
            call_kwargs = mock_class.call_args[1]
            assert call_kwargs["settings_snapshot"] == sample_settings_snapshot


class TestQuickSummary:
    """Tests for quick_summary function."""

    def test_returns_dict(self, mock_get_llm, mock_advanced_search_system):
        """Test that function returns a dictionary."""
        with patch(
            "local_deep_research.api.research_functions.create_settings_snapshot",
            return_value={},
        ):
            result = quick_summary("test query")
            assert isinstance(result, dict)

    def test_returns_summary_key(
        self, mock_get_llm, mock_advanced_search_system
    ):
        """Test that result contains 'summary' key."""
        with patch(
            "local_deep_research.api.research_functions.create_settings_snapshot",
            return_value={},
        ):
            result = quick_summary("test query")
            assert "summary" in result

    def test_returns_findings_key(
        self, mock_get_llm, mock_advanced_search_system
    ):
        """Test that result contains 'findings' key."""
        with patch(
            "local_deep_research.api.research_functions.create_settings_snapshot",
            return_value={},
        ):
            result = quick_summary("test query")
            assert "findings" in result

    def test_returns_iterations_key(
        self, mock_get_llm, mock_advanced_search_system
    ):
        """Test that result contains 'iterations' key."""
        with patch(
            "local_deep_research.api.research_functions.create_settings_snapshot",
            return_value={},
        ):
            result = quick_summary("test query")
            assert "iterations" in result

    def test_returns_sources_key(
        self, mock_get_llm, mock_advanced_search_system
    ):
        """Test that result contains 'sources' key."""
        with patch(
            "local_deep_research.api.research_functions.create_settings_snapshot",
            return_value={},
        ):
            result = quick_summary("test query")
            assert "sources" in result

    def test_generates_research_id_if_none(
        self, mock_get_llm, mock_advanced_search_system
    ):
        """Test that research_id is generated if not provided."""
        with (
            patch(
                "local_deep_research.api.research_functions.create_settings_snapshot",
                return_value={},
            ),
            patch(
                "local_deep_research.api.research_functions.set_search_context"
            ) as mock_set_context,
        ):
            quick_summary("test query")
            call_args = mock_set_context.call_args[0][0]
            assert "research_id" in call_args
            # Should be a valid UUID
            assert len(call_args["research_id"]) == 36

    def test_uses_provided_research_id(
        self, mock_get_llm, mock_advanced_search_system
    ):
        """Test that provided research_id is used."""
        with (
            patch(
                "local_deep_research.api.research_functions.create_settings_snapshot",
                return_value={},
            ),
            patch(
                "local_deep_research.api.research_functions.set_search_context"
            ) as mock_set_context,
        ):
            quick_summary("test query", research_id="custom-id")
            call_args = mock_set_context.call_args[0][0]
            assert call_args["research_id"] == "custom-id"

    def test_registers_custom_retrievers(
        self, mock_get_llm, mock_advanced_search_system
    ):
        """Test that custom retrievers are registered."""
        retriever = MagicMock()
        with (
            patch(
                "local_deep_research.api.research_functions.create_settings_snapshot",
                return_value={},
            ),
            patch(
                "local_deep_research.web_search_engines.retriever_registry.retriever_registry"
            ) as mock_registry,
        ):
            quick_summary("test query", retrievers={"custom": retriever})
            mock_registry.register_multiple.assert_called()

    def test_calls_analyze_topic(
        self, mock_get_llm, mock_advanced_search_system
    ):
        """Test that analyze_topic is called with query."""
        with patch(
            "local_deep_research.api.research_functions.create_settings_snapshot",
            return_value={},
        ):
            quick_summary("test query")
            mock_advanced_search_system.analyze_topic.assert_called_once_with(
                "test query"
            )

    def test_handles_empty_results(self, mock_get_llm):
        """Test handling when analyze_topic returns empty results."""
        mock_system = MagicMock()
        mock_system.analyze_topic.return_value = {}

        with (
            patch(
                "local_deep_research.api.research_functions.AdvancedSearchSystem",
                return_value=mock_system,
            ),
            patch(
                "local_deep_research.api.research_functions.create_settings_snapshot",
                return_value={},
            ),
        ):
            result = quick_summary("test query")
            assert (
                result["summary"] == "Unable to generate summary for the query."
            )

    def test_creates_settings_snapshot_when_not_provided(
        self, mock_get_llm, mock_advanced_search_system
    ):
        """Test that settings snapshot is created if not provided."""
        with patch(
            "local_deep_research.api.research_functions.create_settings_snapshot",
            return_value={},
        ) as mock_create:
            quick_summary("test query", provider="openai", temperature=0.5)
            mock_create.assert_called_once()

    def test_uses_provided_settings_snapshot(
        self, mock_get_llm, mock_advanced_search_system
    ):
        """Test that provided settings_snapshot is used."""
        # Create a valid settings snapshot with required keys
        custom_snapshot = {
            # "library" is always available without extra engine config
            # (searxng needs an instance URL before the factory accepts it).
            "search.tool": {"value": "library"},
            "custom": "settings",
        }
        with patch(
            "local_deep_research.api.research_functions.create_settings_snapshot",
            return_value=custom_snapshot,
        ) as mock_create:
            quick_summary("test query", settings_snapshot=custom_snapshot)
            # Should not call create_settings_snapshot when snapshot is provided
            mock_create.assert_not_called()


class TestGenerateReport:
    """Tests for generate_report function."""

    def test_returns_dict(
        self, mock_get_llm, mock_advanced_search_system, mock_report_generator
    ):
        """Test that function returns a dictionary."""
        with (
            patch(
                "local_deep_research.api.research_functions.IntegratedReportGenerator",
                return_value=mock_report_generator,
            ),
            patch(
                "local_deep_research.api.research_functions.create_settings_snapshot",
                return_value={},
            ),
        ):
            result = generate_report("test query")
            assert isinstance(result, dict)

    def test_returns_content_key(
        self, mock_get_llm, mock_advanced_search_system, mock_report_generator
    ):
        """Test that result contains 'content' key."""
        with (
            patch(
                "local_deep_research.api.research_functions.IntegratedReportGenerator",
                return_value=mock_report_generator,
            ),
            patch(
                "local_deep_research.api.research_functions.create_settings_snapshot",
                return_value={},
            ),
        ):
            result = generate_report("test query")
            assert "content" in result

    def test_calls_analyze_topic(
        self, mock_get_llm, mock_advanced_search_system, mock_report_generator
    ):
        """Test that analyze_topic is called."""
        with (
            patch(
                "local_deep_research.api.research_functions.IntegratedReportGenerator",
                return_value=mock_report_generator,
            ),
            patch(
                "local_deep_research.api.research_functions.create_settings_snapshot",
                return_value={},
            ),
        ):
            generate_report("test query")
            mock_advanced_search_system.analyze_topic.assert_called_once_with(
                "test query"
            )

    def test_calls_report_generator(
        self, mock_get_llm, mock_advanced_search_system, mock_report_generator
    ):
        """Test that report generator is called."""
        with (
            patch(
                "local_deep_research.api.research_functions.IntegratedReportGenerator",
                return_value=mock_report_generator,
            ),
            patch(
                "local_deep_research.api.research_functions.create_settings_snapshot",
                return_value={},
            ),
        ):
            generate_report("test query")
            mock_report_generator.generate_report.assert_called_once()

    def test_saves_to_file(
        self,
        mock_get_llm,
        mock_advanced_search_system,
        mock_report_generator,
        tmp_path,
    ):
        """Test saving report to file."""
        output_file = str(tmp_path / "report.md")
        with (
            patch(
                "local_deep_research.api.research_functions.IntegratedReportGenerator",
                return_value=mock_report_generator,
            ),
            patch(
                "local_deep_research.api.research_functions.create_settings_snapshot",
                return_value={},
            ),
            patch(
                "local_deep_research.security.file_write_verifier.write_file_verified"
            ) as mock_write,
        ):
            result = generate_report("test query", output_file=output_file)
            mock_write.assert_called_once()
            assert result["file_path"] == output_file

    def test_sets_progress_callback(
        self, mock_get_llm, mock_advanced_search_system, mock_report_generator
    ):
        """Test that progress callback is set."""
        callback = MagicMock()
        with (
            patch(
                "local_deep_research.api.research_functions.IntegratedReportGenerator",
                return_value=mock_report_generator,
            ),
            patch(
                "local_deep_research.api.research_functions.create_settings_snapshot",
                return_value={},
            ),
        ):
            generate_report("test query", progress_callback=callback)
            mock_advanced_search_system.set_progress_callback.assert_called_once_with(
                callback
            )

    def test_searches_per_section(
        self, mock_get_llm, mock_advanced_search_system, mock_report_generator
    ):
        """Test that searches_per_section is passed to generator."""
        with (
            patch(
                "local_deep_research.api.research_functions.IntegratedReportGenerator"
            ) as mock_gen_class,
            patch(
                "local_deep_research.api.research_functions.create_settings_snapshot",
                return_value={},
            ),
        ):
            mock_gen_class.return_value = mock_report_generator
            generate_report("test query", searches_per_section=5)
            call_kwargs = mock_gen_class.call_args[1]
            assert call_kwargs["searches_per_section"] == 5


class TestDetailedResearch:
    """Tests for detailed_research function."""

    def test_returns_dict(self, mock_get_llm, mock_advanced_search_system):
        """Test that function returns a dictionary."""
        result = detailed_research("test query")
        assert isinstance(result, dict)

    def test_returns_query(self, mock_get_llm, mock_advanced_search_system):
        """Test that result contains 'query' key."""
        result = detailed_research("test query")
        assert result["query"] == "test query"

    def test_returns_research_id(
        self, mock_get_llm, mock_advanced_search_system
    ):
        """Test that result contains 'research_id' key."""
        result = detailed_research("test query")
        assert "research_id" in result

    def test_uses_provided_research_id(
        self, mock_get_llm, mock_advanced_search_system
    ):
        """Test that provided research_id is used."""
        result = detailed_research("test query", research_id="custom-id")
        assert result["research_id"] == "custom-id"

    def test_generates_research_id(
        self, mock_get_llm, mock_advanced_search_system
    ):
        """Test that research_id is generated if not provided."""
        result = detailed_research("test query")
        # Should be a valid UUID
        assert len(result["research_id"]) == 36

    def test_returns_summary(self, mock_get_llm, mock_advanced_search_system):
        """Test that result contains 'summary' key."""
        result = detailed_research("test query")
        assert "summary" in result

    def test_returns_findings(self, mock_get_llm, mock_advanced_search_system):
        """Test that result contains 'findings' key."""
        result = detailed_research("test query")
        assert "findings" in result

    def test_returns_metadata(self, mock_get_llm, mock_advanced_search_system):
        """Test that result contains 'metadata' key."""
        result = detailed_research("test query")
        assert "metadata" in result
        assert "timestamp" in result["metadata"]

    def test_calls_analyze_topic(
        self, mock_get_llm, mock_advanced_search_system
    ):
        """Test that analyze_topic is called with query."""
        detailed_research("test query")
        mock_advanced_search_system.analyze_topic.assert_called_once_with(
            "test query"
        )


class TestAnalyzeDocuments:
    """Tests for analyze_documents function."""

    def test_returns_dict(self, mock_get_llm, mock_get_search):
        """Test that function returns a dictionary."""
        result = analyze_documents("test query", "test_collection")
        assert isinstance(result, dict)

    def test_returns_summary(self, mock_get_llm, mock_get_search):
        """Test that result contains 'summary' key."""
        result = analyze_documents("test query", "test_collection")
        assert "summary" in result

    def test_returns_documents(self, mock_get_llm, mock_get_search):
        """Test that result contains 'documents' key."""
        result = analyze_documents("test query", "test_collection")
        assert "documents" in result

    def test_collection_not_found(self, mock_get_llm):
        """Test handling when collection is not found."""
        with patch(
            "local_deep_research.api.research_functions.get_search",
            return_value=None,
        ):
            result = analyze_documents("test query", "nonexistent_collection")
            assert "Error" in result["summary"]
            assert result["documents"] == []

    def test_no_results_found(self, mock_get_llm):
        """Test handling when no documents are found."""
        mock_search = MagicMock()
        mock_search.run.return_value = []

        with patch(
            "local_deep_research.api.research_functions.get_search",
            return_value=mock_search,
        ):
            result = analyze_documents("test query", "test_collection")
            assert "No documents found" in result["summary"]
            assert result["documents"] == []

    def test_sets_max_results(self, mock_get_llm):
        """Test that max_results is set on search engine."""
        mock_search = MagicMock()
        mock_search.run.return_value = []

        with patch(
            "local_deep_research.api.research_functions.get_search",
            return_value=mock_search,
        ):
            analyze_documents("test query", "test_collection", max_results=50)
            assert mock_search.max_results == 50

    def test_uses_custom_temperature(self, mock_get_search):
        """Test that custom temperature is passed to get_llm."""
        with patch(
            "local_deep_research.api.research_functions.get_llm"
        ) as mock_llm:
            # Create a mock LLM that returns a proper response
            mock_llm_instance = MagicMock()
            mock_llm_instance.invoke.return_value = MagicMock(
                content="Summary text"
            )
            mock_llm.return_value = mock_llm_instance
            analyze_documents("test query", "test_collection", temperature=0.3)
            mock_llm.assert_called_once()
            assert mock_llm.call_args[1]["temperature"] == 0.3

    def test_saves_to_file(self, mock_get_llm, tmp_path):
        """Test saving analysis to file."""
        output_file = str(tmp_path / "analysis.md")
        mock_search = MagicMock()
        mock_search.run.return_value = [
            {
                "title": "Doc 1",
                "link": "https://example.com",
                "content": "Content",
            }
        ]

        with (
            patch(
                "local_deep_research.api.research_functions.get_search",
                return_value=mock_search,
            ),
            patch(
                "local_deep_research.security.file_write_verifier.write_file_verified"
            ) as mock_write,
        ):
            result = analyze_documents(
                "test query", "test_collection", output_file=output_file
            )
            mock_write.assert_called_once()
            assert result["file_path"] == output_file

    def test_returns_collection_name(self, mock_get_llm, mock_get_search):
        """Test that result includes collection name."""
        result = analyze_documents("test query", "my_collection")
        assert result["collection"] == "my_collection"

    def test_returns_document_count(self, mock_get_llm, mock_get_search):
        """Test that result includes document count."""
        result = analyze_documents("test query", "test_collection")
        assert "document_count" in result
