"""Bounded request iterator for true bidirectional gRPC connectors."""

from __future__ import annotations

from typing import AsyncIterator, Generic, TypeVar

from cmvr_edge_ai.core.edge_queue import (
    EdgeQueue,
    EdgeQueueClosed,
    OverflowPolicy,
    QueueStats,
)


RequestT = TypeVar("RequestT")


class BidiRequestStreamClosed(RuntimeError):
    """Raised when a producer writes after or during stream shutdown."""


class BidiRequestStream(Generic[RequestT]):
    """An async request iterator with explicit backpressure and close."""

    def __init__(self, capacity: int = 8) -> None:
        if capacity < 1:
            raise ValueError("capacity must be at least 1")
        self._queue: EdgeQueue[RequestT] = EdgeQueue(
            capacity,
            OverflowPolicy.BLOCK,
            name="grpc-bidi-request-stream",
        )
        self._consumer_started = False

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

    def stats(self) -> QueueStats:
        return self._queue.stats()

    async def send(self, request: RequestT) -> None:
        try:
            await self._queue.put(request)
        except EdgeQueueClosed as exc:
            raise BidiRequestStreamClosed(
                "bidirectional request stream is closed"
            ) from exc

    async def close(self) -> None:
        # A normal close drains already accepted requests, while atomically
        # waking every blocked sender so none can report a false success.
        await self._queue.close()

    async def __aiter__(self) -> AsyncIterator[RequestT]:
        if self._consumer_started:
            raise RuntimeError("BidiRequestStream supports exactly one consumer")
        self._consumer_started = True
        try:
            async for item in self._queue:
                yield item
        finally:
            # If gRPC cancels or abandons the iterator, no queued request can be
            # delivered.  Discard it and wake blocked producers immediately.
            await self._queue.close(discard=True)


__all__ = ["BidiRequestStream", "BidiRequestStreamClosed"]
