"""Edge-case tests for report_generator — structure parsing boundaries."""

from unittest.mock import Mock

import pytest

from local_deep_research.report_generator import IntegratedReportGenerator


@pytest.fixture
def mock_search_system():
    """Create a mock search system."""
    mock = Mock()
    mock.all_links_of_system = []
    return mock


@pytest.fixture
def generator(mock_llm, mock_search_system, monkeypatch):
    """Create a report generator with mocked dependencies."""
    monkeypatch.setattr(
        "local_deep_research.report_generator.get_llm", lambda: mock_llm
    )
    return IntegratedReportGenerator(
        search_system=mock_search_system, llm=mock_llm
    )


class TestDetermineReportStructureNoDelimiters:
    """Verify LLM response without STRUCTURE/END_STRUCTURE returns empty structure."""

    def test_no_structure_delimiters(self, generator):
        """Response without STRUCTURE markers → empty structure list."""
        generator.model.invoke.return_value = Mock(
            content="Here is my analysis of the topic. It covers many areas."
        )

        findings = {"current_knowledge": "Some knowledge about the topic."}
        structure = generator._determine_report_structure(
            findings, "test query"
        )

        # No numbered section lines → empty structure
        assert structure == []


class TestDetermineReportStructureSectionWithoutPeriod:
    """A numbered section line without a period is skipped gracefully (no IndexError)."""

    def test_section_without_period_is_skipped(self, generator):
        """'1 Section Name' (no period) is skipped rather than raising IndexError."""
        generator.model.invoke.return_value = Mock(
            content=("STRUCTURE\n1 Section Without Period\nEND_STRUCTURE")
        )

        findings = {"current_knowledge": "Some knowledge."}
        # Previously line.split(".")[1] raised IndexError; now the line is skipped.
        structure = generator._determine_report_structure(
            findings, "test query"
        )
        assert structure == []

    def test_valid_and_dotless_lines_mixed(self, generator):
        """A valid numbered line still parses even when a dotless one precedes it."""
        generator.model.invoke.return_value = Mock(
            content=(
                "STRUCTURE\n1 No Period Here\n2. Real Section\nEND_STRUCTURE"
            )
        )

        findings = {"current_knowledge": "Some knowledge."}
        structure = generator._determine_report_structure(
            findings, "test query"
        )
        assert len(structure) == 1
        assert structure[0]["name"] == "Real Section"


class TestDetermineReportStructureSubsectionBeforeAnySection:
    """Verify subsection line before any numbered section doesn't crash."""

    def test_subsection_before_any_section(self, generator):
        """'- Subsection' before any numbered section → current_section is None."""
        generator.model.invoke.return_value = Mock(
            content=(
                "STRUCTURE\n"
                "- Orphan Subsection | some purpose\n"
                "1. Actual Section\n"
                "   - Valid Subsection | valid purpose\n"
                "END_STRUCTURE"
            )
        )

        findings = {"current_knowledge": "Some knowledge."}
        structure = generator._determine_report_structure(
            findings, "test query"
        )

        # The orphan subsection should be safely skipped (current_section guard)
        # Only the actual section with its valid subsection should be parsed
        assert len(structure) == 1
        assert structure[0]["name"] == "Actual Section"
        assert len(structure[0]["subsections"]) == 1
        assert structure[0]["subsections"][0]["name"] == "Valid Subsection"


class TestDetermineReportStructureRemovesSourcesSection:
    """Verify last section named 'References' or 'Sources' is auto-removed."""

    def test_removes_references_section(self, generator):
        """Last section named 'References' should be auto-removed."""
        generator.model.invoke.return_value = Mock(
            content=(
                "STRUCTURE\n"
                "1. Introduction\n"
                "   - Overview | provide overview\n"
                "2. Key Findings\n"
                "   - Results | present results\n"
                "3. References\n"
                "   - Bibliography | list sources\n"
                "END_STRUCTURE"
            )
        )

        findings = {"current_knowledge": "Some knowledge."}
        structure = generator._determine_report_structure(
            findings, "test query"
        )

        # "References" matches source_keywords → removed
        assert len(structure) == 2
        assert structure[0]["name"] == "Introduction"
        assert structure[1]["name"] == "Key Findings"
        # "References" section should be gone
        section_names = [s["name"] for s in structure]
        assert "References" not in section_names

    def test_does_not_remove_non_last_source_section(self, generator):
        """Source-related section that is NOT last should be kept."""
        generator.model.invoke.return_value = Mock(
            content=(
                "STRUCTURE\n"
                "1. Sources Overview\n"
                "   - Data Sources | describe data origins\n"
                "2. Analysis\n"
                "   - Methods | explain methods\n"
                "END_STRUCTURE"
            )
        )

        findings = {"current_knowledge": "Some knowledge."}
        structure = generator._determine_report_structure(
            findings, "test query"
        )

        # "Sources Overview" is first, not last → should NOT be removed
        # "Analysis" is last and doesn't match source keywords → kept
        assert len(structure) == 2
        assert structure[0]["name"] == "Sources Overview"
        assert structure[1]["name"] == "Analysis"
