"""VM power-state operations — pure orchestration over an injected backend.

Every "change the running state of this domain" flow goes through a
function in this module. The bodies are deliberately thin — each is a
single call to the injected :class:`~mgf.vm.core.contracts.HypervisorBackend`
wrapped in typed error translation. The real work (event delivery,
state propagation) happens above the core, in the adapter-bound
event slice that watches the backend and mirrors transitions into
:class:`~mgf.vm.core.monitor.state.StateStore`.

Purity
------
Nothing here touches libvirt, the filesystem, sockets, or a
subprocess. The :class:`HypervisorBackend` is **injected** — the
concrete engine lives behind the Protocol, in an adapter. Orchestrating
through that Protocol is pure and allowed under the import-linter
purity contract.

This is the engine-neutral re-namespacing of the legacy
``mgf.vm.lifecycle.power``. The legacy module spoke to
``mgf.vm.providers.base.VMProvider`` and a name/uuid ``VMHandle``; the
core speaks to the :class:`HypervisorBackend` seam and the typed
:class:`~mgf.vm.core.ids.DomainId`.

Deferred
--------
``managed_save``, ``set_autostart`` and ``get_autostart`` are **not**
part of the :class:`HypervisorBackend` contract (``contracts.md`` §3),
so they cannot be expressed as pure orchestration over the core seam.
They are deferred to the adapter layer (or a future contract revision).
See ``vm-vmanager-docs`` Wave-3 notes.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from mgf.common.observability import operation_span

from mgf.vm.core.exceptions import (
    DomainNotFound,
    OperationError,
    ProviderError,
)

if TYPE_CHECKING:
    from mgf.vm.core.cancellation import CancellationToken
    from mgf.vm.core.common.progress import ProgressReporter
    from mgf.vm.core.contracts import HypervisorBackend
    from mgf.vm.core.ids import DomainId


# ---------------------------------------------------------------------------
# Power transitions
# ---------------------------------------------------------------------------


def start(
    backend: HypervisorBackend,
    domain: DomainId,
    *,
    token: CancellationToken | None = None,
    reporter: ProgressReporter | None = None,
) -> None:
    """Start a defined domain.

    Transparently resumes from a managed save if one exists — the
    engine does this implicitly, so the caller never has to
    distinguish "cold boot" from "resume from saved state". Idempotent
    per the :class:`HypervisorBackend` contract (already-running is
    success).
    """
    _announce(reporter, f"starting {domain.value}")
    with operation_span("vm.start", domain_id=domain.value):
        try:
            backend.start(domain, token=token)
        except DomainNotFound:
            raise
        except ProviderError as e:
            raise OperationError(f"could not start {domain.value!r}: {e}") from e
    _announce(reporter, f"{domain.value} started")


def stop(
    backend: HypervisorBackend,
    domain: DomainId,
    *,
    force: bool = False,
    token: CancellationToken | None = None,
    reporter: ProgressReporter | None = None,
) -> None:
    """Shut down a running domain.

    ``force=False`` (the default) sends a graceful shutdown and asks
    the guest to cooperate. ``force=True`` pulls power immediately —
    callers use this when graceful shutdown hangs.
    """
    kind = "force-stopping" if force else "stopping"
    _announce(reporter, f"{kind} {domain.value}")
    with operation_span("vm.stop", domain_id=domain.value, force=force):
        try:
            backend.stop(domain, force=force, token=token)
        except DomainNotFound:
            raise
        except ProviderError as e:
            raise OperationError(f"could not stop {domain.value!r}: {e}") from e
    _announce(reporter, f"{domain.value} stopped")


def pause(
    backend: HypervisorBackend,
    domain: DomainId,
    *,
    reporter: ProgressReporter | None = None,
) -> None:
    """Suspend CPU execution; RAM stays in memory."""
    _announce(reporter, f"pausing {domain.value}")
    with operation_span("vm.pause", domain_id=domain.value):
        try:
            backend.pause(domain)
        except DomainNotFound:
            raise
        except ProviderError as e:
            raise OperationError(f"could not pause {domain.value!r}: {e}") from e
    _announce(reporter, f"{domain.value} paused")


def resume(
    backend: HypervisorBackend,
    domain: DomainId,
    *,
    reporter: ProgressReporter | None = None,
) -> None:
    """Undo :func:`pause`."""
    _announce(reporter, f"resuming {domain.value}")
    with operation_span("vm.resume", domain_id=domain.value):
        try:
            backend.resume(domain)
        except DomainNotFound:
            raise
        except ProviderError as e:
            raise OperationError(f"could not resume {domain.value!r}: {e}") from e
    _announce(reporter, f"{domain.value} resumed")


def reboot(
    backend: HypervisorBackend,
    domain: DomainId,
    *,
    force: bool = False,
    token: CancellationToken | None = None,
    reporter: ProgressReporter | None = None,
) -> None:
    """Reboot a running domain.

    ``force=False`` asks the guest to reboot gracefully; ``force=True``
    is equivalent to pulling power and starting again without a clean
    shutdown.
    """
    kind = "force-rebooting" if force else "rebooting"
    _announce(reporter, f"{kind} {domain.value}")
    with operation_span("vm.reboot", domain_id=domain.value, force=force):
        try:
            backend.reboot(domain, force=force, token=token)
        except DomainNotFound:
            raise
        except ProviderError as e:
            raise OperationError(f"could not reboot {domain.value!r}: {e}") from e
    _announce(reporter, f"{domain.value} rebooted")


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _announce(reporter: ProgressReporter | None, message: str) -> None:
    """Send a progress update if a reporter is attached."""
    if reporter is not None:
        reporter.report(message)


__all__ = [
    "pause",
    "reboot",
    "resume",
    "start",
    "stop",
]
