"""Tests for diff module: schematic comparison."""



from mcltspice.diff import (
    ComponentChange,
    SchematicDiff,
    _diff_components,
    _diff_directives,
    _diff_nets,
    _diff_wires,
    diff_schematics,
)
from mcltspice.schematic import Component, Flag, Schematic, Text, Wire, write_schematic


def _make_schematic(**kwargs) -> Schematic:
    """Helper to build a Schematic with overrides."""
    sch = Schematic()
    sch.components = kwargs.get("components", [])
    sch.wires = kwargs.get("wires", [])
    sch.flags = kwargs.get("flags", [])
    sch.texts = kwargs.get("texts", [])
    return sch


class TestDiffComponents:
    def test_added_component(self):
        sch_a = _make_schematic(components=[
            Component(name="R1", symbol="res", x=160, y=80, rotation=0, mirror=False,
                      attributes={"Value": "1k"}),
        ])
        sch_b = _make_schematic(components=[
            Component(name="R1", symbol="res", x=160, y=80, rotation=0, mirror=False,
                      attributes={"Value": "1k"}),
            Component(name="C1", symbol="cap", x=256, y=176, rotation=0, mirror=False,
                      attributes={"Value": "100n"}),
        ])
        changes = _diff_components(sch_a, sch_b)
        added = [c for c in changes if c.change_type == "added"]
        assert len(added) == 1
        assert added[0].name == "C1"

    def test_removed_component(self):
        sch_a = _make_schematic(components=[
            Component(name="R1", symbol="res", x=160, y=80, rotation=0, mirror=False,
                      attributes={"Value": "1k"}),
            Component(name="R2", symbol="res", x=320, y=80, rotation=0, mirror=False,
                      attributes={"Value": "2.2k"}),
        ])
        sch_b = _make_schematic(components=[
            Component(name="R1", symbol="res", x=160, y=80, rotation=0, mirror=False,
                      attributes={"Value": "1k"}),
        ])
        changes = _diff_components(sch_a, sch_b)
        removed = [c for c in changes if c.change_type == "removed"]
        assert len(removed) == 1
        assert removed[0].name == "R2"

    def test_modified_value(self):
        sch_a = _make_schematic(components=[
            Component(name="R1", symbol="res", x=160, y=80, rotation=0, mirror=False,
                      attributes={"Value": "1k"}),
        ])
        sch_b = _make_schematic(components=[
            Component(name="R1", symbol="res", x=160, y=80, rotation=0, mirror=False,
                      attributes={"Value": "2.2k"}),
        ])
        changes = _diff_components(sch_a, sch_b)
        modified = [c for c in changes if c.change_type == "modified"]
        assert len(modified) == 1
        assert modified[0].old_value == "1k"
        assert modified[0].new_value == "2.2k"

    def test_moved_component(self):
        sch_a = _make_schematic(components=[
            Component(name="R1", symbol="res", x=160, y=80, rotation=0, mirror=False,
                      attributes={"Value": "1k"}),
        ])
        sch_b = _make_schematic(components=[
            Component(name="R1", symbol="res", x=320, y=160, rotation=0, mirror=False,
                      attributes={"Value": "1k"}),
        ])
        changes = _diff_components(sch_a, sch_b)
        assert len(changes) == 1
        assert changes[0].moved is True

    def test_no_changes(self):
        comp = Component(name="R1", symbol="res", x=160, y=80, rotation=0, mirror=False,
                         attributes={"Value": "1k"})
        sch = _make_schematic(components=[comp])
        changes = _diff_components(sch, sch)
        assert len(changes) == 0


class TestDiffDirectives:
    def test_added_directive(self):
        sch_a = _make_schematic(texts=[
            Text(80, 296, ".tran 10m", type="spice"),
        ])
        sch_b = _make_schematic(texts=[
            Text(80, 296, ".tran 10m", type="spice"),
            Text(80, 320, ".meas tran vmax MAX V(out)", type="spice"),
        ])
        changes = _diff_directives(sch_a, sch_b)
        added = [c for c in changes if c.change_type == "added"]
        assert len(added) == 1

    def test_removed_directive(self):
        sch_a = _make_schematic(texts=[
            Text(80, 296, ".tran 10m", type="spice"),
            Text(80, 320, ".op", type="spice"),
        ])
        sch_b = _make_schematic(texts=[
            Text(80, 296, ".tran 10m", type="spice"),
        ])
        changes = _diff_directives(sch_a, sch_b)
        removed = [c for c in changes if c.change_type == "removed"]
        assert len(removed) == 1

    def test_modified_directive(self):
        sch_a = _make_schematic(texts=[
            Text(80, 296, ".tran 10m", type="spice"),
        ])
        sch_b = _make_schematic(texts=[
            Text(80, 296, ".tran 50m", type="spice"),
        ])
        changes = _diff_directives(sch_a, sch_b)
        modified = [c for c in changes if c.change_type == "modified"]
        assert len(modified) == 1
        assert modified[0].old_text == ".tran 10m"
        assert modified[0].new_text == ".tran 50m"


class TestDiffNets:
    def test_added_nets(self):
        sch_a = _make_schematic(flags=[Flag(80, 176, "0")])
        sch_b = _make_schematic(flags=[Flag(80, 176, "0"), Flag(176, 176, "out")])
        added, removed = _diff_nets(sch_a, sch_b)
        assert "out" in added
        assert len(removed) == 0

    def test_removed_nets(self):
        sch_a = _make_schematic(flags=[Flag(80, 176, "0"), Flag(176, 176, "out")])
        sch_b = _make_schematic(flags=[Flag(80, 176, "0")])
        added, removed = _diff_nets(sch_a, sch_b)
        assert "out" in removed
        assert len(added) == 0


class TestDiffWires:
    def test_added_wires(self):
        sch_a = _make_schematic(wires=[Wire(80, 96, 176, 96)])
        sch_b = _make_schematic(wires=[
            Wire(80, 96, 176, 96),
            Wire(176, 176, 272, 176),
        ])
        added, removed = _diff_wires(sch_a, sch_b)
        assert added == 1
        assert removed == 0

    def test_removed_wires(self):
        sch_a = _make_schematic(wires=[
            Wire(80, 96, 176, 96),
            Wire(176, 176, 272, 176),
        ])
        sch_b = _make_schematic(wires=[Wire(80, 96, 176, 96)])
        added, removed = _diff_wires(sch_a, sch_b)
        assert added == 0
        assert removed == 1


class TestSchematicDiff:
    def test_has_changes_false(self):
        diff = SchematicDiff()
        assert diff.has_changes is False

    def test_has_changes_true(self):
        diff = SchematicDiff(wires_added=1)
        assert diff.has_changes is True

    def test_summary_no_changes(self):
        diff = SchematicDiff()
        assert "No changes" in diff.summary()

    def test_to_dict(self):
        diff = SchematicDiff(
            component_changes=[
                ComponentChange(name="R1", change_type="modified",
                                old_value="1k", new_value="2.2k")
            ]
        )
        d = diff.to_dict()
        assert d["has_changes"] is True
        assert len(d["component_changes"]) == 1


class TestDiffSchematicsIntegration:
    """Write two schematics to disk and compare them end-to-end."""

    def test_full_diff(self, valid_schematic, tmp_path):
        # Create "before" schematic
        path_a = tmp_path / "before.asc"
        write_schematic(valid_schematic, path_a)

        # Create "after" schematic with a modified R1 value
        modified = Schematic()
        modified.flags = list(valid_schematic.flags)
        modified.wires = list(valid_schematic.wires)
        modified.texts = list(valid_schematic.texts)
        modified.components = []
        for comp in valid_schematic.components:
            if comp.name == "R1":
                new_comp = Component(
                    name=comp.name, symbol=comp.symbol,
                    x=comp.x, y=comp.y, rotation=comp.rotation, mirror=comp.mirror,
                    attributes={"Value": "4.7k"},
                )
                modified.components.append(new_comp)
            else:
                modified.components.append(comp)

        path_b = tmp_path / "after.asc"
        write_schematic(modified, path_b)

        diff = diff_schematics(path_a, path_b)
        assert diff.has_changes
        r1_changes = [c for c in diff.component_changes if c.name == "R1"]
        assert len(r1_changes) == 1
        assert r1_changes[0].old_value == "1k"
        assert r1_changes[0].new_value == "4.7k"
