"""Shared pytest fixtures for mgf-vm-sdk.

Per TESTING.md:
  * tier 1 (unit) — VmManagerClient against InProcessTransport
    pointed at mgf.vm.core fakes (FakeHypervisorBackend, etc.).
    No daemons, no network.
  * Phase 1 — GrpcTransport contract tests vs. a stub CP.

Foundation slice: this module carries :class:`FakeGrpcTransport`, a
parity stub that satisfies the :class:`~mgf.vm.sdk.transport.base.Transport`
Protocol structurally. It records submitted ops and returns canned handles —
proving the seam is transport-agnostic (a fake stands in for the real
GrpcTransport with zero changes to the client surface). The ``Grpc`` in the
name is deliberate: it is the placeholder for the Phase 1 wire transport, but
it pulls in NO gRPC dependency.
"""

from __future__ import annotations

from datetime import UTC, datetime
from typing import TYPE_CHECKING

from mgf.vm.core.contracts import OperationHandle, OperationKind
from mgf.vm.core.ids import OperationId

if TYPE_CHECKING:
    from collections.abc import Iterator

    from mgf.vm.core.contracts import EventGap, VmEvent


class FakeGrpcTransport:
    """A canned, in-memory stand-in for the Phase 1 ``GrpcTransport``.

    Satisfies the :class:`~mgf.vm.sdk.transport.base.Transport` Protocol
    structurally (verified via ``runtime_checkable`` ``isinstance`` in the
    test suite). Records every submitted op so tests can assert dispatch,
    and returns a terminal ``SUCCEEDED`` handle from :meth:`watch_op` so the
    client's two-step collapse has something to unpack.
    """

    def __init__(self, *, canned_result: object = None) -> None:
        self.submitted: list[tuple[OperationKind, object, str]] = []
        self.reads: list[object] = []
        self.closed = False
        self._canned_result = canned_result
        self._counter = 0

    def _next_op_id(self) -> OperationId:
        self._counter += 1
        return OperationId(f"op-fake-{self._counter}")

    def submit_op(
        self,
        kind: OperationKind,
        payload: object,
        *,
        client_op_id: str,
        token: object | None = None,
    ) -> OperationHandle:
        self.submitted.append((kind, payload, client_op_id))
        return OperationHandle.pending(self._next_op_id(), kind, datetime.now(UTC))

    def get_op(self, op_id: OperationId) -> OperationHandle:
        now = datetime.now(UTC)
        return OperationHandle.pending(op_id, OperationKind.VM_CREATE, now).started()

    def watch_op(self, op_id: OperationId) -> Iterator[OperationHandle]:
        now = datetime.now(UTC)
        pending = OperationHandle.pending(op_id, OperationKind.VM_CREATE, now)
        yield pending.started()
        yield pending.started().succeeded(self._canned_result, finished_at=now)

    def read(self, query: object) -> object:
        self.reads.append(query)
        return self._canned_result

    def watch_stream(self, subject: object) -> Iterator[VmEvent | EventGap]:
        return iter(())

    def close(self) -> None:
        self.closed = True
