using System.Threading.Channels;
using Grpc.Core;
using ZB.MOM.WW.LocalDb.Contracts;

namespace ZB.MOM.WW.LocalDb.Replication.Internal;

/// <summary>
/// Adapts a gRPC duplex stream (an inbound reader + an outbound writer) to the transport-agnostic
/// <see cref="SyncDuplex"/> a <see cref="SyncSession"/> runs over. Both the passive service and the
/// initiating client build one of these over their respective stream halves.
/// </summary>
internal static class GrpcSyncDuplex
{
    /// <summary>
    /// Wires <paramref name="reader"/> into an unbounded inbox channel drained by a background loop,
    /// and exposes <paramref name="writer"/> as the duplex send. Returns the duplex plus the reader
    /// loop's task so the caller can observe it. On clean stream end the inbox channel is completed;
    /// on a reader fault it is completed WITH the exception — the session's handshake and receive
    /// phases both await the inbox, so terminating it is what unblocks them when the call dies (they
    /// would otherwise hang on a dead stream).
    /// </summary>
    public static (SyncDuplex Duplex, Task ReaderTask) Create(
        IAsyncStreamReader<SyncMessage> reader,
        IAsyncStreamWriter<SyncMessage> writer,
        CancellationToken ct)
    {
        var inbox = Channel.CreateUnbounded<SyncMessage>(new UnboundedChannelOptions { SingleWriter = true });
        var readerTask = PumpInboundAsync(reader, inbox.Writer, ct);
        var duplex = new SyncDuplex
        {
            // A plain delegate is safe: the session's single writer task is the only caller of Send,
            // so this stream writer never sees the concurrent writes IServerStreamWriter /
            // IClientStreamWriter both prohibit. The gRPC WriteAsync(message, ct) overload honors the
            // token even when flow-control blocked, satisfying SyncDuplex.Send's cancellation contract.
            Send = (message, token) => writer.WriteAsync(message, token),
            Inbox = inbox.Reader,
        };
        return (duplex, readerTask);
    }

    private static async Task PumpInboundAsync(
        IAsyncStreamReader<SyncMessage> reader, ChannelWriter<SyncMessage> inbox, CancellationToken ct)
    {
        try
        {
            while (await reader.MoveNext(ct))
                inbox.TryWrite(reader.Current);
        }
        catch (Exception ex)
        {
            // Fault the inbox so the session's inbox awaiters observe the transport failure (a
            // cancelled call surfaces here as OperationCanceledException). Swallowed on this task:
            // the failure travels to the session through the channel, not by faulting the reader task.
            inbox.TryComplete(ex);
            return;
        }
        inbox.TryComplete();
    }
}
