"""Tests for :mod:`mgf.vm.interchange.ovf` — parser + writer round trip."""

from __future__ import annotations

import pytest
from mgf.vm.core.exceptions import ImportExportError

from mgf.vm.interchange.ovf import (
    OVFNIC,
    OVFDescriptor,
    OVFDisk,
    parse_ovf,
    serialize_ovf,
)


def _simple_descriptor() -> OVFDescriptor:
    return OVFDescriptor(
        name="demo-vm",
        description="unit-test demo",
        vcpus=4,
        memory_mb=4096,
        disks=[
            OVFDisk(
                disk_id="vmdisk1",
                file_ref="file1",
                file_href="demo-vm-vmdisk1.vmdk",
                capacity_bytes=20 * 1024 * 1024 * 1024,
            ),
        ],
        nics=[OVFNIC(name="eth0", model="virtio", connection="NAT")],
        networks=["NAT"],
    )


class TestSerialize:
    def test_serialized_output_is_valid_xml(self) -> None:
        text = serialize_ovf(_simple_descriptor())
        assert text.startswith("<?xml")
        assert "<ovf:Envelope" in text
        assert "xmlns:ovf=" in text

    def test_serialized_output_includes_file_reference(self) -> None:
        text = serialize_ovf(_simple_descriptor())
        assert "demo-vm-vmdisk1.vmdk" in text
        assert 'ovf:id="file1"' in text

    def test_serialized_output_records_memory_and_cpu_items(self) -> None:
        text = serialize_ovf(_simple_descriptor())
        # CPU item
        assert "<rasd:VirtualQuantity>4</rasd:VirtualQuantity>" in text
        # Memory item
        assert "<rasd:VirtualQuantity>4096</rasd:VirtualQuantity>" in text

    def test_empty_description_is_replaced_with_default_info(self) -> None:
        d = _simple_descriptor()
        d.description = ""
        text = serialize_ovf(d)
        # Envelope <Info> should fall back to "<name> virtual machine".
        assert "demo-vm virtual machine" in text


class TestRoundTrip:
    def test_round_trip_preserves_name_cpus_and_memory(self) -> None:
        original = _simple_descriptor()
        text = serialize_ovf(original)
        parsed = parse_ovf(text)
        assert parsed.name == "demo-vm"
        assert parsed.vcpus == 4
        assert parsed.memory_mb == 4096

    def test_round_trip_preserves_disk_metadata(self) -> None:
        original = _simple_descriptor()
        text = serialize_ovf(original)
        parsed = parse_ovf(text)
        assert len(parsed.disks) == 1
        d = parsed.disks[0]
        assert d.disk_id == "vmdisk1"
        assert d.file_ref == "file1"
        assert d.file_href == "demo-vm-vmdisk1.vmdk"
        assert d.capacity_bytes == 20 * 1024 * 1024 * 1024

    def test_round_trip_preserves_nic_metadata(self) -> None:
        original = _simple_descriptor()
        text = serialize_ovf(original)
        parsed = parse_ovf(text)
        assert len(parsed.nics) == 1
        n = parsed.nics[0]
        assert n.connection == "NAT"
        assert n.model == "virtio"


class TestParserErrors:
    def test_root_element_must_be_envelope(self) -> None:
        text = "<?xml version='1.0'?><NotAnEnvelope/>"
        with pytest.raises(ImportExportError, match="Envelope"):
            parse_ovf(text)

    def test_missing_virtual_system_raises(self) -> None:
        text = (
            '<?xml version="1.0"?>'
            '<ovf:Envelope xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1">'
            "<ovf:References/>"
            "</ovf:Envelope>"
        )
        with pytest.raises(ImportExportError, match="VirtualSystem"):
            parse_ovf(text)

    def test_malformed_xml_is_reported(self) -> None:
        text = "<?xml version='1.0'?><envelope><broken"
        with pytest.raises(ImportExportError, match="invalid OVF XML"):
            parse_ovf(text)


class TestMemoryUnitNormalization:
    def test_byte_exponent_20_is_mib(self) -> None:
        from mgf.vm.interchange.ovf import _memory_quantity_to_mb

        assert _memory_quantity_to_mb("2048", "byte * 2^20") == 2048

    def test_byte_exponent_30_is_gib_scaled_to_mib(self) -> None:
        from mgf.vm.interchange.ovf import _memory_quantity_to_mb

        # 4 GiB = 4096 MiB
        assert _memory_quantity_to_mb("4", "byte * 2^30") == 4096

    def test_missing_unit_defaults_to_mib(self) -> None:
        from mgf.vm.interchange.ovf import _memory_quantity_to_mb

        assert _memory_quantity_to_mb("1500", "") == 1500
