"""``FakeDomainPolicyBackend`` — in-memory implementation of the OPTIONAL
:class:`~mgf.vm.core.contracts.DomainPolicyBackend` (Wave-R7, D-G25).

Modeled on the sibling fakes (``FakeMetricsBackend`` / ``FakeGuestAgentBackend``):
a working in-memory adapter with correct semantics, not a mock. It tracks an
autostart flag + a running/managed-saved state per "known" domain, raising
``DomainNotFound`` for an unknown domain and ``OperationError`` when asked to
managed-save a domain that is not running.

This backend is OPTIONAL (D-G25): a composition that passes no policy backend
is unaffected. Where one is wanted (the ``vm autostart`` / ``vm hibernate``
ports), this fake satisfies ``DomainPolicyBackend`` so downstream suites and
the dev loop can exercise the surface without a real engine.
"""

from __future__ import annotations

import threading
from typing import TYPE_CHECKING

from mgf.vm.core.exceptions import DomainNotFound, OperationError

if TYPE_CHECKING:
    from collections.abc import Iterable

    from mgf.vm.core.ids import DomainId


class FakeDomainPolicyBackend:
    """In-memory per-domain autostart + managed-save adapter (D-G25 fake).

    Thread-safe via a single ``RLock`` (consistent with the other fakes'
    threading rules, ``contracts.md`` §8). Constructed with the set of domain
    IDs it "knows about"; unknown domains raise ``DomainNotFound``. With no
    arguments it knows no domains — tests register them via
    :meth:`_register_domain`.
    """

    def __init__(
        self,
        *,
        known_domains: Iterable[DomainId] | None = None,
    ) -> None:
        self._lock = threading.RLock()
        self._known: set[DomainId] = set(known_domains or ())
        self._autostart: dict[DomainId, bool] = {}
        self._running: set[DomainId] = set()
        self._managed_saved: set[DomainId] = set()
        self._closed = False

    @property
    def closed(self) -> bool:
        return self._closed

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

    def close(self) -> None:
        with self._lock:
            self._closed = True

    def __enter__(self) -> FakeDomainPolicyBackend:
        return self

    def __exit__(self, *exc: object) -> None:
        self.close()

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

    def get_autostart(self, d: DomainId) -> bool:
        """Return ``d``'s autostart flag (default ``False`` for a known but
        never-set domain). Raises ``DomainNotFound`` for an unknown domain."""
        with self._lock:
            self._require_known(d)
            return self._autostart.get(d, False)

    def set_autostart(self, d: DomainId, *, enabled: bool) -> None:
        """Set ``d``'s autostart flag. Raises ``DomainNotFound`` if absent."""
        with self._lock:
            self._require_known(d)
            self._autostart[d] = enabled

    def managed_save(self, d: DomainId) -> None:
        """Managed-save ``d``: mark it saved + no longer running. Raises
        ``DomainNotFound`` if absent; ``OperationError`` if not running."""
        with self._lock:
            self._require_known(d)
            if d not in self._running:
                msg = f"domain {d} is not running; nothing to managed-save"
                raise OperationError(msg)
            self._running.discard(d)
            self._managed_saved.add(d)

    # --- test helpers (not part of the Protocol) -------------------------

    def _require_known(self, d: DomainId) -> None:
        if d not in self._known:
            msg = f"no domain {d}"
            raise DomainNotFound(msg)

    def _register_domain(
        self,
        d: DomainId,
        *,
        running: bool = False,
        autostart: bool = False,
    ) -> None:
        """Mark a domain known, with initial running + autostart state."""
        with self._lock:
            self._known.add(d)
            self._autostart[d] = autostart
            if running:
                self._running.add(d)


__all__ = ["FakeDomainPolicyBackend"]
