"""Integration — real libvirt storage round-trip through the SDK (B3/B4).

Drives ``create_volume → snapshot_volume → delete_volume`` against
``qemu:///session`` (the libvirt session connection — no root, no KVM needed
for the storage path) via ``InProcessTransport`` + ``VmManagerClient``. Proves
the storage orchestration works against a *real* ``StorageBackend``, not just
``FakeStorageBackend``.

Mirrors the libvirt adapter's own storage conformance fixture: a **transient**
``dir`` pool named ``default`` in a temp dir (``qemu:///session`` ships no
persistent pools). Marked ``@pytest.mark.integration``; the adapter + binding
imports are guarded and the whole module **skips gracefully** when libvirt /
``qemu-img`` is unavailable, so a default ``uv run pytest`` stays green.
"""

from __future__ import annotations

import shutil
import uuid
from typing import TYPE_CHECKING

import pytest

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

if TYPE_CHECKING:
    from collections.abc import Iterator
    from pathlib import Path

pytestmark = pytest.mark.integration

# Guard the adapter + binding import — they live only in the test-integration
# extra. A missing binding is a skip, not a collection error.
_libvirt_mod = pytest.importorskip("mgf.vm.libvirt")
LibvirtStorageBackend = _libvirt_mod.LibvirtStorageBackend
LibvirtHypervisorBackend = _libvirt_mod.LibvirtHypervisorBackend

_SESSION_URI = "qemu:///session"


@pytest.fixture
def pool_name() -> str:
    """A unique pool name per test.

    Unlike the core conformance helpers (which hard-code ``"default"``), the SDK
    passes the pool name explicitly, so a per-test unique name sidesteps any
    stale / inactive pool left on the session bus and avoids cross-test
    collisions on the transient-pool registry.
    """
    return f"sdk-itest-pool-{uuid.uuid4().hex[:12]}"


@pytest.fixture
def storage_client(tmp_path: Path, pool_name: str) -> Iterator[VmManagerClient]:
    """A ``VmManagerClient`` over a live ``LibvirtStorageBackend`` (+ hypervisor).

    Creates a fresh transient dir pool in a unique temp dir; tears it down
    (force-cascades volumes) and closes the connection (DP-13) afterward.
    """
    if shutil.which("qemu-img") is None:
        pytest.skip("qemu-img not available")
    try:
        import libvirt
    except ImportError:  # pragma: no cover - env-dependent
        pytest.skip("libvirt-python not available")
    try:
        conn = libvirt.open(_SESSION_URI)
    except libvirt.libvirtError as exc:  # pragma: no cover - env-dependent
        pytest.skip(f"cannot open {_SESSION_URI}: {exc}")
    if conn is None:  # pragma: no cover - env-dependent
        pytest.skip(f"cannot open {_SESSION_URI}")
    conn.close()

    storage = LibvirtStorageBackend.open(_SESSION_URI)
    hypervisor = LibvirtHypervisorBackend(_SESSION_URI)
    target = tmp_path / "pool"
    target.mkdir(parents=True, exist_ok=True)

    from mgf.vm.core.contracts import PoolSpec

    try:
        storage.create_pool(PoolSpec(name=pool_name, pool_type="dir", target_path=str(target)))
        transport = InProcessTransport.from_adapters(hypervisor, storage)
        with VmManagerClient(transport) as client:
            yield client
    finally:
        try:
            storage.delete_pool(pool_name, force=True)
        except Exception:  # noqa: S110 - teardown is best-effort
            pass


def test_real_libvirt_volume_create_snapshot_delete(
    storage_client: VmManagerClient,
    pool_name: str,
) -> None:
    from mgf.vm.core.contracts import VolumeSpec
    from mgf.vm.core.exceptions import VolumeHasSnapshots, VolumeNotFound
    from mgf.vm.core.ids import SnapshotId, VolumeId

    spec = VolumeSpec(
        pool=pool_name,
        name=f"sdk-itest-{uuid.uuid4().hex[:8]}",
        size_bytes=64 * (1 << 20),
        format="qcow2",
    )
    vol = storage_client.create_volume(spec)
    assert isinstance(vol, VolumeId)

    try:
        assert storage_client.get_volume(vol).name == spec.name
        assert any(v.volume_id == vol for v in storage_client.list_volumes(pool_name))

        snap = storage_client.snapshot_volume(vol, "itest-snap")
        assert isinstance(snap, SnapshotId)
        assert [s.name for s in storage_client.list_volume_snapshots(vol)] == ["itest-snap"]

        # Reference-counted GC: a volume with snapshots refuses delete (§4).
        with pytest.raises(VolumeHasSnapshots):
            storage_client.delete_volume(vol)

        storage_client.delete_volume_snapshot(snap)
        storage_client.delete_volume(vol)
        with pytest.raises(VolumeNotFound):
            storage_client.get_volume(vol)
        vol = None  # type: ignore[assignment]  # already deleted; skip finally
    finally:
        if vol is not None:
            try:
                storage_client.delete_volume(vol)
            except Exception:  # noqa: S110 - best-effort cleanup
                pass


def test_real_libvirt_storage_capabilities(storage_client: VmManagerClient) -> None:
    caps = storage_client.storage_capabilities()
    assert "qcow2" in caps.supports_formats
    assert "dir" in caps.supports_pool_types
