"""Unit tests for domain_builder.py and network_builder.py."""

from __future__ import annotations

import xml.etree.ElementTree as ET

import pytest

from vmcluster_mcp.cluster.domain_builder import build_domain_xml
from vmcluster_mcp.cluster.network_builder import build_network_xml, get_gateway_ip
from vmcluster_mcp.topology.schema import ExtraDisk, NetworkSpec, NodeSpec


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture
def node():
    return NodeSpec(name="node1", role="worker", vcpus=2, memory_mb=2048, ip="192.168.100.10")


@pytest.fixture
def node_with_extra_disks():
    return NodeSpec(
        name="node2",
        role="worker",
        vcpus=4,
        memory_mb=4096,
        ip="192.168.100.11",
        extra_disks=[
            ExtraDisk(path="/var/lib/extra/data1.qcow2", size_gb=50, bus="virtio"),
            ExtraDisk(path="/var/lib/extra/data2.qcow2", size_gb=20, bus="virtio"),
        ],
    )


@pytest.fixture
def network():
    return NetworkSpec(
        name="mycluster-net",
        bridge="virbr100",
        subnet="192.168.100.0/24",
        dhcp=False,
    )


# ---------------------------------------------------------------------------
# domain_builder tests
# ---------------------------------------------------------------------------


def test_build_domain_xml_returns_string(node):
    xml = build_domain_xml("mycluster", node, "/overlay/node1.qcow2", "/overlay/node1-cidata.iso", "virbr100", "52:54:00:ab:cd:ef")
    assert isinstance(xml, str)
    assert len(xml) > 0


def test_build_domain_xml_domain_type_kvm(node):
    xml = build_domain_xml("mycluster", node, "/overlay/node1.qcow2", "/overlay/node1-cidata.iso", "virbr100", "52:54:00:ab:cd:ef")
    root = ET.fromstring(xml)
    assert root.tag == "domain"
    assert root.attrib.get("type") == "kvm"


def test_build_domain_xml_name(node):
    xml = build_domain_xml("mycluster", node, "/overlay/node1.qcow2", "/overlay/node1-cidata.iso", "virbr100", "52:54:00:ab:cd:ef")
    root = ET.fromstring(xml)
    name_el = root.find("name")
    assert name_el is not None
    assert name_el.text == "mycluster_node1"


def test_build_domain_xml_memory(node):
    xml = build_domain_xml("mycluster", node, "/overlay/node1.qcow2", "/overlay/node1-cidata.iso", "virbr100", "52:54:00:ab:cd:ef")
    root = ET.fromstring(xml)
    mem_el = root.find("memory")
    assert mem_el is not None
    assert mem_el.text == "2048"
    assert mem_el.attrib.get("unit") == "MiB"


def test_build_domain_xml_vcpu(node):
    xml = build_domain_xml("mycluster", node, "/overlay/node1.qcow2", "/overlay/node1-cidata.iso", "virbr100", "52:54:00:ab:cd:ef")
    root = ET.fromstring(xml)
    vcpu_el = root.find("vcpu")
    assert vcpu_el is not None
    assert vcpu_el.text == "2"


def test_build_domain_xml_overlay_disk(node):
    overlay_path = "/var/lib/vmcluster/overlays/mycluster/node1.qcow2"
    xml = build_domain_xml("mycluster", node, overlay_path, "/overlay/node1-cidata.iso", "virbr100", "52:54:00:ab:cd:ef")
    root = ET.fromstring(xml)
    devices = root.find("devices")
    assert devices is not None
    # Find the primary disk (vda virtio)
    disks = devices.findall("disk")
    vda_disk = None
    for d in disks:
        target = d.find("target")
        if target is not None and target.attrib.get("dev") == "vda":
            vda_disk = d
            break
    assert vda_disk is not None, "No vda disk found"
    source = vda_disk.find("source")
    assert source is not None
    assert source.attrib.get("file") == overlay_path
    target_el = vda_disk.find("target")
    assert target_el.attrib.get("bus") == "virtio"


def test_build_domain_xml_cidata_iso(node):
    cidata_path = "/var/lib/vmcluster/overlays/mycluster/node1-cidata.iso"
    xml = build_domain_xml("mycluster", node, "/overlay/node1.qcow2", cidata_path, "virbr100", "52:54:00:ab:cd:ef")
    root = ET.fromstring(xml)
    devices = root.find("devices")
    disks = devices.findall("disk")
    cdrom_disk = None
    for d in disks:
        if d.attrib.get("device") == "cdrom":
            cdrom_disk = d
            break
    assert cdrom_disk is not None, "No cdrom disk found"
    source = cdrom_disk.find("source")
    assert source.attrib.get("file") == cidata_path
    target = cdrom_disk.find("target")
    assert target.attrib.get("bus") == "sata"
    readonly = cdrom_disk.find("readonly")
    assert readonly is not None


def test_build_domain_xml_bridge_interface(node):
    xml = build_domain_xml("mycluster", node, "/overlay/node1.qcow2", "/overlay/node1-cidata.iso", "virbr100", "52:54:00:ab:cd:ef")
    root = ET.fromstring(xml)
    devices = root.find("devices")
    iface = devices.find("interface")
    assert iface is not None
    assert iface.attrib.get("type") == "bridge"
    source = iface.find("source")
    assert source.attrib.get("bridge") == "virbr100"
    mac = iface.find("mac")
    assert mac.attrib.get("address") == "52:54:00:ab:cd:ef"


def test_build_domain_xml_serial_console(node):
    xml = build_domain_xml("mycluster", node, "/overlay/node1.qcow2", "/overlay/node1-cidata.iso", "virbr100", "52:54:00:ab:cd:ef")
    root = ET.fromstring(xml)
    devices = root.find("devices")
    serial = devices.find("serial")
    assert serial is not None
    assert serial.attrib.get("type") == "pty"


def test_build_domain_xml_extra_disks(node_with_extra_disks):
    xml = build_domain_xml("mycluster", node_with_extra_disks, "/overlay/node2.qcow2", "/overlay/node2-cidata.iso", "virbr100", "52:54:00:aa:bb:cc")
    root = ET.fromstring(xml)
    devices = root.find("devices")
    disks = devices.findall("disk")
    # Check vdb and vdc appear
    devs = {d.find("target").attrib.get("dev") for d in disks if d.find("target") is not None}
    assert "vdb" in devs, f"vdb not found in {devs}"
    assert "vdc" in devs, f"vdc not found in {devs}"


# ---------------------------------------------------------------------------
# network_builder tests
# ---------------------------------------------------------------------------


def test_build_network_xml_returns_string(network):
    xml = build_network_xml(network)
    assert isinstance(xml, str)
    assert len(xml) > 0


def test_build_network_xml_name(network):
    xml = build_network_xml(network)
    root = ET.fromstring(xml)
    assert root.tag == "network"
    name_el = root.find("name")
    assert name_el is not None
    assert name_el.text == "mycluster-net"


def test_build_network_xml_bridge(network):
    xml = build_network_xml(network)
    root = ET.fromstring(xml)
    bridge_el = root.find("bridge")
    assert bridge_el is not None
    assert bridge_el.attrib.get("name") == "virbr100"


def test_build_network_xml_forward_nat(network):
    xml = build_network_xml(network)
    root = ET.fromstring(xml)
    forward_el = root.find("forward")
    assert forward_el is not None
    assert forward_el.attrib.get("mode") == "nat"


def test_build_network_xml_gateway_ip(network):
    xml = build_network_xml(network)
    root = ET.fromstring(xml)
    ip_el = root.find("ip")
    assert ip_el is not None
    # Gateway should be first host in 192.168.100.0/24 → 192.168.100.1
    assert ip_el.attrib.get("address") == "192.168.100.1"
    assert ip_el.attrib.get("netmask") == "255.255.255.0"


def test_build_network_xml_no_dhcp(network):
    xml = build_network_xml(network)
    assert "dhcp" not in xml.lower()


def test_get_gateway_ip():
    assert get_gateway_ip("192.168.100.0/24") == "192.168.100.1"
    assert get_gateway_ip("10.0.0.0/8") == "10.0.0.1"
    assert get_gateway_ip("172.16.0.0/16") == "172.16.0.1"
