"""Behaviour tests for ``mgf.vm.core.lifecycle.rename``."""

from __future__ import annotations

import pytest

import mgf.vm.core.lifecycle.rename as rename_mod
from mgf.vm.core.common.progress import RecordingReporter
from mgf.vm.core.contracts import VmSpec
from mgf.vm.core.domain import VMHandle, VMStatus
from mgf.vm.core.exceptions import (
    DomainAlreadyExistsError,
    DomainNotFound,
    HypervisorTransientError,
    OperationError,
    SpecValidationError,
)
from mgf.vm.core.fakes.hypervisor import FakeHypervisorBackend
from mgf.vm.core.ids import DomainId
from mgf.vm.core.lifecycle.rename import DomainRenamer
from mgf.vm.core.monitor.state import StateStore


class _FakeRenamer:
    """Records the rename and optionally raises a chosen error."""

    def __init__(self, *, raises: Exception | None = None) -> None:
        self.calls: list[tuple[DomainId, str]] = []
        self._raises = raises

    def rename_domain(self, domain: DomainId, new_name: str) -> DomainId:
        if self._raises is not None:
            raise self._raises
        self.calls.append((domain, new_name))
        return DomainId(f"{domain.value}::{new_name}")


def _defined(backend: FakeHypervisorBackend, name: str = "old") -> DomainId:
    return backend.define(VmSpec(name=name, cpu=1, memory_bytes=1 << 30))


def test_renamer_protocol_is_structural() -> None:
    assert isinstance(_FakeRenamer(), DomainRenamer)


def test_rename_stopped_domain_calls_renamer_and_rekeys_store() -> None:
    backend = FakeHypervisorBackend()
    d = _defined(backend, "old")
    renamer = _FakeRenamer()
    store = StateStore()
    store.insert(VMHandle(name="old", uuid=d.value, state=VMStatus.STOPPED))
    reporter = RecordingReporter()

    new_id = rename_mod.rename(backend, renamer, store, d, "old", "new", reporter=reporter)

    assert renamer.calls == [(d, "new")]
    assert new_id == DomainId(f"{d.value}::new")
    assert store.get("new") is not None
    assert store.get("old") is None
    msgs = [c[0] for c in reporter.calls]
    assert msgs == ["renaming old -> new", "renamed to new"]


def test_rename_no_op_when_same_name_returns_same_id() -> None:
    backend = FakeHypervisorBackend()
    d = _defined(backend, "same")
    renamer = _FakeRenamer()

    result = rename_mod.rename(backend, renamer, None, d, "same", "same")

    assert result == d
    assert renamer.calls == []  # engine never touched


def test_rename_invalid_name_raises_spec_validation() -> None:
    backend = FakeHypervisorBackend()
    d = _defined(backend, "old")
    renamer = _FakeRenamer()

    with pytest.raises(SpecValidationError):
        rename_mod.rename(backend, renamer, None, d, "old", "bad name with spaces")
    assert renamer.calls == []


def test_rename_running_domain_refused() -> None:
    backend = FakeHypervisorBackend()
    d = _defined(backend, "old")
    backend.start(d)
    renamer = _FakeRenamer()

    with pytest.raises(OperationError) as ei:
        rename_mod.rename(backend, renamer, None, d, "old", "new")
    assert "running" in str(ei.value)
    assert renamer.calls == []


def test_rename_works_without_store() -> None:
    backend = FakeHypervisorBackend()
    d = _defined(backend, "old")
    renamer = _FakeRenamer()

    new_id = rename_mod.rename(backend, renamer, None, d, "old", "new")
    assert new_id == DomainId(f"{d.value}::new")


def test_rename_domain_already_exists_passes_through() -> None:
    backend = FakeHypervisorBackend()
    d = _defined(backend, "old")
    renamer = _FakeRenamer(raises=DomainAlreadyExistsError("new"))

    with pytest.raises(DomainAlreadyExistsError):
        rename_mod.rename(backend, renamer, None, d, "old", "new")


def test_rename_not_found_from_renamer_passes_through() -> None:
    backend = FakeHypervisorBackend()
    d = _defined(backend, "old")
    renamer = _FakeRenamer(raises=DomainNotFound("old"))

    with pytest.raises(DomainNotFound):
        rename_mod.rename(backend, renamer, None, d, "old", "new")


def test_rename_renamer_provider_error_translated() -> None:
    backend = FakeHypervisorBackend()
    d = _defined(backend, "old")
    renamer = _FakeRenamer(raises=HypervisorTransientError("engine refused"))

    with pytest.raises(OperationError) as ei:
        rename_mod.rename(backend, renamer, None, d, "old", "new")
    assert "could not rename" in str(ei.value)


def test_rename_get_domain_provider_error_translated() -> None:
    class _Backend:
        def get_domain(self, d: DomainId) -> object:
            raise HypervisorTransientError("cannot read state")

    renamer = _FakeRenamer()
    with pytest.raises(OperationError) as ei:
        rename_mod.rename(_Backend(), renamer, None, DomainId("old"), "old", "new")
    assert "could not read state" in str(ei.value)


def test_rename_get_domain_not_found_passes_through() -> None:
    backend = FakeHypervisorBackend()
    renamer = _FakeRenamer()

    with pytest.raises(DomainNotFound):
        rename_mod.rename(backend, renamer, None, DomainId("ghost"), "ghost", "new")
