"""Unit tests for the real ``_qemu_img`` runner + snapshot-list parsing.

These exercise the production ``_qemu_img`` (subprocess) and
``_read_qcow2_snapshots`` paths that the main backend tests monkeypatch out,
plus the lazy snapshot-tree seeding from an existing ``qemu-img -l`` view.
"""

from __future__ import annotations

import subprocess
from typing import TYPE_CHECKING, Any

import pytest
from mgf.vm.core.exceptions import StorageError

from mgf.vm.libvirt._connection import LibvirtConnection
from mgf.vm.libvirt._storage import LibvirtStorageBackend

if TYPE_CHECKING:
    from collections.abc import Iterator


class _Conn:
    def getVersion(self) -> int:  # noqa: N802
        return 9_000_005

    def close(self) -> int:
        return 0


@pytest.fixture
def backend() -> Iterator[LibvirtStorageBackend]:
    be = LibvirtStorageBackend.adopt(LibvirtConnection.adopt(_Conn(), uri="test:///q"))
    yield be
    be.close()


def test_qemu_img_missing_binary(
    backend: LibvirtStorageBackend, monkeypatch: pytest.MonkeyPatch
) -> None:
    monkeypatch.setattr("mgf.vm.libvirt._storage.shutil.which", lambda _n: None)
    with pytest.raises(StorageError, match="qemu-img not found"):
        backend._qemu_img(["snapshot", "-l", "/x"])


def test_qemu_img_nonzero_exit(
    backend: LibvirtStorageBackend, monkeypatch: pytest.MonkeyPatch
) -> None:
    monkeypatch.setattr("mgf.vm.libvirt._storage.shutil.which", lambda _n: "/usr/bin/qemu-img")

    class _Proc:
        returncode = 1
        stderr = "boom"
        stdout = ""

    monkeypatch.setattr("mgf.vm.libvirt._storage.subprocess.run", lambda *_a, **_k: _Proc())
    with pytest.raises(StorageError, match="exited 1"):
        backend._qemu_img(["snapshot", "-c", "s", "/x"])


def test_qemu_img_run_error(
    backend: LibvirtStorageBackend, monkeypatch: pytest.MonkeyPatch
) -> None:
    monkeypatch.setattr("mgf.vm.libvirt._storage.shutil.which", lambda _n: "/usr/bin/qemu-img")

    def _boom(*_a: Any, **_k: Any) -> Any:
        raise subprocess.TimeoutExpired(cmd="qemu-img", timeout=1)

    monkeypatch.setattr("mgf.vm.libvirt._storage.subprocess.run", _boom)
    with pytest.raises(StorageError, match="failed to run"):
        backend._qemu_img(["convert", "-O", "raw", "/a", "/b"])


def test_qemu_img_success_returns_stdout(
    backend: LibvirtStorageBackend, monkeypatch: pytest.MonkeyPatch
) -> None:
    monkeypatch.setattr("mgf.vm.libvirt._storage.shutil.which", lambda _n: "/usr/bin/qemu-img")

    class _Proc:
        returncode = 0
        stderr = ""
        stdout = "ok-output"

    monkeypatch.setattr("mgf.vm.libvirt._storage.subprocess.run", lambda *_a, **_k: _Proc())
    assert backend._qemu_img(["info", "/x"]) == "ok-output"


def test_read_qcow2_snapshots_parses_list(
    backend: LibvirtStorageBackend, monkeypatch: pytest.MonkeyPatch
) -> None:
    listing = (
        "Snapshot list:\n"
        "ID        TAG               VM SIZE\n"
        "1         alpha             0 B\n"
        "2         beta              0 B\n"
        "garbage line with no id\n"
    )
    monkeypatch.setattr(backend, "_qemu_img", lambda _args: listing)
    rows = backend._read_qcow2_snapshots("/disk.qcow2")
    assert [name for name, _ in rows] == ["alpha", "beta"]


def test_read_qcow2_snapshots_swallows_error(
    backend: LibvirtStorageBackend, monkeypatch: pytest.MonkeyPatch
) -> None:
    def _boom(_args: list[str]) -> str:
        raise StorageError("no such file")

    monkeypatch.setattr(backend, "_qemu_img", _boom)
    assert backend._read_qcow2_snapshots("/missing") == []


def test_snapshot_tree_seeded_from_existing_list(
    backend: LibvirtStorageBackend, monkeypatch: pytest.MonkeyPatch
) -> None:
    # A backend that restarts must still SEE pre-existing snapshots (so it
    # refuses to delete the volume) by reading qemu-img -l lazily.
    monkeypatch.setattr(backend, "_qemu_img", lambda _args: "ID  TAG\n1  preexisting\n")
    assert backend._volume_has_snapshots("/disk.qcow2", "qcow2") is True


def test_format_version_decoding() -> None:
    assert LibvirtStorageBackend._format_version(9_000_005) == "9.0.5"
    assert LibvirtStorageBackend._format_version(8_002_000) == "8.2.0"
