"""Tier-1 unit tests for :class:`LibvirtNetworkBackend` (Wave-R3, D-G15).

Exercises virtual-network management (define / start / stop / delete) + the
``NetworkSpec`` → network-XML mapping against a fake ``virConnect`` — nothing
imports the real binding. Also runs CONF-N-01 against the adapter with a
:class:`LibvirtHypervisorBackend` observer over the *same* fake connection
(the management-observed-via-observation contract the conformance pins).
"""

from __future__ import annotations

from typing import Any
from xml.etree import ElementTree as ET

import pytest
from mgf.vm.core.conformance import network_conformance_suite
from mgf.vm.core.contracts import NetworkSpec
from mgf.vm.core.exceptions import NetworkNotFound
from tests.conftest import FakeLibvirtError

from mgf.vm.libvirt._connection import LibvirtConnection
from mgf.vm.libvirt._hypervisor import LibvirtHypervisorBackend
from mgf.vm.libvirt._network import LibvirtNetworkBackend, network_spec_to_xml

# ---------------------------------------------------------------------------
# Fakes — a virNetwork + a virConnect that supports network management
# ---------------------------------------------------------------------------


class FakeNetwork:
    def __init__(
        self,
        name: str,
        registry: dict[str, FakeNetwork],
        *,
        forward_mode: str | None = "nat",
        bridge: str = "virbr-test",
    ) -> None:
        self._name = name
        self._registry = registry
        self._forward = forward_mode
        self._bridge = bridge
        self._active = False
        self.autostart = False

    def name(self) -> str:
        return self._name

    def bridgeName(self) -> str:  # noqa: N802
        return self._bridge

    def isActive(self) -> int:  # noqa: N802
        return 1 if self._active else 0

    def XMLDesc(self, _flags: int = 0) -> str:  # noqa: N802
        forward = f"<forward mode='{self._forward}'/>" if self._forward else ""
        return f"<network><name>{self._name}</name>{forward}</network>"

    def create(self) -> None:
        self._active = True

    def destroy(self) -> None:
        self._active = False

    def undefine(self) -> None:
        self._registry.pop(self._name, None)

    def setAutostart(self, value: int) -> None:  # noqa: N802
        self.autostart = bool(value)


class FakeConn:
    def __init__(self) -> None:
        self.networks: dict[str, FakeNetwork] = {}

    def networkDefineXML(self, xml: str) -> FakeNetwork:  # noqa: N802
        root = ET.fromstring(xml)
        name = root.findtext("name") or ""
        forward_el = root.find("forward")
        forward = forward_el.get("mode") if forward_el is not None else None
        net = FakeNetwork(name, self.networks, forward_mode=forward)
        self.networks[name] = net
        return net

    def networkLookupByName(self, name: str) -> FakeNetwork:  # noqa: N802
        if name not in self.networks:
            raise FakeLibvirtError(43, f"no network {name}")
        return self.networks[name]

    def listAllNetworks(self, _flags: int = 0) -> list[FakeNetwork]:  # noqa: N802
        return list(self.networks.values())

    def close(self) -> int:
        return 0


@pytest.fixture
def conn() -> FakeConn:
    return FakeConn()


@pytest.fixture
def backend(conn: FakeConn) -> Any:
    wrapped = LibvirtConnection.adopt(conn, uri="test:///unit")
    yield LibvirtNetworkBackend(wrapped)
    wrapped.close()


# ---------------------------------------------------------------------------
# network_spec_to_xml
# ---------------------------------------------------------------------------


class TestSpecToXml:
    def test_nat_with_cidr_and_dhcp(self) -> None:
        xml = network_spec_to_xml(
            NetworkSpec(name="labnet", forward_mode="nat", cidr="192.168.50.0/24")
        )
        root = ET.fromstring(xml)
        assert root.findtext("name") == "labnet"
        assert root.find("forward").get("mode") == "nat"
        ip = root.find("ip")
        assert ip.get("address") == "192.168.50.1"
        assert ip.get("netmask") == "255.255.255.0"
        rng = ip.find("dhcp/range")
        assert rng.get("start") == "192.168.50.2"
        assert rng.get("end") == "192.168.50.254"

    def test_nat_without_dhcp_omits_range(self) -> None:
        xml = network_spec_to_xml(
            NetworkSpec(name="n", forward_mode="nat", cidr="10.0.0.0/24", dhcp=False)
        )
        root = ET.fromstring(xml)
        assert root.find("ip/dhcp") is None
        assert root.find("ip").get("address") == "10.0.0.1"

    def test_bridge_mode(self) -> None:
        xml = network_spec_to_xml(NetworkSpec(name="br", forward_mode="bridge", bridge="br0"))
        root = ET.fromstring(xml)
        assert root.find("forward").get("mode") == "bridge"
        assert root.find("bridge").get("name") == "br0"
        assert root.find("ip") is None

    def test_isolated_no_forward_and_dns_off(self) -> None:
        xml = network_spec_to_xml(
            NetworkSpec(name="q", forward_mode=None, bridge="virbr-q", dns_enabled=False)
        )
        root = ET.fromstring(xml)
        assert root.find("forward") is None
        assert root.find("bridge").get("name") == "virbr-q"
        assert root.find("dns").get("enable") == "no"


# ---------------------------------------------------------------------------
# management methods
# ---------------------------------------------------------------------------


class TestManagement:
    def _spec(self, name: str = "labnet") -> NetworkSpec:
        return NetworkSpec(name=name, forward_mode="nat", cidr="192.168.50.0/24")

    def test_define_returns_inactive_profile_and_sets_autostart(
        self, backend: Any, conn: FakeConn
    ) -> None:
        profile = backend.define_network(self._spec())
        assert profile.name == "labnet"
        assert profile.active is False
        assert profile.forward_mode == "nat"
        assert conn.networks["labnet"].autostart is True

    def test_start_then_stop(self, backend: Any, conn: FakeConn) -> None:
        backend.define_network(self._spec())
        backend.start_network("labnet")
        assert conn.networks["labnet"].isActive() == 1
        backend.start_network("labnet")  # idempotent
        backend.stop_network("labnet")
        assert conn.networks["labnet"].isActive() == 0
        backend.stop_network("labnet")  # idempotent

    def test_delete_removes_then_idempotent(self, backend: Any, conn: FakeConn) -> None:
        backend.define_network(self._spec())
        backend.start_network("labnet")
        backend.delete_network("labnet")
        assert "labnet" not in conn.networks
        backend.delete_network("labnet")  # idempotent, no raise

    def test_start_unknown_raises_network_not_found(self, backend: Any) -> None:
        with pytest.raises(NetworkNotFound):
            backend.start_network("ghost")

    def test_stop_unknown_raises_network_not_found(self, backend: Any) -> None:
        with pytest.raises(NetworkNotFound):
            backend.stop_network("ghost")


# ---------------------------------------------------------------------------
# CONF-N-01 against the adapter (management observed via the hypervisor)
# ---------------------------------------------------------------------------


def test_network_conformance_against_adapter(conn: FakeConn) -> None:
    wrapped = LibvirtConnection.adopt(conn, uri="test:///unit")
    try:
        net_backend = LibvirtNetworkBackend(wrapped)
        observer = LibvirtHypervisorBackend(connection=wrapped)
        suite = network_conformance_suite(net_backend, observer=observer)
        result = suite.run_one("CONF-N-01")
        assert result.passed is True, result.error
    finally:
        wrapped.close()
