"""B3 unit tests — VM snapshots + volume snapshots through the SDK.

Drives ``VmManagerClient`` over ``InProcessTransport`` pointed at the core
fakes (``FakeHypervisorBackend`` for VM snapshots, ``FakeStorageBackend`` for
volume snapshots). Covers round-trips, idempotency, and the typed-error
contract (``SnapshotExists`` / ``SnapshotNotFound``).
"""

from __future__ import annotations

import pytest
from mgf.vm.core.contracts import VmSpec, VolumeSpec
from mgf.vm.core.exceptions import SnapshotExists, SnapshotNotFound
from mgf.vm.core.fakes.hypervisor import FakeHypervisorBackend
from mgf.vm.core.fakes.storage import FakeStorageBackend
from mgf.vm.core.ids import SnapshotId, VolumeId

from mgf.vm.sdk.client import VmManagerClient
from mgf.vm.sdk.transport.inprocess import InProcessTransport


def _client(*, storage: bool = False) -> VmManagerClient:
    hv = FakeHypervisorBackend()
    st = FakeStorageBackend() if storage else None
    return VmManagerClient(InProcessTransport.from_adapters(hv, st))


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


# -- VM snapshots (HypervisorBackend) --------------------------------------


def test_vm_snapshot_create_list_delete_round_trip() -> None:
    client = _client()
    info = client.create_vm(_spec())
    vm_id = info.vm_id

    snap = client.snapshot_vm(vm_id, "s1", include_memory=True)
    assert isinstance(snap, SnapshotId)

    snaps = client.list_snapshots(vm_id)
    assert [s.name for s in snaps] == ["s1"]
    assert snaps[0].has_memory is True

    client.delete_snapshot(vm_id, snap)
    assert client.list_snapshots(vm_id) == []


def test_vm_snapshot_idempotent_same_name_same_content() -> None:
    client = _client()
    vm_id = client.create_vm(_spec()).vm_id
    first = client.snapshot_vm(vm_id, "s1", include_memory=False)
    again = client.snapshot_vm(vm_id, "s1", include_memory=False)
    assert first == again
    assert len(client.list_snapshots(vm_id)) == 1


def test_vm_snapshot_conflict_same_name_diff_content() -> None:
    client = _client()
    vm_id = client.create_vm(_spec()).vm_id
    client.snapshot_vm(vm_id, "s1", include_memory=False)
    with pytest.raises(SnapshotExists):
        client.snapshot_vm(vm_id, "s1", include_memory=True)


def test_vm_revert_to_unknown_snapshot_raises() -> None:
    client = _client()
    vm_id = client.create_vm(_spec()).vm_id
    with pytest.raises(SnapshotNotFound):
        client.revert_snapshot(vm_id, SnapshotId("nope"))


def test_vm_revert_to_existing_snapshot_succeeds() -> None:
    client = _client()
    vm_id = client.create_vm(_spec()).vm_id
    snap = client.snapshot_vm(vm_id, "s1")
    client.revert_snapshot(vm_id, snap)  # no raise


def test_vm_delete_snapshot_idempotent() -> None:
    client = _client()
    vm_id = client.create_vm(_spec()).vm_id
    # Deleting an already-gone snapshot is success (idempotent).
    client.delete_snapshot(vm_id, SnapshotId("gone"))


# -- volume snapshots (StorageBackend) -------------------------------------


def _make_volume(client: VmManagerClient, name: str = "vol") -> VolumeId:
    return client.create_volume(VolumeSpec(pool="default", name=name, size_bytes=1 << 30))


def test_volume_snapshot_create_list_delete_round_trip() -> None:
    client = _client(storage=True)
    vol = _make_volume(client)

    snap = client.snapshot_volume(vol, "vs1")
    assert isinstance(snap, SnapshotId)

    snaps = client.list_volume_snapshots(vol)
    assert [s.name for s in snaps] == ["vs1"]
    assert snaps[0].has_memory is False  # volume snaps never carry memory

    client.delete_volume_snapshot(snap)
    assert client.list_volume_snapshots(vol) == []


def test_volume_snapshot_idempotent_same_name() -> None:
    client = _client(storage=True)
    vol = _make_volume(client)
    first = client.snapshot_volume(vol, "vs1")
    again = client.snapshot_volume(vol, "vs1")
    assert first == again


def test_volume_revert_to_existing_snapshot_succeeds() -> None:
    client = _client(storage=True)
    vol = _make_volume(client)
    snap = client.snapshot_volume(vol, "vs1")
    client.revert_volume_snapshot(vol, snap)  # no raise


def test_volume_revert_to_unknown_snapshot_raises() -> None:
    client = _client(storage=True)
    vol = _make_volume(client)
    with pytest.raises(SnapshotNotFound):
        client.revert_volume_snapshot(vol, SnapshotId("nope"))


def test_volume_snapshot_without_storage_backend_fails() -> None:
    # No StorageBackend injected → typed TransportError surfaced on the op.
    from mgf.vm.core.exceptions import TransportError

    client = _client(storage=False)
    with pytest.raises(TransportError):
        client.snapshot_volume(VolumeId("v"), "vs1")
