"""High-value tests for findings/topic.py pure dataclass logic."""

import unittest

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


def _make_topic(topic_id="t1", title="Topic 1", lead=None, **kwargs):
    return Topic(
        id=topic_id,
        title=title,
        lead_source=lead or {"url": f"http://{topic_id}.com", "title": title},
        **kwargs,
    )


class TestTopicDefaults(unittest.TestCase):
    def test_supporting_sources_empty(self):
        t = _make_topic()
        assert t.supporting_sources == []

    def test_rejected_sources_empty(self):
        t = _make_topic()
        assert t.rejected_sources == []

    def test_related_topic_ids_empty(self):
        t = _make_topic()
        assert t.related_topic_ids == []

    def test_parent_topic_id_none(self):
        t = _make_topic()
        assert t.parent_topic_id is None


class TestTopicAddSupportingSource(unittest.TestCase):
    def test_add_new_source(self):
        t = _make_topic()
        src = {"url": "http://new.com", "title": "New"}
        t.add_supporting_source(src)
        assert src in t.supporting_sources

    def test_duplicate_not_added(self):
        t = _make_topic()
        src = {"url": "http://new.com", "title": "New"}
        t.add_supporting_source(src)
        t.add_supporting_source(src)
        assert len(t.supporting_sources) == 1


class TestTopicRejectSource(unittest.TestCase):
    def test_moves_from_supporting_to_rejected(self):
        t = _make_topic()
        src = {"url": "http://s.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_not_in_supporting(self):
        t = _make_topic()
        src = {"url": "http://s.com"}
        t.reject_source(src)
        assert src in t.rejected_sources

    def test_duplicate_reject_not_added(self):
        t = _make_topic()
        src = {"url": "http://s.com"}
        t.reject_source(src)
        t.reject_source(src)
        assert len(t.rejected_sources) == 1


class TestTopicUpdateLeadSource(unittest.TestCase):
    def test_old_lead_becomes_supporting(self):
        t = _make_topic()
        old_lead = t.lead_source
        new_lead = {"url": "http://new.com"}
        t.update_lead_source(new_lead)
        assert t.lead_source == new_lead
        assert old_lead in t.supporting_sources

    def test_update_lead_twice_no_duplicate_supporting(self):
        t = _make_topic()
        lead_a = {"url": "http://a.com"}
        lead_b = {"url": "http://b.com"}
        t.update_lead_source(lead_a)
        t.update_lead_source(lead_b)
        # Original lead and lead_a should each appear exactly once in supporting
        supporting_urls = [s.get("url") for s in t.supporting_sources]
        assert supporting_urls.count("http://a.com") == 1

    def test_new_lead_removed_from_supporting(self):
        t = _make_topic()
        src = {"url": "http://s.com"}
        t.add_supporting_source(src)
        t.update_lead_source(src)
        assert t.lead_source == src
        # Should appear exactly once (not in supporting anymore)
        assert src not in t.supporting_sources


class TestTopicGetAllSources(unittest.TestCase):
    def test_includes_lead_and_supporting(self):
        t = _make_topic()
        src = {"url": "http://s.com"}
        t.add_supporting_source(src)
        all_sources = t.get_all_sources()
        assert len(all_sources) == 2
        assert t.lead_source in all_sources
        assert src in all_sources


class TestTopicToDict(unittest.TestCase):
    def test_all_fields_present(self):
        t = _make_topic()
        d = t.to_dict()
        assert d["id"] == "t1"
        assert d["title"] == "Topic 1"
        assert "lead_source" in d
        assert "supporting_sources" in d
        assert "rejected_sources" in d
        assert "created_at" in d
        assert "related_topic_ids" in d
        assert "parent_topic_id" in d
        assert "child_topic_ids" in d

    def test_created_at_is_isoformat(self):
        t = _make_topic()
        d = t.to_dict()
        assert isinstance(d["created_at"], str)
        assert "T" in d["created_at"]  # ISO format has T separator


class TestTopicGraphBasic(unittest.TestCase):
    def test_add_and_get_topic(self):
        g = TopicGraph()
        t = _make_topic()
        g.add_topic(t)
        assert g.get_topic("t1") is t

    def test_get_missing_returns_none(self):
        g = TopicGraph()
        assert g.get_topic("nonexistent") is None

    def test_empty_graph(self):
        g = TopicGraph()
        assert g.topics == {}


class TestTopicGraphLinkTopics(unittest.TestCase):
    def test_bidirectional_link(self):
        g = TopicGraph()
        t1 = _make_topic("t1")
        t2 = _make_topic("t2")
        g.add_topic(t1)
        g.add_topic(t2)
        g.link_topics("t1", "t2")
        assert "t2" in t1.related_topic_ids
        assert "t1" in t2.related_topic_ids

    def test_no_duplicate_links(self):
        g = TopicGraph()
        t1 = _make_topic("t1")
        t2 = _make_topic("t2")
        g.add_topic(t1)
        g.add_topic(t2)
        g.link_topics("t1", "t2")
        g.link_topics("t1", "t2")
        assert t1.related_topic_ids.count("t2") == 1

    def test_invalid_topic_ids(self):
        g = TopicGraph()
        t1 = _make_topic("t1")
        g.add_topic(t1)
        g.link_topics("t1", "missing")  # Should not raise
        assert t1.related_topic_ids == []  # No mutation


class TestTopicGraphParentChild(unittest.TestCase):
    def test_parent_child_set(self):
        g = TopicGraph()
        parent = _make_topic("p1")
        child = _make_topic("c1")
        g.add_topic(parent)
        g.add_topic(child)
        g.set_parent_child("p1", "c1")
        assert "c1" in parent.child_topic_ids
        assert child.parent_topic_id == "p1"

    def test_invalid_ids(self):
        g = TopicGraph()
        p1 = _make_topic("p1")
        g.add_topic(p1)
        g.set_parent_child("p1", "missing")  # Should not raise
        assert p1.child_topic_ids == []  # No mutation


class TestTopicGraphRootTopics(unittest.TestCase):
    def test_root_topics_no_parent(self):
        g = TopicGraph()
        t1 = _make_topic("t1")
        t2 = _make_topic("t2")
        g.add_topic(t1)
        g.add_topic(t2)
        g.set_parent_child("t1", "t2")
        roots = g.get_root_topics()
        assert len(roots) == 1
        assert roots[0].id == "t1"


class TestTopicGraphGetRelated(unittest.TestCase):
    def test_returns_related_topics(self):
        g = TopicGraph()
        t1 = _make_topic("t1")
        t2 = _make_topic("t2")
        t3 = _make_topic("t3")
        g.add_topic(t1)
        g.add_topic(t2)
        g.add_topic(t3)
        g.link_topics("t1", "t2")
        g.link_topics("t1", "t3")
        related = g.get_related_topics("t1")
        assert len(related) == 2

    def test_missing_topic_returns_empty(self):
        g = TopicGraph()
        assert g.get_related_topics("missing") == []


class TestTopicGraphMerge(unittest.TestCase):
    def test_merge_combines_sources(self):
        g = TopicGraph()
        t1 = _make_topic("t1")
        t2 = _make_topic("t2")
        t2.add_supporting_source({"url": "http://extra.com"})
        g.add_topic(t1)
        g.add_topic(t2)
        t2_lead = t2.lead_source
        merged = g.merge_topics("t1", "t2")
        assert merged is t1
        assert "t2" not in g.topics
        # t2's lead_source must end up in t1's supporting sources
        assert t2_lead in t1.supporting_sources
        # t2's supporting sources are merged into t1
        assert any(
            s.get("url") == "http://extra.com" for s in t1.supporting_sources
        )

    def test_merge_with_custom_title(self):
        g = TopicGraph()
        t1 = _make_topic("t1")
        t2 = _make_topic("t2")
        g.add_topic(t1)
        g.add_topic(t2)
        merged = g.merge_topics("t1", "t2", new_title="Merged")
        assert merged.title == "Merged"

    def test_merge_invalid_ids(self):
        g = TopicGraph()
        g.add_topic(_make_topic("t1"))
        assert g.merge_topics("t1", "missing") is None

    def test_merge_updates_references(self):
        g = TopicGraph()
        t1 = _make_topic("t1")
        t2 = _make_topic("t2")
        t3 = _make_topic("t3")
        g.add_topic(t1)
        g.add_topic(t2)
        g.add_topic(t3)
        g.link_topics("t2", "t3")
        g.merge_topics("t1", "t2")
        # t3 should now reference t1 instead of t2
        assert "t1" in t3.related_topic_ids
        assert "t2" not in t3.related_topic_ids


class TestTopicGraphToDict(unittest.TestCase):
    def test_serializes_all_topics(self):
        g = TopicGraph()
        g.add_topic(_make_topic("t1"))
        g.add_topic(_make_topic("t2"))
        d = g.to_dict()
        assert "t1" in d
        assert "t2" in d
        assert d["t1"]["id"] == "t1"


if __name__ == "__main__":
    unittest.main()
