"""Tests for the inject-Element XML rewrite helpers
(mgf.vm.core.operations.xml_rewrite).

These pass an already-parsed Element (as the adapter would, after
defusedxml parsing) — there is no string-parsing surface in core.
"""

from __future__ import annotations

import xml.etree.ElementTree as ET
from pathlib import Path

import pytest

from mgf.vm.core.exceptions import BackupError, SpecValidationError
from mgf.vm.core.operations.xml_rewrite import (
    find_parent,
    rewrite_clone_domain,
    rewrite_restore_domain,
    strip_extra_whitespace,
)


def _domain(*, name: str = "src", uuid: str | None = "old-uuid") -> ET.Element:
    root = ET.Element("domain")
    n = ET.SubElement(root, "name")
    n.text = name
    if uuid is not None:
        u = ET.SubElement(root, "uuid")
        u.text = uuid
    devices = ET.SubElement(root, "devices")
    disk = ET.SubElement(devices, "disk")
    src = ET.SubElement(disk, "source")
    src.set("file", "/disks/src.qcow2")
    iface = ET.SubElement(devices, "interface")
    mac = ET.SubElement(iface, "mac")
    mac.set("address", "52:54:00:aa:bb:cc")
    return root


# --- find_parent ---------------------------------------------------------


def test_find_parent_returns_immediate_parent() -> None:
    root = _domain()
    mac = root.find("devices/interface/mac")
    assert mac is not None
    parent = find_parent(root, mac)
    assert parent is not None
    assert parent.tag == "interface"


def test_find_parent_returns_none_for_detached_element() -> None:
    root = _domain()
    orphan = ET.Element("orphan")
    assert find_parent(root, orphan) is None


# --- strip_extra_whitespace ---------------------------------------------


def test_strip_extra_whitespace_collapses_blank_line_runs() -> None:
    assert strip_extra_whitespace("<a>\n\n  \n<b/></a>") == "<a>\n<b/></a>"


def test_strip_extra_whitespace_leaves_single_newlines() -> None:
    assert strip_extra_whitespace("<a>\n<b/>\n</a>") == "<a>\n<b/>\n</a>"


# --- rewrite_clone_domain ------------------------------------------------


def test_rewrite_clone_sets_name_uuid_and_disk() -> None:
    root = _domain()
    out = rewrite_clone_domain(
        root,
        new_name="clone",
        new_uuid="new-uuid",
        disk_substitutions={"/disks/src.qcow2": "/disks/clone.qcow2"},
    )
    parsed = ET.fromstring(out)
    assert parsed.findtext("name") == "clone"
    assert parsed.findtext("uuid") == "new-uuid"
    src = parsed.find(".//devices/disk/source")
    assert src is not None
    assert src.get("file") == "/disks/clone.qcow2"


def test_rewrite_clone_strips_mac() -> None:
    root = _domain()
    out = rewrite_clone_domain(root, new_name="c", new_uuid="u", disk_substitutions={})
    parsed = ET.fromstring(out)
    assert parsed.find(".//devices/interface/mac") is None
    # interface element itself is kept
    assert parsed.find(".//devices/interface") is not None


def test_rewrite_clone_leaves_unmatched_disk_untouched() -> None:
    root = _domain()
    out = rewrite_clone_domain(
        root, new_name="c", new_uuid="u", disk_substitutions={"/other": "/x"}
    )
    src = ET.fromstring(out).find(".//devices/disk/source")
    assert src is not None
    assert src.get("file") == "/disks/src.qcow2"


def test_rewrite_clone_inserts_name_when_missing() -> None:
    root = ET.Element("domain")
    ET.SubElement(root, "devices")
    out = rewrite_clone_domain(root, new_name="fresh", new_uuid="u", disk_substitutions={})
    assert ET.fromstring(out).findtext("name") == "fresh"


def test_rewrite_clone_inserts_uuid_when_missing() -> None:
    root = _domain(uuid=None)
    out = rewrite_clone_domain(root, new_name="c", new_uuid="brand-new", disk_substitutions={})
    assert ET.fromstring(out).findtext("uuid") == "brand-new"


def test_rewrite_clone_rejects_non_domain_root() -> None:
    root = ET.Element("not-domain")
    ET.SubElement(root, "name")
    with pytest.raises(SpecValidationError, match="<domain> root"):
        rewrite_clone_domain(root, new_name="c", new_uuid="u", disk_substitutions={})


def test_rewrite_clone_skips_source_without_file_attr() -> None:
    root = ET.Element("domain")
    devices = ET.SubElement(root, "devices")
    disk = ET.SubElement(devices, "disk")
    ET.SubElement(disk, "source")  # no file attr
    out = rewrite_clone_domain(root, new_name="c", new_uuid="u", disk_substitutions={"x": "y"})
    src = ET.fromstring(out).find(".//devices/disk/source")
    assert src is not None
    assert src.get("file") is None


# --- rewrite_restore_domain ----------------------------------------------


def test_rewrite_restore_renames_drops_uuid_repoints_disk() -> None:
    root = _domain()
    out = rewrite_restore_domain(
        root,
        new_name="restored",
        disk_files=("vda.qcow2",),
        destination_dir=Path("/restored"),
    )
    parsed = ET.fromstring(out)
    assert parsed.findtext("name") == "restored"
    assert parsed.find("uuid") is None
    src = parsed.find("devices/disk/source")
    assert src is not None
    assert src.get("file") == "/restored/vda.qcow2"


def test_rewrite_restore_strips_mac() -> None:
    root = _domain()
    out = rewrite_restore_domain(
        root, new_name="r", disk_files=("vda.qcow2",), destination_dir=Path("/d")
    )
    assert ET.fromstring(out).find("devices/interface/mac") is None


def test_rewrite_restore_adds_name_when_missing() -> None:
    root = ET.Element("domain")
    ET.SubElement(root, "devices")
    out = rewrite_restore_domain(root, new_name="r", disk_files=(), destination_dir=Path("/d"))
    assert ET.fromstring(out).findtext("name") == "r"


def test_rewrite_restore_drops_source_dev_attr() -> None:
    root = ET.Element("domain")
    ET.SubElement(root, "name")
    devices = ET.SubElement(root, "devices")
    disk = ET.SubElement(devices, "disk")
    src = ET.SubElement(disk, "source")
    src.set("dev", "/dev/sda")
    out = rewrite_restore_domain(
        root, new_name="r", disk_files=("vda.qcow2",), destination_dir=Path("/d")
    )
    parsed_src = ET.fromstring(out).find("devices/disk/source")
    assert parsed_src is not None
    assert parsed_src.get("dev") is None
    assert parsed_src.get("file") == "/d/vda.qcow2"


def test_rewrite_restore_stops_at_disk_files_length() -> None:
    root = ET.Element("domain")
    ET.SubElement(root, "name")
    devices = ET.SubElement(root, "devices")
    for _ in range(2):
        disk = ET.SubElement(devices, "disk")
        s = ET.SubElement(disk, "source")
        s.set("file", "/orig")
    out = rewrite_restore_domain(
        root, new_name="r", disk_files=("vda.qcow2",), destination_dir=Path("/d")
    )
    sources = ET.fromstring(out).findall("devices/disk/source")
    assert sources[0].get("file") == "/d/vda.qcow2"
    assert sources[1].get("file") == "/orig"  # untouched, beyond disk_files


def test_rewrite_restore_skips_disk_without_source() -> None:
    root = ET.Element("domain")
    ET.SubElement(root, "name")
    devices = ET.SubElement(root, "devices")
    ET.SubElement(devices, "disk")  # no <source>
    out = rewrite_restore_domain(
        root, new_name="r", disk_files=("vda.qcow2",), destination_dir=Path("/d")
    )
    assert ET.fromstring(out).find("devices/disk/source") is None


def test_rewrite_restore_rejects_non_domain_root() -> None:
    root = ET.Element("not-domain")
    with pytest.raises(BackupError, match="<domain> root"):
        rewrite_restore_domain(root, new_name="r", disk_files=(), destination_dir=Path("/d"))
