"""``vmspec_to_xml`` — deterministic :class:`LibvirtVmSpec` → domain XML.

Consolidates the XML-shaping logic that lived spread across the legacy
``creator/builder.py`` (virt-install flags) and the per-subsystem
``configurator/*.py`` ``plan()`` functions. Unlike the legacy path this
emits the **full domain XML directly** via stdlib ElementTree — no
``virt-install`` subprocess, no shell.

Determinism contract (contracts.md §3 ``define``): the same spec on the
same adapter version MUST produce **byte-identical** XML. We guarantee
that by:

* building elements in a **fixed order** (the order of the calls below),
* writing attributes in insertion order (ElementTree preserves it; we
  always insert in the same order),
* never emitting timestamps, random UUIDs, or hostname-derived values —
  any UUID comes from the spec, not from ``uuid4()``.

The generator is pure (no libvirt import): it builds a tree and
serialises it. Output is a single line (no pretty-printing) so byte
comparison is stable across platforms.
"""

from __future__ import annotations

from typing import TYPE_CHECKING
from xml.etree import ElementTree as ET

if TYPE_CHECKING:
    from mgf.vm.libvirt._spec import DiskSpec, LibvirtVmSpec, NicSpec

# 1 MiB / 1 GiB helpers — libvirt's <memory> takes a unit attribute; we
# always emit bytes with unit="bytes" so there is no rounding ambiguity.
_BYTES = "bytes"


def vmspec_to_xml(spec: LibvirtVmSpec) -> str:
    """Render ``spec`` to a libvirt domain XML string (deterministic).

    The element order is fixed and documented inline. Returns a
    single-line UTF-8 ``str`` (ElementTree ``unicode`` encoding).
    """
    domain = ET.Element("domain", {"type": "kvm"})

    # ── Identity ───────────────────────────────────────────────────────
    _text(domain, "name", spec.name)
    if spec.uuid is not None:
        _text(domain, "uuid", spec.uuid)
    if spec.description:
        _text(domain, "description", spec.description)

    # ── Memory (max) + currentMemory (balloon target) ──────────────────
    max_mem = spec.memory_max_bytes or spec.memory_bytes
    _text(domain, "memory", str(max_mem), {"unit": _BYTES})
    _text(domain, "currentMemory", str(spec.memory_bytes), {"unit": _BYTES})
    if spec.hugepages:
        backing = ET.SubElement(domain, "memoryBacking")
        ET.SubElement(backing, "hugepages")

    # ── vCPU ───────────────────────────────────────────────────────────
    vcpu_attrs = {}
    vcpu_max = spec.vcpus_max or spec.vcpus
    if vcpu_max != spec.vcpus:
        vcpu_attrs["current"] = str(spec.vcpus)
    _text(domain, "vcpu", str(vcpu_max), vcpu_attrs)

    if spec.cpu_pinning is not None:
        cputune = ET.SubElement(domain, "cputune")
        ET.SubElement(
            cputune,
            "vcpupin",
            {"vcpu": "0", "cpuset": spec.cpu_pinning},
        )

    # ── OS / firmware ──────────────────────────────────────────────────
    os_el = ET.SubElement(domain, "os")
    ET.SubElement(os_el, "type", {"arch": "x86_64", "machine": "q35"}).text = "hvm"
    if spec.firmware == "uefi":
        loader_attrs = {"readonly": "yes", "type": "pflash"}
        if spec.secure_boot:
            loader_attrs["secure"] = "yes"
        ET.SubElement(os_el, "loader", loader_attrs).text = "/usr/share/OVMF/OVMF_CODE.fd"

    # ── CPU model ──────────────────────────────────────────────────────
    if spec.cpu_model == "host-passthrough":
        ET.SubElement(domain, "cpu", {"mode": "host-passthrough"})
    else:
        cpu_el = ET.SubElement(domain, "cpu", {"mode": "custom"})
        _text(cpu_el, "model", spec.cpu_model)

    # ── Devices ────────────────────────────────────────────────────────
    devices = ET.SubElement(domain, "devices")
    for disk in spec.disks:
        _append_disk(devices, disk)
    if spec.balloon:
        ET.SubElement(devices, "memballoon", {"model": "virtio"})
    for nic in spec.nics:
        _append_nic(devices, nic)
    _append_graphics(devices, spec)

    return ET.tostring(domain, encoding="unicode")


def _append_disk(devices: ET.Element, disk: DiskSpec) -> None:
    """Emit one ``<disk>`` element with a fixed child order."""
    disk_el = ET.SubElement(
        devices,
        "disk",
        {"type": "file", "device": "disk"},
    )
    ET.SubElement(
        disk_el,
        "driver",
        {"name": "qemu", "type": disk.format, "cache": disk.cache},
    )
    if disk.source_path is not None:
        ET.SubElement(disk_el, "source", {"file": disk.source_path})
    ET.SubElement(disk_el, "target", {"dev": disk.target, "bus": disk.bus})
    if disk.readonly:
        ET.SubElement(disk_el, "readonly")


def _append_nic(devices: ET.Element, nic: NicSpec) -> None:
    """Emit one ``<interface type='network'>`` element."""
    iface = ET.SubElement(devices, "interface", {"type": "network"})
    if nic.mac is not None:
        ET.SubElement(iface, "mac", {"address": nic.mac})
    ET.SubElement(iface, "source", {"network": nic.network})
    ET.SubElement(iface, "model", {"type": nic.model})


def _append_graphics(devices: ET.Element, spec: LibvirtVmSpec) -> None:
    """Emit the ``<graphics>`` element (skipped for display='none')."""
    if spec.display == "none":
        return
    attrs = {"type": spec.display, "listen": spec.display_listen}
    if spec.display_port is not None:
        attrs["port"] = str(spec.display_port)
        attrs["autoport"] = "no"
    else:
        attrs["autoport"] = "yes"
    graphics = ET.SubElement(devices, "graphics", attrs)
    ET.SubElement(graphics, "listen", {"type": "address", "address": spec.display_listen})


def _text(
    parent: ET.Element,
    tag: str,
    text: str,
    attrs: dict[str, str] | None = None,
) -> ET.Element:
    """Append ``<tag attrs>text</tag>`` to ``parent`` and return it."""
    el = ET.SubElement(parent, tag, attrs or {})
    el.text = text
    return el


__all__ = ["vmspec_to_xml"]
