"""Tests for :mod:`mgf.vm.core.configurator.xml_helpers`."""

from __future__ import annotations

import xml.etree.ElementTree as ET

from mgf.vm.core.configurator.xml_helpers import (
    diff_xml,
    ensure,
    find,
    iter_children,
    remove,
    serialize_domain_xml,
    set_attr,
    set_text,
)


def _root() -> ET.Element:
    return ET.fromstring(
        "<domain>"
        "<name>web</name>"
        "<vcpu>2</vcpu>"
        "<devices>"
        "<disk><source file='a.qcow2'/></disk>"
        "<disk><source file='b.qcow2'/></disk>"
        "</devices>"
        "</domain>"
    )


class TestSerialise:
    def test_round_trip(self) -> None:
        root = ET.fromstring("<domain><name>web</name></domain>")
        assert root.tag == "domain"
        assert "<name>web</name>" in serialize_domain_xml(root)


class TestPrimitives:
    def test_find_hit_and_miss(self) -> None:
        root = _root()
        assert find(root, "name") is not None
        assert find(root, "missing") is None

    def test_ensure_creates_missing(self) -> None:
        root = _root()
        node = ensure(root, "cpu/topology")
        node.set("sockets", "1")
        topo = root.find("cpu/topology")
        assert topo is not None
        assert topo.get("sockets") == "1"

    def test_ensure_descends_existing(self) -> None:
        root = _root()
        node = ensure(root, "devices")
        assert node.tag == "devices"
        # No duplicate <devices> created.
        assert len(root.findall("devices")) == 1

    def test_set_text_creates(self) -> None:
        root = _root()
        set_text(root, "title", "my vm")
        assert root.findtext("title") == "my vm"

    def test_set_text_overwrites(self) -> None:
        root = _root()
        set_text(root, "vcpu", "8")
        assert root.findtext("vcpu") == "8"

    def test_set_attr(self) -> None:
        root = _root()
        set_attr(root, "vcpu", "current", "4")
        vcpu = root.find("vcpu")
        assert vcpu is not None
        assert vcpu.get("current") == "4"

    def test_remove_hit(self) -> None:
        root = _root()
        assert remove(root, "vcpu") is True
        assert root.find("vcpu") is None

    def test_remove_missing_returns_false(self) -> None:
        root = _root()
        assert remove(root, "bogus") is False

    def test_remove_missing_parent_returns_false(self) -> None:
        root = _root()
        assert remove(root, "nope/child") is False

    def test_remove_nested_removes_first_match(self) -> None:
        root = _root()
        assert remove(root, "devices/disk") is True
        assert len(root.findall("devices/disk")) == 1

    def test_iter_children(self) -> None:
        root = _root()
        disks = iter_children(root, "devices/disk")
        assert len(disks) == 2

    def test_iter_children_empty(self) -> None:
        root = _root()
        assert iter_children(root, "devices/nic") == []


class TestDiffXml:
    def test_identical_produces_empty_diff(self) -> None:
        xml = "<domain><name>web</name></domain>"
        assert diff_xml(xml, xml) == ""

    def test_change_is_visible(self) -> None:
        a = "<domain>\n  <vcpu>2</vcpu>\n</domain>\n"
        b = "<domain>\n  <vcpu>4</vcpu>\n</domain>\n"
        out = diff_xml(a, b)
        assert "before" in out
        assert "after" in out
        assert "-  <vcpu>2</vcpu>" in out
        assert "+  <vcpu>4</vcpu>" in out

    def test_context_lines_respected(self) -> None:
        a = "a\nb\nc\nd\ne\n"
        b = "a\nb\nC\nd\ne\n"
        out = diff_xml(a, b, context_lines=0)
        # No surrounding context lines emitted with n=0.
        assert "-c" in out
        assert "+C" in out
        body = [ln for ln in out.splitlines() if not ln.startswith(("---", "+++", "@@"))]
        # With n=0 the only body lines are the removed/added pair.
        assert body == ["-c", "+C"]
