"""
Tests for the no-sources guard in StandardCitationHandler.

When all searches return nothing (no matches or engine errors), the
handler must NOT invoke the LLM: prompting for a cited answer with an
empty sources section makes the model fabricate references from its
training data (e.g. invented blog posts). See #4480.
"""

from unittest.mock import MagicMock


def _make_handler(settings_snapshot=None):
    from local_deep_research.citation_handlers.standard_citation_handler import (
        StandardCitationHandler,
    )

    mock_llm = MagicMock()
    mock_llm.invoke.return_value = MagicMock(content="response [1]")
    return StandardCitationHandler(
        llm=mock_llm, settings_snapshot=settings_snapshot or {}
    ), mock_llm


class TestAnalyzeInitialNoSourcesGuard:
    def test_empty_results_skip_llm(self):
        """No documents -> no LLM call, explicit no-sources content."""
        handler, mock_llm = _make_handler()

        result = handler.analyze_initial("test query", [])

        mock_llm.invoke.assert_not_called()
        assert result["documents"] == []
        assert "No sources were found" in result["content"]

    def test_string_results_skip_llm(self):
        """String input produces no documents -> same guard applies."""
        handler, mock_llm = _make_handler()

        result = handler.analyze_initial("test query", "unstructured text")

        mock_llm.invoke.assert_not_called()
        assert "No sources were found" in result["content"]

    def test_nonempty_results_invoke_llm(self):
        handler, mock_llm = _make_handler()
        results = [
            {"full_content": "content", "link": "http://a.com", "title": "A"},
        ]

        result = handler.analyze_initial("test query", results)

        mock_llm.invoke.assert_called_once()
        assert len(result["documents"]) == 1


class TestAnalyzeFollowupNoSourcesGuard:
    def test_empty_results_and_no_knowledge_skip_llm(self):
        """Neither sources nor previous knowledge -> refuse synthesis."""
        handler, mock_llm = _make_handler()

        result = handler.analyze_followup("question", [], "", 0)

        mock_llm.invoke.assert_not_called()
        assert result["documents"] == []
        assert "No sources were found" in result["content"]

    def test_empty_results_skip_fact_check_too(self):
        """The fact-check LLM pass is also skipped by the guard."""
        handler, mock_llm = _make_handler(
            settings_snapshot={"general.enable_fact_checking": True}
        )

        handler.analyze_followup("question", [], "", 0)

        mock_llm.invoke.assert_not_called()

    def test_empty_results_with_knowledge_invoke_llm(self):
        """Previous knowledge alone is a legitimate citation source."""
        handler, mock_llm = _make_handler()

        result = handler.analyze_followup(
            "question", [], "Prior findings: X is true [1].", 0
        )

        mock_llm.invoke.assert_called_once()
        assert "No sources were found" not in result["content"]

    def test_whitespace_knowledge_counts_as_empty(self):
        handler, mock_llm = _make_handler()

        result = handler.analyze_followup("question", [], "   \n", 0)

        mock_llm.invoke.assert_not_called()
        assert "No sources were found" in result["content"]

    def test_nonempty_results_invoke_llm(self):
        handler, mock_llm = _make_handler()
        results = [
            {"full_content": "content", "link": "http://a.com", "title": "A"},
        ]

        result = handler.analyze_followup("question", results, "", 0)

        mock_llm.invoke.assert_called_once()
        assert len(result["documents"]) == 1
