"""Tests for :mod:`mgf.vm.core.configurator.reader` (pure inject-Element).

These mirror the umbrella ``test_configurator_reader`` read_config cases,
but feed an already-parsed :class:`xml.etree.ElementTree.Element` directly
(the pure-core convention) instead of seeding a mock provider. Tests use
stdlib ``ET.fromstring`` to build the injected ``Element`` — tests are not
the pure runtime path, matching the other core inject-Element tests.
"""

from __future__ import annotations

import xml.etree.ElementTree as ET

from mgf.vm.core.configurator import read_config
from mgf.vm.core.configurator.reader import read_config as reader_read_config

# ---------------------------------------------------------------------------
# Fixtures: handcrafted XML blobs covering every subsystem.
# ---------------------------------------------------------------------------


FULL_XML = """<domain type='kvm'>
  <name>web</name>
  <uuid>fake-uuid</uuid>
  <title>web frontend</title>
  <description>nginx + tls</description>
  <memory unit='KiB'>4194304</memory>
  <currentMemory unit='KiB'>2097152</currentMemory>
  <memoryBacking><hugepages/></memoryBacking>
  <vcpu current='2'>4</vcpu>
  <cpu mode='custom'>
    <model>host-passthrough</model>
  </cpu>
  <os>
    <loader secure='yes'>/usr/share/OVMF/OVMF_CODE.fd</loader>
  </os>
  <devices>
    <disk type='file' device='disk'>
      <driver name='qemu' type='qcow2'/>
      <source file='/var/lib/libvirt/images/web.qcow2'/>
      <target dev='vda' bus='virtio'/>
    </disk>
    <disk type='file' device='disk'>
      <driver name='qemu' type='raw'/>
      <source file='/var/lib/libvirt/images/web-data.raw'/>
      <target dev='vdb' bus='virtio'/>
      <readonly/>
    </disk>
    <disk type='file' device='cdrom'>
      <source file='/var/lib/libvirt/images/seed.iso'/>
      <target dev='sda' bus='sata'/>
    </disk>
    <interface type='network'>
      <mac address='52:54:00:aa:bb:cc'/>
      <source network='default'/>
      <model type='virtio'/>
    </interface>
    <interface type='bridge'>
      <mac address='52:54:00:dd:ee:ff'/>
      <source bridge='br0'/>
      <model type='e1000e'/>
    </interface>
    <filesystem type='mount'>
      <driver type='virtiofs'/>
      <source dir='/srv/shared'/>
      <target dir='shared'/>
      <readonly/>
    </filesystem>
    <hostdev mode='subsystem' type='pci'>
      <source>
        <address domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
      </source>
    </hostdev>
    <hostdev mode='subsystem' type='usb'>
      <source>
        <vendor id='0x046d'/>
        <product id='0xc534'/>
      </source>
    </hostdev>
    <graphics type='spice' port='5901' listen='0.0.0.0'/>
  </devices>
</domain>"""

VNC_AUTOPORT_XML = """<domain>
  <name>vncbox</name>
  <uuid>u2</uuid>
  <devices>
    <graphics type='vnc' port='-1'>
      <listen type='address' address='127.0.0.1'/>
    </graphics>
  </devices>
</domain>"""

MINIMAL_XML = "<domain><name>min</name><uuid>u</uuid></domain>"


def _full() -> ET.Element:
    return ET.fromstring(FULL_XML)


def _minimal() -> ET.Element:
    return ET.fromstring(MINIMAL_XML)


# ---------------------------------------------------------------------------
# read_config — full config
# ---------------------------------------------------------------------------


class TestReadConfig:
    def test_reexport_is_same_callable(self) -> None:
        assert read_config is reader_read_config

    def test_name_uuid(self) -> None:
        config = read_config(_full())
        assert config.name == "web"
        assert config.uuid == "fake-uuid"

    def test_cpu(self) -> None:
        config = read_config(_full())
        assert config.cpu.vcpus == 2
        assert config.cpu.vcpus_max == 4
        assert config.cpu.model == "host-passthrough"

    def test_memory(self) -> None:
        config = read_config(_full())
        assert config.memory.current_mb == 2048
        assert config.memory.max_mb == 4096
        assert config.memory.hugepages is True

    def test_firmware_uefi_secure(self) -> None:
        config = read_config(_full())
        assert config.firmware.type == "uefi"
        assert config.firmware.secure_boot is True

    def test_firmware_bios_default(self) -> None:
        config = read_config(_minimal())
        assert config.firmware.type == "bios"
        assert config.firmware.secure_boot is False

    def test_metadata(self) -> None:
        config = read_config(_full())
        assert config.metadata.title == "web frontend"
        assert config.metadata.description == "nginx + tls"

    def test_display_spice(self) -> None:
        config = read_config(_full())
        assert config.display.type == "spice"
        assert config.display.listen == "0.0.0.0"
        assert config.display.port == 5901

    def test_display_vnc_autoport_listen_element(self) -> None:
        config = read_config(ET.fromstring(VNC_AUTOPORT_XML))
        assert config.display.type == "vnc"
        assert config.display.listen == "127.0.0.1"  # nested <listen address=>
        assert config.display.port is None  # "-1" -> auto

    def test_disks_skip_cdrom_and_carry_bus_format_readonly(self) -> None:
        config = read_config(_full())
        # cdrom is skipped; only the two device='disk' entries remain.
        assert len(config.disks) == 2
        primary = config.disks[0]
        assert primary.target == "vda"
        assert primary.bus == "virtio"
        assert primary.format == "qcow2"
        assert primary.source == "/var/lib/libvirt/images/web.qcow2"
        assert primary.readonly is False
        assert config.disks[1].readonly is True
        assert config.disks[1].format == "raw"

    def test_nics_network_and_bridge(self) -> None:
        config = read_config(_full())
        assert len(config.nics) == 2
        assert config.nics[0].mac == "52:54:00:aa:bb:cc"
        assert config.nics[0].network == "default"
        assert config.nics[0].bridge is None
        assert config.nics[0].model == "virtio"
        assert config.nics[1].bridge == "br0"
        assert config.nics[1].network is None
        assert config.nics[1].model == "e1000e"

    def test_shares(self) -> None:
        config = read_config(_full())
        assert len(config.shares) == 1
        assert config.shares[0].driver == "virtiofs"
        assert config.shares[0].source == "/srv/shared"
        assert config.shares[0].target == "shared"
        assert config.shares[0].readonly is True

    def test_pci(self) -> None:
        config = read_config(_full())
        assert len(config.pci_devices) == 1
        dev = config.pci_devices[0]
        assert (dev.domain, dev.bus, dev.slot, dev.function) == (0, 1, 0, 0)

    def test_usb(self) -> None:
        config = read_config(_full())
        assert len(config.usb_devices) == 1
        usb = config.usb_devices[0]
        assert usb.vendor == 0x046D
        assert usb.product == 0xC534

    def test_minimal_xml_defaults(self) -> None:
        config = read_config(_minimal())
        assert config.name == "min"
        assert config.uuid == "u"
        assert config.cpu.vcpus == 1
        assert config.cpu.vcpus_max == 1
        assert config.cpu.model is None
        assert config.memory.current_mb == 0
        assert config.memory.max_mb == 0
        assert config.memory.hugepages is False
        assert config.metadata.title is None
        assert config.metadata.description is None
        assert config.display.type == "none"
        assert config.disks == ()
        assert config.nics == ()
        assert config.shares == ()
        assert config.pci_devices == ()
        assert config.usb_devices == ()


# ---------------------------------------------------------------------------
# Edge cases that exercise the helper branches.
# ---------------------------------------------------------------------------


class TestReaderEdges:
    def test_empty_name_uuid_default_to_blank(self) -> None:
        config = read_config(ET.fromstring("<domain></domain>"))
        assert config.name == ""
        assert config.uuid == ""

    def test_vcpu_no_current_uses_max(self) -> None:
        config = read_config(ET.fromstring("<domain><vcpu>3</vcpu></domain>"))
        assert config.cpu.vcpus == 3
        assert config.cpu.vcpus_max == 3

    def test_vcpu_empty_text_defaults_to_one(self) -> None:
        config = read_config(ET.fromstring("<domain><vcpu></vcpu></domain>"))
        assert config.cpu.vcpus_max == 1

    def test_currentMemory_absent_falls_back_to_memory(self) -> None:
        config = read_config(ET.fromstring("<domain><memory unit='MiB'>512</memory></domain>"))
        assert config.memory.max_mb == 512
        assert config.memory.current_mb == 512

    def test_memory_unit_variants(self) -> None:
        gib = read_config(ET.fromstring("<domain><memory unit='GiB'>2</memory></domain>"))
        assert gib.memory.max_mb == 2048
        byts = read_config(
            ET.fromstring("<domain><memory unit='bytes'>1073741824</memory></domain>")
        )
        assert byts.memory.max_mb == 1024
        unknown = read_config(ET.fromstring("<domain><memory unit='TiB'>1048576</memory></domain>"))
        assert unknown.memory.max_mb == 1024  # default-assumption KiB path

    def test_firmware_bios_loader_without_ovmf(self) -> None:
        config = read_config(
            ET.fromstring("<domain><os><loader>/usr/share/seabios/bios.bin</loader></os></domain>")
        )
        assert config.firmware.type == "bios"
        assert config.firmware.secure_boot is False

    def test_display_inline_listen_attr_and_bad_port(self) -> None:
        config = read_config(
            ET.fromstring(
                "<domain><devices><graphics type='vnc' port='abc' listen='1.2.3.4'/>"
                "</devices></domain>"
            )
        )
        assert config.display.listen == "1.2.3.4"
        assert config.display.port is None  # non-numeric port -> None

    def test_graphics_missing_type_is_none(self) -> None:
        config = read_config(
            ET.fromstring("<domain><devices><graphics port='5900'/></devices></domain>")
        )
        assert config.display.type == "none"

    def test_disk_with_block_dev_source(self) -> None:
        config = read_config(
            ET.fromstring(
                "<domain><devices><disk device='disk'>"
                "<source dev='/dev/sdb'/><target dev='vda'/>"
                "</disk></devices></domain>"
            )
        )
        assert config.disks[0].source == "/dev/sdb"
        assert config.disks[0].bus == "virtio"  # default when target has no bus
        assert config.disks[0].format == "qcow2"  # default when no driver

    def test_pci_hostdev_without_address_skipped(self) -> None:
        config = read_config(
            ET.fromstring(
                "<domain><devices><hostdev type='pci'><source/></hostdev></devices></domain>"
            )
        )
        assert config.pci_devices == ()

    def test_usb_hostdev_missing_product_skipped(self) -> None:
        config = read_config(
            ET.fromstring(
                "<domain><devices><hostdev type='usb'><source>"
                "<vendor id='0x046d'/></source></hostdev></devices></domain>"
            )
        )
        assert config.usb_devices == ()

    def test_hex_attr_decimal_and_garbage_fallbacks(self) -> None:
        config = read_config(
            ET.fromstring(
                "<domain><devices><hostdev type='pci'><source>"
                "<address domain='10' bus='zz' slot='0x02' function='0x0'/>"
                "</source></hostdev></devices></domain>"
            )
        )
        dev = config.pci_devices[0]
        assert dev.domain == 0x10  # '10' parses as hex 0x10
        assert dev.bus == 0  # 'zz' -> 0 fallback
        assert dev.slot == 2
