using System.Runtime.CompilerServices;
using ZB.MOM.WW.HistorianGateway.Contracts.Grpc;

namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests;

/// <summary>
/// Reusable in-memory test double for <see cref="IHistorianGatewayClient"/>. Every method returns
/// from a public settable result field and records its call arguments into public fields, so the
/// later driver tasks (T7/T8/T11/T12/T14) can drive behaviour and assert on what the driver sent
/// without a live gateway. Throw fields let a test simulate transport faults (e.g. an
/// <c>RpcException</c>) per operation; reads share <see cref="ThrowOnRead"/>.
/// </summary>
public sealed class FakeHistorianGatewayClient : IHistorianGatewayClient
{
    // ---- ReadRaw -------------------------------------------------------------------------------
    public IReadOnlyList<HistorianSample> RawSamples = Array.Empty<HistorianSample>();
    public string? LastReadRawTag;
    public DateTime LastReadRawStartUtc;
    public DateTime LastReadRawEndUtc;
    public int LastReadRawMaxValues;
    public int ReadRawCallCount;

    // ---- ReadAggregate -------------------------------------------------------------------------
    public IReadOnlyList<HistorianAggregateSample> AggregateSamples = Array.Empty<HistorianAggregateSample>();
    public string? LastAggregateTag;
    public DateTime LastAggregateStartUtc;
    public DateTime LastAggregateEndUtc;
    public RetrievalMode LastAggregateMode;
    public TimeSpan LastAggregateInterval;
    public int ReadAggregateCallCount;

    // ---- ReadAtTime ----------------------------------------------------------------------------
    public IReadOnlyList<HistorianSample> AtTimeSamples = Array.Empty<HistorianSample>();
    public string? LastReadAtTimeTag;
    public IReadOnlyList<DateTime>? LastReadAtTimeTimestamps;
    public int ReadAtTimeCallCount;

    // ---- ReadEvents ----------------------------------------------------------------------------
    public IReadOnlyList<HistorianEvent> Events = Array.Empty<HistorianEvent>();
    public string? LastReadEventsSourceName;
    public DateTime LastReadEventsStartUtc;
    public DateTime LastReadEventsEndUtc;
    public int LastReadEventsMaxEvents;
    public int ReadEventsCallCount;

    /// <summary>Thrown (deferred to first enumeration) by every read method when set.</summary>
    public Exception? ThrowOnRead;

    // ---- WriteLiveValues -----------------------------------------------------------------------
    public WriteAck WriteLiveValuesResult = new() { Success = true };
    public string? LastWriteLiveTag;
    public IReadOnlyList<HistorianLiveValue>? LastWriteLiveValues;
    public int WriteLiveValuesCallCount;
    public Exception? WriteLiveValuesThrows;

    // ---- SendEvent -----------------------------------------------------------------------------
    public WriteAck SendEventResult = new() { Success = true };
    public HistorianEvent? LastSendEvent;
    public int SendEventCallCount;
    public Exception? SendEventThrows;

    // ---- EnsureTags ----------------------------------------------------------------------------
    public TagOperationResults EnsureTagsResult = new();
    public IReadOnlyList<HistorianTagDefinition>? LastEnsureDefinitions;
    public int EnsureTagsCallCount;
    public Exception? EnsureTagsThrows;

    // ---- Probe ---------------------------------------------------------------------------------
    public bool ProbeResult = true;
    public int ProbeCallCount;
    public Exception? ProbeThrows;

    // ---- GetConnectionStatus -------------------------------------------------------------------
    public ConnectionStatus ConnectionStatus = new();
    public int GetConnectionStatusCallCount;
    public Exception? GetConnectionStatusThrows;

    // ---- Dispose -------------------------------------------------------------------------------
    public int DisposeCallCount;

    public IAsyncEnumerable<HistorianSample> ReadRawAsync(
        string tag,
        DateTime startUtc,
        DateTime endUtc,
        int maxValues,
        CancellationToken ct)
    {
        LastReadRawTag = tag;
        LastReadRawStartUtc = startUtc;
        LastReadRawEndUtc = endUtc;
        LastReadRawMaxValues = maxValues;
        ReadRawCallCount++;
        return ToAsyncStream(RawSamples, ThrowOnRead, ct);
    }

    public IAsyncEnumerable<HistorianAggregateSample> ReadAggregateAsync(
        string tag,
        DateTime startUtc,
        DateTime endUtc,
        RetrievalMode mode,
        TimeSpan interval,
        CancellationToken ct)
    {
        LastAggregateTag = tag;
        LastAggregateStartUtc = startUtc;
        LastAggregateEndUtc = endUtc;
        LastAggregateMode = mode;
        LastAggregateInterval = interval;
        ReadAggregateCallCount++;
        return ToAsyncStream(AggregateSamples, ThrowOnRead, ct);
    }

    public Task<IReadOnlyList<HistorianSample>> ReadAtTimeAsync(
        string tag,
        IReadOnlyList<DateTime> timestampsUtc,
        CancellationToken ct)
    {
        LastReadAtTimeTag = tag;
        LastReadAtTimeTimestamps = timestampsUtc;
        ReadAtTimeCallCount++;
        return ThrowOnRead is not null
            ? Task.FromException<IReadOnlyList<HistorianSample>>(ThrowOnRead)
            : Task.FromResult(AtTimeSamples);
    }

    public IAsyncEnumerable<HistorianEvent> ReadEventsAsync(
        string? sourceName,
        DateTime startUtc,
        DateTime endUtc,
        int maxEvents,
        CancellationToken ct)
    {
        LastReadEventsSourceName = sourceName;
        LastReadEventsStartUtc = startUtc;
        LastReadEventsEndUtc = endUtc;
        LastReadEventsMaxEvents = maxEvents;
        ReadEventsCallCount++;
        return ToAsyncStream(Events, ThrowOnRead, ct);
    }

    public Task<WriteAck> WriteLiveValuesAsync(
        string tag,
        IReadOnlyList<HistorianLiveValue> values,
        CancellationToken ct)
    {
        LastWriteLiveTag = tag;
        LastWriteLiveValues = values;
        WriteLiveValuesCallCount++;
        return WriteLiveValuesThrows is not null
            ? Task.FromException<WriteAck>(WriteLiveValuesThrows)
            : Task.FromResult(WriteLiveValuesResult);
    }

    public Task<WriteAck> SendEventAsync(HistorianEvent evt, CancellationToken ct)
    {
        LastSendEvent = evt;
        SendEventCallCount++;
        return SendEventThrows is not null
            ? Task.FromException<WriteAck>(SendEventThrows)
            : Task.FromResult(SendEventResult);
    }

    public Task<TagOperationResults> EnsureTagsAsync(
        IReadOnlyList<HistorianTagDefinition> definitions,
        CancellationToken ct)
    {
        LastEnsureDefinitions = definitions;
        EnsureTagsCallCount++;
        return EnsureTagsThrows is not null
            ? Task.FromException<TagOperationResults>(EnsureTagsThrows)
            : Task.FromResult(EnsureTagsResult);
    }

    public Task<bool> ProbeAsync(CancellationToken ct)
    {
        ProbeCallCount++;
        return ProbeThrows is not null
            ? Task.FromException<bool>(ProbeThrows)
            : Task.FromResult(ProbeResult);
    }

    public Task<ConnectionStatus> GetConnectionStatusAsync(CancellationToken ct)
    {
        GetConnectionStatusCallCount++;
        return GetConnectionStatusThrows is not null
            ? Task.FromException<ConnectionStatus>(GetConnectionStatusThrows)
            : Task.FromResult(ConnectionStatus);
    }

    public ValueTask DisposeAsync()
    {
        DisposeCallCount++;
        return ValueTask.CompletedTask;
    }

    private static async IAsyncEnumerable<T> ToAsyncStream<T>(
        IReadOnlyList<T> items,
        Exception? error,
        [EnumeratorCancellation] CancellationToken ct)
    {
        if (error is not null)
        {
            throw error;
        }

        foreach (var item in items)
        {
            ct.ThrowIfCancellationRequested();
            yield return item;
        }

        await ValueTask.CompletedTask.ConfigureAwait(false);
    }
}
