"""
Pure-logic tests for FindingsRepository.synthesize_findings.

Tests old_formatting branch (pure logic, no LLM), knowledge assembly,
and truncation logic — LLM is mocked only for truncation verification.
"""

from unittest.mock import Mock, patch

from local_deep_research.advanced_search_system.findings.repository import (
    FindingsRepository,
)


def _make_repo(questions_by_iteration=None):
    """Build a FindingsRepository with a mocked model."""
    model = Mock()
    model.invoke = Mock(return_value="synthesized answer")
    repo = FindingsRepository(model=model)
    if questions_by_iteration is not None:
        repo.questions_by_iteration = questions_by_iteration
    return repo


def _get_kwarg(mock_call, key):
    """Extract a keyword argument from a mock call, falling back to positional."""
    kwargs = mock_call[1]
    if key in kwargs:
        return kwargs[key]
    # Positional fallback based on format_findings(findings_list, synthesized_content, ...)
    pos_map = {
        "findings_list": 0,
        "synthesized_content": 1,
        "questions_by_iteration": 2,
    }
    return mock_call[0][pos_map[key]]


# ---------------------------------------------------------------------------
# old_formatting=True branch
# ---------------------------------------------------------------------------


class TestOldFormattingBranch:
    """Verify old_formatting=True exercises pure-logic formatting path."""

    def test_dict_findings(self):
        """Dict findings are passed through to format_findings."""
        repo = _make_repo(questions_by_iteration={1: ["Q1"]})
        findings = [
            {"phase": "Phase 1", "content": "Finding content A"},
            {"phase": "Phase 2", "content": "Finding content B"},
        ]
        with patch(
            "local_deep_research.advanced_search_system.findings.repository.format_findings"
        ) as mock_format:
            mock_format.return_value = "formatted output"
            result = repo.synthesize_findings(
                query="test query",
                sub_queries=["sq1"],
                findings=findings,
                old_formatting=True,
            )
        assert result == "formatted output"
        findings_list = _get_kwarg(mock_format.call_args, "findings_list")
        assert findings_list == findings

    def test_string_findings_converted(self):
        """String findings are converted to dicts with phase labels."""
        repo = _make_repo(questions_by_iteration={1: ["Q1"]})
        findings = ["Finding A", "Finding B"]
        with patch(
            "local_deep_research.advanced_search_system.findings.repository.format_findings"
        ) as mock_format:
            mock_format.return_value = "formatted output"
            repo.synthesize_findings(
                query="test query",
                sub_queries=["sq1"],
                findings=findings,
                old_formatting=True,
            )
        findings_list = _get_kwarg(mock_format.call_args, "findings_list")
        assert findings_list[0]["phase"] == "Finding 1"
        assert findings_list[0]["content"] == "Finding A"
        assert findings_list[1]["phase"] == "Finding 2"
        assert findings_list[1]["content"] == "Finding B"

    def test_mixed_findings(self):
        """Mix of dict and string findings both handled correctly."""
        repo = _make_repo(questions_by_iteration={})
        findings = [
            {"phase": "Explicit", "content": "Dict content"},
            "String content",
        ]
        with patch(
            "local_deep_research.advanced_search_system.findings.repository.format_findings"
        ) as mock_format:
            mock_format.return_value = "formatted"
            repo.synthesize_findings(
                query="q",
                sub_queries=[],
                findings=findings,
                old_formatting=True,
            )
        findings_list = _get_kwarg(mock_format.call_args, "findings_list")
        assert findings_list[0] == {
            "phase": "Explicit",
            "content": "Dict content",
        }
        assert findings_list[1]["phase"] == "Finding 2"
        assert findings_list[1]["content"] == "String content"

    def test_empty_findings_list(self):
        """Empty findings list produces empty findings_list."""
        repo = _make_repo(questions_by_iteration={})
        with patch(
            "local_deep_research.advanced_search_system.findings.repository.format_findings"
        ) as mock_format:
            mock_format.return_value = ""
            repo.synthesize_findings(
                query="q",
                sub_queries=[],
                findings=[],
                old_formatting=True,
            )
        findings_list = _get_kwarg(mock_format.call_args, "findings_list")
        assert findings_list == []


# ---------------------------------------------------------------------------
# Knowledge assembly
# ---------------------------------------------------------------------------


class TestKnowledgeAssembly:
    """Verify accumulated_knowledge building from findings."""

    def test_none_knowledge_builds_from_dict_findings(self):
        """When accumulated_knowledge is None, it's built from finding contents."""
        repo = _make_repo()
        findings = [
            {"content": "Fact A"},
            {"content": "Fact B"},
        ]
        with patch(
            "local_deep_research.advanced_search_system.findings.repository.format_findings"
        ) as mock_format:
            mock_format.return_value = "out"
            repo.synthesize_findings(
                query="q",
                sub_queries=[],
                findings=findings,
                accumulated_knowledge=None,
                old_formatting=True,
            )
        synthesized = _get_kwarg(mock_format.call_args, "synthesized_content")
        assert "Fact A" in synthesized
        assert "Fact B" in synthesized

    def test_provided_knowledge_used_directly(self):
        """When accumulated_knowledge is provided, it's used directly."""
        repo = _make_repo()
        with patch(
            "local_deep_research.advanced_search_system.findings.repository.format_findings"
        ) as mock_format:
            mock_format.return_value = "out"
            repo.synthesize_findings(
                query="q",
                sub_queries=[],
                findings=[{"content": "ignored"}],
                accumulated_knowledge="Pre-built knowledge",
                old_formatting=True,
            )
        synthesized = _get_kwarg(mock_format.call_args, "synthesized_content")
        assert synthesized == "Pre-built knowledge"

    def test_findings_without_content_key_skipped(self):
        """Dict findings without 'content' key are skipped in assembly."""
        repo = _make_repo()
        findings = [
            {"other_key": "no content"},
            {"content": "Has content"},
        ]
        with patch(
            "local_deep_research.advanced_search_system.findings.repository.format_findings"
        ) as mock_format:
            mock_format.return_value = "out"
            repo.synthesize_findings(
                query="q",
                sub_queries=[],
                findings=findings,
                accumulated_knowledge=None,
                old_formatting=True,
            )
        synthesized = _get_kwarg(mock_format.call_args, "synthesized_content")
        assert "Has content" in synthesized
        assert "no content" not in synthesized


# ---------------------------------------------------------------------------
# Truncation logic (non-old_formatting path)
# ---------------------------------------------------------------------------


class TestTruncationLogic:
    """Verify knowledge truncation boundaries.

    Truncation requires two conditions:
    1. estimated_tokens (len/4) > max_safe_tokens (12000), i.e. len > 48000
    2. len(current_knowledge) > 24000 (always true when #1 is true)
    """

    def test_under_token_limit_no_truncation(self):
        """Content under 48000 chars (~12000 tokens) is not truncated."""
        repo = _make_repo()
        content = "a" * 47999
        findings = [{"content": content}]
        repo.synthesize_findings(
            query="q",
            sub_queries=["sq1"],
            findings=findings,
            old_formatting=False,
        )
        call_args = repo.model.invoke.call_args
        prompt = call_args[0][0] if call_args[0] else str(call_args)
        assert "[...content truncated" not in prompt

    def test_exactly_at_token_limit_no_truncation(self):
        """Content at exactly 48000 chars (12000 tokens) is not truncated."""
        repo = _make_repo()
        content = "a" * 48000
        findings = [{"content": content}]
        repo.synthesize_findings(
            query="q",
            sub_queries=["sq1"],
            findings=findings,
            old_formatting=False,
        )
        call_args = repo.model.invoke.call_args
        prompt = call_args[0][0] if call_args[0] else str(call_args)
        assert "[...content truncated" not in prompt

    def test_over_token_limit_triggers_truncation(self):
        """Content over 48000 chars triggers truncation with marker."""
        repo = _make_repo()
        content = "a" * 48001
        findings = [{"content": content}]
        repo.synthesize_findings(
            query="q",
            sub_queries=["sq1"],
            findings=findings,
            old_formatting=False,
        )
        call_args = repo.model.invoke.call_args
        prompt = call_args[0][0] if call_args[0] else str(call_args)
        assert "[...content truncated due to length...]" in prompt
