from django.test import TestCase
from components.base import Component


class MockLeaf(Component):
    """A leaf component with no children."""

    def __init__(self, uid="", output=""):
        super().__init__(uid=uid)
        self.output = output

    def render_html(self, **kwargs):
        return self.output


class MockContainer(Component):
    """A container component with children."""

    def __init__(self, uid="", children=None, output=""):
        super().__init__(uid=uid, children=children or [])
        self.output = output

    def render_html(self, **kwargs):
        child_output = "".join(c.render(**kwargs) for c in self.children)
        return f"{self.output}{child_output}"


class GetChildrenTest(TestCase):
    def test_leaf_returns_empty(self):
        leaf = MockLeaf(uid="leaf1")
        self.assertEqual(leaf.get_children(), [])

    def test_container_returns_children(self):
        c1 = MockLeaf(uid="c1")
        c2 = MockLeaf(uid="c2")
        container = MockContainer(uid="root", children=[c1, c2])
        self.assertEqual(container.get_children(), [c1, c2])


class RenderPartialTest(TestCase):
    def test_renders_self_when_uid_matches(self):
        leaf = MockLeaf(uid="target", output="found")
        result = leaf.render_partial("target")
        self.assertEqual(result, "found")

    def test_returns_none_when_no_match(self):
        leaf = MockLeaf(uid="other", output="nope")
        result = leaf.render_partial("target")
        self.assertIsNone(result)

    def test_finds_child_in_tree(self):
        target = MockLeaf(uid="target", output="found")
        sibling = MockLeaf(uid="sibling", output="nope")
        root = MockContainer(uid="root", children=[sibling, target])
        result = root.render_partial("target")
        self.assertEqual(result, "found")

    def test_finds_deeply_nested_child(self):
        target = MockLeaf(uid="deep", output="deep_found")
        inner = MockContainer(uid="inner", children=[target])
        outer = MockContainer(uid="outer", children=[inner])
        result = outer.render_partial("deep")
        self.assertEqual(result, "deep_found")

    def test_returns_none_for_missing_uid(self):
        child = MockLeaf(uid="child", output="x")
        root = MockContainer(uid="root", children=[child])
        result = root.render_partial("nonexistent")
        self.assertIsNone(result)


class PatchTest(TestCase):
    def test_patch_replaces_child(self):
        original = MockLeaf(uid="target", output="old")
        root = MockContainer(uid="root", children=[original])

        root.patch("target", lambda c: MockLeaf(uid="target", output="new"))

        self.assertEqual(root.children[0].output, "new")

    def test_patch_replaces_deeply_nested(self):
        target = MockLeaf(uid="deep", output="old")
        inner = MockContainer(uid="inner", children=[target])
        outer = MockContainer(uid="outer", children=[inner])

        outer.patch("deep", lambda c: MockLeaf(uid="deep", output="patched"))

        self.assertEqual(inner.children[0].output, "patched")

    def test_patch_returns_new_component_when_root_matches(self):
        root = MockLeaf(uid="root", output="old")
        result = root.patch("root", lambda c: MockLeaf(uid="root", output="replaced"))
        self.assertEqual(result.output, "replaced")

    def test_patch_noop_when_uid_not_found(self):
        child = MockLeaf(uid="child", output="original")
        root = MockContainer(uid="root", children=[child])

        root.patch("nonexistent", lambda c: MockLeaf(uid="x", output="y"))

        self.assertEqual(child.output, "original")
        self.assertEqual(len(root.children), 1)

    def test_render_partial_works_after_patch(self):
        target = MockLeaf(uid="target", output="before")
        root = MockContainer(uid="root", children=[target])

        root.patch("target", lambda c: MockLeaf(uid="target", output="after"))

        result = root.render_partial("target")
        self.assertEqual(result, "after")
