"""P — :class:`LibvirtDomainPolicyBackend`, the OPTIONAL ``DomainPolicyBackend``
(D-G25).

Implements ``mgf.vm.core.contracts.DomainPolicyBackend`` (Wave-R7) against
libvirt. Like :class:`mgf.vm.libvirt._metrics.LibvirtMetricsBackend`, it lives
in **its own module** because ``DomainPolicyBackend`` is a *separate, injectable*
Protocol (D-G25): a composition that wants the host-boot autostart flag +
managed-save constructs one of these over the **same**
:class:`mgf.vm.libvirt._connection.LibvirtConnection` the hypervisor backend
holds; a composition that wants neither never builds it.

Field sourcing mirrors the legacy ``LibvirtProvider``:

* :meth:`get_autostart` ← ``domain.autostart()`` (libvirt returns 0/1);
* :meth:`set_autostart` → ``domain.setAutostart(1|0)``;
* :meth:`managed_save` → ``domain.managedSave(0)`` (suspend RAM to disk + stop;
  libvirt raises ``VIR_ERR_OPERATION_INVALID`` for a non-running domain, which
  the seam normalises to a typed error).

Every native call routes through the RLock-guarded connection
(``with self._conn.lock``, §8) and every public method is decorated with
:func:`mgf.vm.libvirt._errors.translate_at_seam`, so no raw ``libvirtError``
escapes (§7).
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from mgf.vm.libvirt._errors import LibvirtErrorTranslator, translate_at_seam

if TYPE_CHECKING:
    from types import TracebackType

    from mgf.vm.core.ids import DomainId

    from mgf.vm.libvirt._connection import LibvirtConnection


class LibvirtDomainPolicyBackend:
    """``DomainPolicyBackend`` over a single libvirt connection (D-G25).

    Construct with an already-wrapped :class:`LibvirtConnection` — the same one
    the hypervisor backend uses — so a composition can inject this alongside the
    lifecycle surface without opening a second connection.
    """

    def __init__(self, connection: LibvirtConnection) -> None:
        self._conn = connection
        self._translator = LibvirtErrorTranslator()

    # -- seam glue ----------------------------------------------------------

    def translate_exception(self, exc: Exception) -> Any:
        """Delegate to the foundation translator (used by ``translate_at_seam``)."""
        return self._translator.translate_exception(exc)

    # -- the DomainPolicyBackend Protocol -----------------------------------

    @translate_at_seam
    def get_autostart(self, d: DomainId) -> bool:
        """Return ``d``'s host-boot autostart flag (``domain.autostart()``).

        Read-only + idempotent. An unknown domain raises ``DomainNotFound``.
        """
        with self._conn.lock:
            dom = self._resolve(self._conn.raw(), d)
            return bool(dom.autostart())

    @translate_at_seam
    def set_autostart(self, d: DomainId, *, enabled: bool) -> None:
        """Set ``d``'s autostart flag (``domain.setAutostart``).

        An unknown domain raises ``DomainNotFound``.
        """
        with self._conn.lock:
            dom = self._resolve(self._conn.raw(), d)
            dom.setAutostart(1 if enabled else 0)

    @translate_at_seam
    def managed_save(self, d: DomainId) -> None:
        """Managed-save ``d`` (``domain.managedSave(0)``): suspend RAM to disk
        and stop; the next start restores from the image.

        An unknown domain raises ``DomainNotFound``; a non-running domain
        surfaces libvirt's ``VIR_ERR_OPERATION_INVALID`` as a typed error.
        """
        with self._conn.lock:
            dom = self._resolve(self._conn.raw(), d)
            dom.managedSave(0)

    @staticmethod
    def _resolve(conn: Any, d: DomainId) -> Any:
        """Resolve a ``virDomain`` by UUID, normalising any lookup failure to
        ``DomainNotFound`` (mirrors :class:`LibvirtMetricsBackend._resolve`).

        Caller MUST hold the connection lock.
        """
        from mgf.vm.core.exceptions import DomainNotFound

        try:
            return conn.lookupByUUIDString(d.value)
        except Exception as exc:
            raise DomainNotFound(f"no domain {d.value!r}") from exc

    # -- lifecycle ----------------------------------------------------------

    def __enter__(self) -> LibvirtDomainPolicyBackend:
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        tb: TracebackType | None,
    ) -> None:
        # The connection lifecycle is owned by whoever opened it (shared with
        # the hypervisor backend), so this is a no-op exit.
        return None


__all__ = ["LibvirtDomainPolicyBackend"]
