using Grpc.Core;
using ZB.MOM.WW.MxGateway.Contracts.Proto;

namespace ZB.MOM.WW.MxGateway.Client.Tests;

/// <summary>
/// Fake implementation of IMxGatewayClientTransport for testing.
/// </summary>
internal sealed class FakeGatewayTransport(MxGatewayClientOptions options) : IMxGatewayClientTransport
{
    private readonly Queue<MxCommandReply> _invokeReplies = new();
    private readonly List<MxEvent> _events = [];

    /// <inheritdoc />
    public MxGatewayClientOptions Options { get; } = options;

    /// <inheritdoc />
    public MxAccessGateway.MxAccessGatewayClient? RawClient => null;

    /// <summary>
    /// Gets the list of captured OpenSessionAsync calls.
    /// </summary>
    public List<(OpenSessionRequest Request, CallOptions CallOptions)> OpenSessionCalls { get; } = [];

    /// <summary>
    /// Gets the list of captured CloseSessionAsync calls.
    /// </summary>
    public List<(CloseSessionRequest Request, CallOptions CallOptions)> CloseSessionCalls { get; } = [];

    /// <summary>
    /// Gets the list of captured InvokeAsync calls.
    /// </summary>
    public List<(MxCommandRequest Request, CallOptions CallOptions)> InvokeCalls { get; } = [];

    /// <summary>
    /// Gets the list of captured StreamEventsAsync calls.
    /// </summary>
    public List<(StreamEventsRequest Request, CallOptions CallOptions)> StreamEventsCalls { get; } = [];

    /// <summary>
    /// Gets the list of captured AcknowledgeAlarmAsync calls.
    /// </summary>
    public List<(AcknowledgeAlarmRequest Request, CallOptions CallOptions)> AcknowledgeAlarmCalls { get; } = [];

    /// <summary>
    /// Gets the list of captured QueryActiveAlarmsAsync calls.
    /// </summary>
    public List<(QueryActiveAlarmsRequest Request, CallOptions CallOptions)> QueryActiveAlarmsCalls { get; } = [];

    /// <summary>
    /// Gets the list of captured StreamAlarmsAsync calls.
    /// </summary>
    public List<(StreamAlarmsRequest Request, CallOptions CallOptions)> StreamAlarmsCalls { get; } = [];

    /// <summary>
    /// Gets the queue of exceptions to throw from AcknowledgeAlarmAsync.
    /// </summary>
    public Queue<Exception> AcknowledgeAlarmExceptions { get; } = new();

    private readonly Queue<AcknowledgeAlarmReply> _acknowledgeReplies = new();
    private readonly List<ActiveAlarmSnapshot> _activeAlarmSnapshots = [];
    private readonly List<AlarmFeedMessage> _alarmFeedMessages = [];

    /// <summary>
    /// Gets or sets the reply to return from OpenSessionAsync.
    /// </summary>
    public OpenSessionReply OpenSessionReply { get; set; } = new()
    {
        SessionId = "session-fixture",
        BackendName = "mxaccess-worker",
        GatewayProtocolVersion = 1,
        WorkerProtocolVersion = 1,
        ProtocolStatus = new ProtocolStatus { Code = ProtocolStatusCode.Ok },
    };

    /// <summary>
    /// Gets or sets the reply to return from CloseSessionAsync.
    /// </summary>
    public CloseSessionReply CloseSessionReply { get; set; } = new()
    {
        SessionId = "session-fixture",
        FinalState = SessionState.Closed,
        ProtocolStatus = new ProtocolStatus { Code = ProtocolStatusCode.Ok },
    };

    /// <summary>
    /// Gets the queue of exceptions to throw from OpenSessionAsync.
    /// </summary>
    public Queue<Exception> OpenSessionExceptions { get; } = new();

    /// <summary>
    /// Gets the queue of exceptions to throw from CloseSessionAsync.
    /// </summary>
    public Queue<Exception> CloseSessionExceptions { get; } = new();

    /// <summary>
    /// Gets the queue of exceptions to throw from InvokeAsync.
    /// </summary>
    public Queue<Exception> InvokeExceptions { get; } = new();

    /// <inheritdoc />
    public Task<OpenSessionReply> OpenSessionAsync(
        OpenSessionRequest request,
        CallOptions callOptions)
    {
        OpenSessionCalls.Add((request, callOptions));
        if (OpenSessionExceptions.TryDequeue(out Exception? exception))
        {
            throw exception;
        }

        return Task.FromResult(OpenSessionReply);
    }

    /// <inheritdoc />
    public Task<CloseSessionReply> CloseSessionAsync(
        CloseSessionRequest request,
        CallOptions callOptions)
    {
        CloseSessionCalls.Add((request, callOptions));
        if (CloseSessionExceptions.TryDequeue(out Exception? exception))
        {
            throw exception;
        }

        return Task.FromResult(CloseSessionReply);
    }

    /// <inheritdoc />
    public Task<MxCommandReply> InvokeAsync(
        MxCommandRequest request,
        CallOptions callOptions)
    {
        InvokeCalls.Add((request, callOptions));
        if (InvokeExceptions.TryDequeue(out Exception? exception))
        {
            throw exception;
        }

        return Task.FromResult(_invokeReplies.Dequeue());
    }

    /// <inheritdoc />
    public async IAsyncEnumerable<MxEvent> StreamEventsAsync(
        StreamEventsRequest request,
        CallOptions callOptions)
    {
        StreamEventsCalls.Add((request, callOptions));

        foreach (MxEvent gatewayEvent in _events)
        {
            callOptions.CancellationToken.ThrowIfCancellationRequested();
            await Task.Yield();
            yield return gatewayEvent;
        }
    }

    /// <summary>
    /// Enqueues a reply to be returned from the next InvokeAsync call.
    /// </summary>
    /// <param name="reply">The reply to enqueue.</param>
    public void AddInvokeReply(MxCommandReply reply)
    {
        _invokeReplies.Enqueue(reply);
    }

    /// <summary>
    /// Enqueues an event to be yielded from StreamEventsAsync.
    /// </summary>
    /// <param name="gatewayEvent">The event to enqueue.</param>
    public void AddEvent(MxEvent gatewayEvent)
    {
        _events.Add(gatewayEvent);
    }

    /// <inheritdoc />
    public Task<AcknowledgeAlarmReply> AcknowledgeAlarmAsync(
        AcknowledgeAlarmRequest request,
        CallOptions callOptions)
    {
        AcknowledgeAlarmCalls.Add((request, callOptions));
        if (AcknowledgeAlarmExceptions.TryDequeue(out Exception? exception))
        {
            throw exception;
        }

        return Task.FromResult(_acknowledgeReplies.Count > 0
            ? _acknowledgeReplies.Dequeue()
            : new AcknowledgeAlarmReply
            {
                CorrelationId = request.ClientCorrelationId,
                ProtocolStatus = new ProtocolStatus { Code = ProtocolStatusCode.Ok },
                Status = new MxStatusProxy { Success = 1, Category = MxStatusCategory.Ok },
            });
    }

    /// <inheritdoc />
    public async IAsyncEnumerable<ActiveAlarmSnapshot> QueryActiveAlarmsAsync(
        QueryActiveAlarmsRequest request,
        CallOptions callOptions)
    {
        QueryActiveAlarmsCalls.Add((request, callOptions));

        foreach (ActiveAlarmSnapshot snapshot in _activeAlarmSnapshots)
        {
            callOptions.CancellationToken.ThrowIfCancellationRequested();
            await Task.Yield();
            yield return snapshot;
        }
    }

    /// <summary>Enqueues an acknowledge reply.</summary>
    /// <param name="reply">The acknowledge reply to enqueue.</param>
    public void AddAcknowledgeReply(AcknowledgeAlarmReply reply)
    {
        _acknowledgeReplies.Enqueue(reply);
    }

    /// <summary>Enqueues a snapshot to be yielded from QueryActiveAlarmsAsync.</summary>
    /// <param name="snapshot">The snapshot to enqueue.</param>
    public void AddActiveAlarmSnapshot(ActiveAlarmSnapshot snapshot)
    {
        _activeAlarmSnapshots.Add(snapshot);
    }

    /// <inheritdoc />
    public async IAsyncEnumerable<AlarmFeedMessage> StreamAlarmsAsync(
        StreamAlarmsRequest request,
        CallOptions callOptions)
    {
        StreamAlarmsCalls.Add((request, callOptions));

        foreach (AlarmFeedMessage message in _alarmFeedMessages)
        {
            callOptions.CancellationToken.ThrowIfCancellationRequested();
            await Task.Yield();
            yield return message;
        }
    }

    /// <summary>Enqueues an alarm feed message to be yielded from StreamAlarmsAsync.</summary>
    /// <param name="message">The alarm feed message to enqueue.</param>
    public void AddAlarmFeedMessage(AlarmFeedMessage message)
    {
        _alarmFeedMessages.Add(message);
    }
}
