"""``configurator/network`` — per-VM NIC ``<interface>`` edits (pure planners).

This module owns the ``<interface>`` elements on one VM's domain XML
(distinct from libvirt *virtual networks*, which the adapter owns).

NIC add is hotpluggable for virtio; other emulated models vary by
libvirt version. NIC model change (e.g. virtio -> e1000e) requires a
reboot because the guest driver binds to the PCI device at boot.

**Pure core.** Each planner accepts an *already-parsed*
:class:`xml.etree.ElementTree.Element`; the ``apply_*`` halves stay in
the adapter (``mgf-vm``).
"""

from __future__ import annotations

import re
import xml.etree.ElementTree as ET

from mgf.vm.core.configurator.types import ConfigChange
from mgf.vm.core.configurator.xml_helpers import ensure, serialize_domain_xml
from mgf.vm.core.exceptions import SpecValidationError

SUBSYSTEM = "nic"

_MAC_RE = re.compile(r"^([0-9a-f]{2}:){5}[0-9a-f]{2}$", re.IGNORECASE)


def plan_add(
    domain: ET.Element,
    *,
    network: str,
    model: str = "virtio",
    mac: str | None = None,
) -> ConfigChange:
    """Attach a new ``<interface type='network'>`` to the injected tree."""
    if mac is not None and not _MAC_RE.match(mac):
        raise SpecValidationError([f"invalid MAC address {mac!r}; expected xx:xx:xx:xx:xx:xx"])

    old_xml = serialize_domain_xml(domain)
    devices = ensure(domain, "devices")

    if mac is not None and _find_by_mac(devices, mac) is not None:
        raise SpecValidationError([f"interface with MAC {mac} already exists on domain"])

    iface = ET.SubElement(devices, "interface", {"type": "network"})
    if mac is not None:
        ET.SubElement(iface, "mac", {"address": mac})
    ET.SubElement(iface, "source", {"network": network})
    ET.SubElement(iface, "model", {"type": model})

    return ConfigChange(
        subsystem=SUBSYSTEM,
        summary=f"nic added on network={network}, model={model}",
        old_xml=old_xml,
        new_xml=serialize_domain_xml(domain),
        requires_shutdown=False,
    )


def plan_remove(domain: ET.Element, *, mac: str) -> ConfigChange:
    """Drop the interface whose MAC matches ``mac``."""
    if not _MAC_RE.match(mac):
        raise SpecValidationError([f"invalid MAC address {mac!r}"])
    old_xml = serialize_domain_xml(domain)
    devices = domain.find("devices")
    if devices is None:
        raise SpecValidationError(["domain has no <devices> element"])

    iface = _find_by_mac(devices, mac)
    if iface is None:
        raise SpecValidationError([f"no NIC with MAC {mac}"])
    devices.remove(iface)

    return ConfigChange(
        subsystem=SUBSYSTEM,
        summary=f"nic removed mac={mac}",
        old_xml=old_xml,
        new_xml=serialize_domain_xml(domain),
        requires_shutdown=False,
    )


def _find_by_mac(devices: ET.Element, mac: str) -> ET.Element | None:
    for iface in devices.findall("interface"):
        mac_el = iface.find("mac")
        if mac_el is not None and (mac_el.get("address") or "").lower() == mac.lower():
            return iface
    return None


__all__ = [
    "SUBSYSTEM",
    "plan_add",
    "plan_remove",
]
