"""SDK guest-agent surface tests (Wave-R6, D-G19).

Drives ``VmManagerClient`` (over ``InProcessTransport``) and
``AsyncVmManagerClient`` (over ``AsyncInProcessTransport``) against the core
``FakeGuestAgentBackend``: ``guest_ping`` / ``guest_os_info`` /
``guest_interfaces`` round-trip through an injected backend; an unknown VM
preserves ``DomainNotFound``; an unreachable agent surfaces
``GuestUnreachableError``; and a transport with NO guest-agent backend raises a
typed ``TransportError`` (never a silent empty).
"""

from __future__ import annotations

import pytest
from mgf.vm.core.contracts import GuestNic, GuestOsInfo, VmSpec
from mgf.vm.core.exceptions import (
    DomainNotFound,
    GuestUnreachableError,
    TransportError,
)
from mgf.vm.core.fakes.guest_agent import FakeGuestAgentBackend
from mgf.vm.core.fakes.hypervisor import FakeHypervisorBackend
from mgf.vm.core.ids import DomainId, VmId

from mgf.vm.sdk.client import AsyncVmManagerClient, VmManagerClient
from mgf.vm.sdk.transport.async_inprocess import AsyncInProcessTransport
from mgf.vm.sdk.transport.inprocess import InProcessTransport


def _spec(name: str = "ga-vm") -> VmSpec:
    return VmSpec(name=name, cpu=2, memory_bytes=2 << 30)


def _client_with_guest() -> tuple[VmManagerClient, FakeGuestAgentBackend, VmId]:
    hv = FakeHypervisorBackend()
    guest = FakeGuestAgentBackend()
    client = VmManagerClient(InProcessTransport.from_adapters(hv, guest_agent=guest))
    vm_id = client.create_vm(_spec()).vm_id
    guest._register_domain(DomainId(vm_id.value))
    return client, guest, vm_id


# ===========================================================================
# sync
# ===========================================================================


def test_guest_ping_sync_reachable() -> None:
    client, _guest, vm_id = _client_with_guest()
    assert client.guest_ping(vm_id) is True


def test_guest_ping_sync_unreachable_returns_false() -> None:
    client, guest, vm_id = _client_with_guest()
    guest._set_reachable(DomainId(vm_id.value), reachable=False)
    assert client.guest_ping(vm_id) is False


def test_guest_os_info_sync_round_trip() -> None:
    client, guest, vm_id = _client_with_guest()
    guest._set_os_info(DomainId(vm_id.value), GuestOsInfo(id="ubuntu", name="Ubuntu"))
    info = client.guest_os_info(vm_id)
    assert isinstance(info, GuestOsInfo)
    assert info.id == "ubuntu"


def test_guest_interfaces_sync_round_trip() -> None:
    client, guest, vm_id = _client_with_guest()
    guest._set_interfaces(
        DomainId(vm_id.value),
        (GuestNic(name="eth0", mac="52:54:00:00:00:01", ip_addresses=("10.0.0.9",)),),
    )
    nics = client.guest_interfaces(vm_id)
    assert nics[0].ip_addresses == ("10.0.0.9",)


def test_guest_os_info_sync_unreachable_raises() -> None:
    client, guest, vm_id = _client_with_guest()
    guest._set_reachable(DomainId(vm_id.value), reachable=False)
    # The backend's typed error is preserved across the seam (not re-wrapped).
    with pytest.raises(GuestUnreachableError):
        client.guest_os_info(vm_id)


def test_guest_sync_unknown_domain_raises_domain_not_found() -> None:
    hv = FakeHypervisorBackend()
    guest = FakeGuestAgentBackend()
    client = VmManagerClient(InProcessTransport.from_adapters(hv, guest_agent=guest))
    with pytest.raises(DomainNotFound):
        client.guest_ping(VmId("never-registered"))


def test_guest_sync_without_backend_raises_transport_error() -> None:
    hv = FakeHypervisorBackend()
    client = VmManagerClient(InProcessTransport.from_adapters(hv))
    vm_id = client.create_vm(_spec()).vm_id
    with pytest.raises(TransportError, match="GuestAgentBackend"):
        client.guest_ping(vm_id)


# ===========================================================================
# async
# ===========================================================================


async def test_guest_surface_async_round_trip() -> None:
    hv = FakeHypervisorBackend()
    guest = FakeGuestAgentBackend()
    transport = AsyncInProcessTransport.from_adapters(hv, guest_agent=guest)
    async with AsyncVmManagerClient(transport) as client:
        vm_id = (await client.create_vm(_spec())).vm_id
        guest._register_domain(DomainId(vm_id.value))
        guest._set_os_info(DomainId(vm_id.value), GuestOsInfo(id="debian"))
        assert await client.guest_ping(vm_id) is True
        assert (await client.guest_os_info(vm_id)).id == "debian"
        assert await client.guest_interfaces(vm_id) == ()
