"""High-value tests for findings module: topic.py and repository.py.

Covers Topic source management, TopicGraph operations,
FindingsRepository CRUD, and format_links helper.
"""

from unittest.mock import MagicMock

from local_deep_research.advanced_search_system.findings.topic import (
    Topic,
    TopicGraph,
)
from local_deep_research.advanced_search_system.findings.repository import (
    FindingsRepository,
    format_links,
)


class TestTopicSourceManagement:
    """Test Topic source add/reject/update operations."""

    def _make_topic(self):
        return Topic(
            id="t1",
            title="Test Topic",
            lead_source={"url": "http://lead.com", "title": "Lead"},
        )

    def test_add_supporting_source_no_duplicates(self):
        """Same source added twice only appears once."""
        t = self._make_topic()
        src = {"url": "http://a.com"}
        t.add_supporting_source(src)
        t.add_supporting_source(src)
        assert len(t.supporting_sources) == 1

    def test_reject_source_moves_from_supporting(self):
        """Rejecting moves source from supporting to rejected."""
        t = self._make_topic()
        src = {"url": "http://a.com"}
        t.add_supporting_source(src)
        t.reject_source(src)
        assert src not in t.supporting_sources
        assert src in t.rejected_sources

    def test_reject_source_not_in_supporting(self):
        """Rejecting a source not in supporting still adds to rejected."""
        t = self._make_topic()
        src = {"url": "http://new.com"}
        t.reject_source(src)
        assert src in t.rejected_sources

    def test_update_lead_source(self):
        """Old lead becomes supporting, new lead replaces it."""
        t = self._make_topic()
        old_lead = t.lead_source
        new_lead = {"url": "http://new-lead.com"}
        t.update_lead_source(new_lead)
        assert t.lead_source is new_lead
        assert old_lead in t.supporting_sources

    def test_update_lead_removes_from_supporting(self):
        """New lead from supporting is removed from supporting."""
        t = self._make_topic()
        new_lead = {"url": "http://support.com"}
        t.add_supporting_source(new_lead)
        t.update_lead_source(new_lead)
        assert new_lead not in t.supporting_sources
        assert t.lead_source is new_lead

    def test_get_all_sources_includes_lead(self):
        """get_all_sources returns lead + supporting."""
        t = self._make_topic()
        t.add_supporting_source({"url": "http://a.com"})
        all_sources = t.get_all_sources()
        assert len(all_sources) == 2
        assert all_sources[0] == t.lead_source

    def test_to_dict_has_expected_keys(self):
        """to_dict returns all expected keys."""
        t = self._make_topic()
        d = t.to_dict()
        expected = {
            "id",
            "title",
            "lead_source",
            "supporting_sources",
            "rejected_sources",
            "created_at",
            "related_topic_ids",
            "parent_topic_id",
            "child_topic_ids",
        }
        assert set(d.keys()) == expected


class TestTopicGraph:
    """Test TopicGraph operations."""

    def _make_graph(self):
        g = TopicGraph()
        t1 = Topic(id="t1", title="T1", lead_source={"url": "a"})
        t2 = Topic(id="t2", title="T2", lead_source={"url": "b"})
        g.add_topic(t1)
        g.add_topic(t2)
        return g

    def test_add_and_get_topic(self):
        g = self._make_graph()
        assert g.get_topic("t1") is not None
        assert g.get_topic("nonexistent") is None

    def test_link_topics_bidirectional(self):
        """link_topics creates bidirectional relationships."""
        g = self._make_graph()
        g.link_topics("t1", "t2")
        assert "t2" in g.get_topic("t1").related_topic_ids
        assert "t1" in g.get_topic("t2").related_topic_ids

    def test_link_topics_no_duplicates(self):
        """Linking twice doesn't create duplicate relationships."""
        g = self._make_graph()
        g.link_topics("t1", "t2")
        g.link_topics("t1", "t2")
        assert g.get_topic("t1").related_topic_ids.count("t2") == 1

    def test_set_parent_child(self):
        g = self._make_graph()
        g.set_parent_child("t1", "t2")
        assert "t2" in g.get_topic("t1").child_topic_ids
        assert g.get_topic("t2").parent_topic_id == "t1"

    def test_get_root_topics(self):
        """Root topics have no parent."""
        g = self._make_graph()
        g.set_parent_child("t1", "t2")
        roots = g.get_root_topics()
        assert len(roots) == 1
        assert roots[0].id == "t1"

    def test_get_related_topics(self):
        g = self._make_graph()
        g.link_topics("t1", "t2")
        related = g.get_related_topics("t1")
        assert len(related) == 1
        assert related[0].id == "t2"

    def test_get_related_topics_nonexistent(self):
        g = self._make_graph()
        assert g.get_related_topics("nonexistent") == []

    def test_merge_topics(self):
        """Merging combines sources and removes topic2."""
        g = self._make_graph()
        g.get_topic("t2").add_supporting_source({"url": "c"})
        merged = g.merge_topics("t1", "t2", new_title="Merged")
        assert merged.title == "Merged"
        assert {"url": "c"} in merged.supporting_sources
        assert g.get_topic("t2") is None

    def test_merge_nonexistent_returns_none(self):
        g = self._make_graph()
        assert g.merge_topics("t1", "nonexistent") is None

    def test_to_dict(self):
        g = self._make_graph()
        d = g.to_dict()
        assert "t1" in d
        assert "t2" in d


class TestFormatLinks:
    """Test format_links helper function."""

    def test_formats_correctly(self):
        links = [
            {"title": "Title A", "url": "http://a.com"},
            {"title": "Title B", "url": "http://b.com"},
        ]
        result = format_links(links)
        assert "1. Title A" in result
        assert "2. Title B" in result
        assert "http://a.com" in result

    def test_empty_list(self):
        assert format_links([]) == ""


class TestFindingsRepository:
    """Test FindingsRepository CRUD operations."""

    def _make_repo(self):
        model = MagicMock()
        return FindingsRepository(model=model)

    def test_add_string_finding(self):
        """String finding is converted to dict."""
        repo = self._make_repo()
        repo.add_finding("query1", "finding text")
        findings = repo.get_findings("query1")
        assert len(findings) == 1
        assert findings[0]["content"] == "finding text"
        assert findings[0]["phase"] == "Synthesis"

    def test_add_dict_finding(self):
        """Dict finding is stored as-is."""
        repo = self._make_repo()
        finding = {"phase": "Phase 1", "content": "result"}
        repo.add_finding("query1", finding)
        assert repo.get_findings("query1")[0]["content"] == "result"

    def test_final_synthesis_creates_extra_entry(self):
        """Final synthesis finding creates a _synthesis key."""
        repo = self._make_repo()
        finding = {"phase": "Final synthesis", "content": "final result"}
        repo.add_finding("query1", finding)
        assert "query1_synthesis" in repo.findings

    def test_get_findings_nonexistent_returns_empty(self):
        repo = self._make_repo()
        assert repo.get_findings("nonexistent") == []

    def test_clear_findings(self):
        repo = self._make_repo()
        repo.add_finding("q1", "finding")
        repo.clear_findings("q1")
        assert repo.get_findings("q1") == []

    def test_clear_findings_nonexistent_no_error(self):
        """Clearing nonexistent query doesn't raise."""
        repo = self._make_repo()
        repo.clear_findings("nonexistent")  # Should not raise

    def test_add_documents(self):
        repo = self._make_repo()
        docs = [MagicMock(), MagicMock()]
        repo.add_documents(docs)
        assert len(repo.documents) == 2

    def test_set_questions_by_iteration(self):
        repo = self._make_repo()
        questions = {1: ["Q1a", "Q1b"], 2: ["Q2a"]}
        repo.set_questions_by_iteration(questions)
        assert repo.questions_by_iteration == questions
        # Verify it's a copy, not the same reference
        assert repo.questions_by_iteration is not questions
