"""Typed data classes returned by the configurator read-path.

Every configurator subsystem speaks in terms of a slice of
:class:`VMConfig`: ``cpu`` reads/writes :class:`CPUConfig`,
``storage`` reads/writes a list of :class:`DiskConfig`, and so
on. Keeping these as small frozen dataclasses lets tests compare
configurations with ``==`` and lets the CLI pretty-print them
without special casing.

These types live in :mod:`mgf.vm.core.configurator` rather than in
:mod:`mgf.vm.core.domain` because they are an implementation detail
of the read-path: a hardware read-back of a live domain's XML,
distinct from the frozen :class:`mgf.vm.core.domain.VmSpec` request
object. Lower layers (state machine, contracts) have no business
looking at them.

This module is pure: it defines only frozen dataclasses with stdlib
types and never parses XML or touches an engine. The XML-parsing
reader and the apply-path that produce/consume these types are
adapter-bound and live outside the pure core.
"""

from __future__ import annotations

from dataclasses import dataclass, field


@dataclass(frozen=True)
class CPUConfig:
    """vCPU layout for a single domain.

    ``vcpus`` is the currently-active count (what the guest sees
    today); ``vcpus_max`` is the maximum that the guest will
    expand to on a hot-add. ``model`` is libvirt's CPU model
    name (e.g. ``host-passthrough``, ``Nehalem``) or ``None`` when
    the domain uses the default.
    """

    vcpus: int
    vcpus_max: int
    model: str | None = None


@dataclass(frozen=True)
class MemoryConfig:
    """Memory layout for a single domain.

    ``current_mb`` is what the guest sees today (the balloon
    target); ``max_mb`` is the ceiling that a hot-add can grow
    the guest to without a reboot. ``hugepages`` is ``True`` when
    the domain is backed by hugetlbfs.
    """

    current_mb: int
    max_mb: int
    hugepages: bool = False


@dataclass(frozen=True)
class DiskConfig:
    """A single ``<disk>`` element on a domain.

    ``target`` is the guest-visible device name (``vda``, ``vdb``,
    ...). ``source`` is the host-side path. ``bus`` is the guest
    bus (``virtio``, ``sata``, ``scsi``). ``format`` is the image
    format (``qcow2``, ``raw``). ``readonly`` reflects the
    ``<readonly/>`` element on the disk.
    """

    target: str
    source: str
    bus: str
    format: str
    readonly: bool = False


@dataclass(frozen=True)
class NICConfig:
    """A single ``<interface>`` element on a domain.

    ``mac`` is the 17-character lowercase form. ``model`` is the
    emulated NIC model (``virtio``, ``e1000e``). ``network`` is
    the libvirt network name when ``type='network'``; ``bridge``
    is the host bridge name when ``type='bridge'``.
    """

    mac: str
    model: str
    network: str | None = None
    bridge: str | None = None


@dataclass(frozen=True)
class DisplayConfig:
    """The ``<graphics>`` element on a domain.

    ``type`` is ``vnc``, ``spice``, or ``none``. ``listen`` is the
    listen address (``127.0.0.1`` for local-only, ``0.0.0.0`` for
    any interface). ``port`` is ``None`` when libvirt auto-assigns.
    """

    type: str
    listen: str | None = None
    port: int | None = None


@dataclass(frozen=True)
class ShareConfig:
    """A ``<filesystem>`` element on a domain.

    ``driver`` is ``virtiofs`` or ``9p``. ``source`` is the host
    path to expose. ``target`` is the mount tag the guest uses.
    ``readonly`` maps to ``<readonly/>``.
    """

    driver: str
    source: str
    target: str
    readonly: bool = False


@dataclass(frozen=True)
class PCIDevice:
    """A single PCI passthrough ``<hostdev>`` device.

    The four integers are the host-side domain/bus/slot/function
    address. A PCI device can only be attached to a stopped VM.
    """

    domain: int
    bus: int
    slot: int
    function: int


@dataclass(frozen=True)
class USBDevice:
    """A single USB passthrough ``<hostdev>`` device.

    ``vendor`` and ``product`` are the USB vendor/product IDs as
    16-bit integers; the XML carries them as hex strings like
    ``0x1d6b`` but every VManager call site works with the raw
    integers.
    """

    vendor: int
    product: int


@dataclass(frozen=True)
class FirmwareConfig:
    """Firmware mode for a domain.

    ``type`` is ``bios`` or ``uefi``. ``secure_boot`` is only
    meaningful for UEFI and is ignored on BIOS domains.
    """

    type: str
    secure_boot: bool = False


@dataclass(frozen=True)
class Metadata:
    """Free-form metadata: libvirt ``<title>`` and ``<description>``."""

    title: str | None = None
    description: str | None = None


@dataclass(frozen=True)
class VMConfig:
    """Aggregated read-back of a domain's hardware configuration.

    Produced by the configurator read-path. Every field is its own
    frozen sub-dataclass so the CLI can render per-subsystem views
    without having to know the layout of every other subsystem.
    """

    name: str
    uuid: str
    cpu: CPUConfig
    memory: MemoryConfig
    firmware: FirmwareConfig
    metadata: Metadata
    display: DisplayConfig
    disks: tuple[DiskConfig, ...] = field(default_factory=tuple)
    nics: tuple[NICConfig, ...] = field(default_factory=tuple)
    shares: tuple[ShareConfig, ...] = field(default_factory=tuple)
    pci_devices: tuple[PCIDevice, ...] = field(default_factory=tuple)
    usb_devices: tuple[USBDevice, ...] = field(default_factory=tuple)


@dataclass(frozen=True)
class ConfigChange:
    """Result of a subsystem ``plan()`` call.

    ``subsystem`` is a short identifier (``"cpu"``, ``"memory"``,
    ``"storage"``, ...) suitable as the ``subsystem`` argument to
    the history/audit recorder. ``summary`` is a one-line human
    description of the change used both in log output and in the
    events audit trail. ``old_xml`` and ``new_xml`` are the
    before/after domain XML strings — the CLI uses them to produce a
    unified diff for ``--preview``. ``requires_shutdown`` tells the
    apply layer to refuse a live change and demand ``--next-boot``
    instead.
    """

    subsystem: str
    summary: str
    old_xml: str
    new_xml: str
    requires_shutdown: bool = False


__all__ = [
    "CPUConfig",
    "ConfigChange",
    "DiskConfig",
    "DisplayConfig",
    "FirmwareConfig",
    "MemoryConfig",
    "Metadata",
    "NICConfig",
    "PCIDevice",
    "ShareConfig",
    "USBDevice",
    "VMConfig",
]
