"""``FakeNetworkBackend`` — in-memory implementation of the OPTIONAL
:class:`~mgf.vm.core.contracts.NetworkBackend` (Wave-R3, D-G15).

Modeled on the sibling fakes (``FakeHostDiskBackend`` et al., D-F3): a working
in-memory adapter with correct semantics, not a mock. It **writes through to a
:class:`~mgf.vm.core.fakes.hypervisor.FakeHypervisorBackend`'s network store**,
so a network defined / started / stopped / deleted through this management
surface is immediately observable via that hypervisor's
:meth:`~mgf.vm.core.fakes.hypervisor.FakeHypervisorBackend.list_networks`
(D-G14). This mirrors the real libvirt deployment, where the
``LibvirtNetworkBackend`` and the ``LibvirtHypervisorBackend`` speak to one
``libvirtd`` — and it is what lets the CONF-N-01 conformance scenario verify
management by *observing* its effects.

This backend is OPTIONAL (D-G15): a composition that passes no network backend
is unaffected. Where one is wanted, this fake satisfies ``NetworkBackend`` so
downstream suites and the dev loop exercise the surface without a real engine.
"""

from __future__ import annotations

from dataclasses import replace
from typing import TYPE_CHECKING

from mgf.vm.core.contracts import NetworkProfile
from mgf.vm.core.exceptions import NetworkExists, NetworkNotFound

if TYPE_CHECKING:
    from mgf.vm.core.contracts import NetworkSpec
    from mgf.vm.core.fakes.hypervisor import FakeHypervisorBackend


class FakeNetworkBackend:
    """In-memory virtual-network manager — D-F3 first-class fake.

    Constructed over a :class:`FakeHypervisorBackend` whose network store it
    mutates (sharing that backend's ``RLock`` for thread-safety, consistent
    with the other fakes' rules, ``contracts.md`` §8). Management mutations are
    therefore observable through the hypervisor's ``list_networks``.
    """

    def __init__(self, hypervisor: FakeHypervisorBackend) -> None:
        self._hv = hypervisor
        self._closed = False

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

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

    def close(self) -> None:
        # Does NOT close the shared hypervisor (it owns that lifecycle, DP-13).
        self._closed = True

    def __enter__(self) -> FakeNetworkBackend:
        return self

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

    # --- the NetworkBackend Protocol -------------------------------------

    def define_network(self, spec: NetworkSpec) -> NetworkProfile:
        """Define ``spec`` (inactive); return its observed profile (D-G15)."""
        with self._hv._lock:
            store = self._store()
            existing = store.get(spec.name)
            profile = NetworkProfile(
                name=spec.name,
                bridge=spec.bridge,
                active=False,
                forward_mode=spec.forward_mode,
            )
            if existing is not None:
                # Idempotent on an identical desired shape (preserve active
                # state); a different shape on the same name is a conflict.
                if (
                    existing.bridge == profile.bridge
                    and existing.forward_mode == profile.forward_mode
                ):
                    return existing
                msg = f"network {spec.name!r} already exists"
                raise NetworkExists(msg)
            store[spec.name] = profile
            return profile

    def start_network(self, name: str) -> None:
        """Activate ``name`` (idempotent); raise ``NetworkNotFound`` if absent."""
        with self._hv._lock:
            profile = self._require(name)
            self._store()[name] = replace(profile, active=True)

    def stop_network(self, name: str) -> None:
        """Deactivate ``name`` (idempotent); raise ``NetworkNotFound`` if absent."""
        with self._hv._lock:
            profile = self._require(name)
            self._store()[name] = replace(profile, active=False)

    def delete_network(self, name: str) -> None:
        """Undefine ``name``. Idempotent — deleting an absent network is a
        no-op success (D-G15)."""
        with self._hv._lock:
            self._store().pop(name, None)

    # --- internal helpers ------------------------------------------------

    def _store(self) -> dict[str, NetworkProfile]:
        return self._hv._networks

    def _require(self, name: str) -> NetworkProfile:
        profile = self._store().get(name)
        if profile is None:
            msg = f"no network {name!r}"
            raise NetworkNotFound(msg)
        return profile


__all__ = ["FakeNetworkBackend"]
