using Google.Protobuf.WellKnownTypes;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
using ZB.MOM.WW.HistorianGateway.Contracts.Grpc;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

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

public sealed class GatewayHistorianDataSourceTests
{
    [Fact]
    public async Task ReadRaw_maps_samples_and_passes_args()
    {
        var fake = new FakeHistorianGatewayClient
        {
            RawSamples = new[]
            {
                new HistorianSample { Tag = "T", NumericValue = 1.0, OpcQuality = 192, Timestamp = Ts(2026, 1, 1, 0, 0, 0) },
                new HistorianSample { Tag = "T", NumericValue = 2.0, OpcQuality = 0, Timestamp = Ts(2026, 1, 1, 0, 0, 1) },
            },
        };
        var ds = new GatewayHistorianDataSource(fake, NullLogger<GatewayHistorianDataSource>.Instance);
        var r = await ds.ReadRawAsync("T", DateTime.UtcNow.AddMinutes(-5), DateTime.UtcNow, 100, TestContext.Current.CancellationToken);
        Assert.Equal(2, r.Samples.Count);
        Assert.Equal(0x80000000u, r.Samples[1].StatusCode); // Bad from quality 0
        Assert.Equal("T", fake.LastReadRawTag);
        Assert.Equal(100, fake.LastReadRawMaxValues);
    }

    [Fact]
    public async Task ReadProcessed_uses_aggregate_mode_mapping()
    {
        var fake = new FakeHistorianGatewayClient();
        var ds = new GatewayHistorianDataSource(fake, NullLogger<GatewayHistorianDataSource>.Instance);
        await ds.ReadProcessedAsync("T", default, default, TimeSpan.FromSeconds(60), HistoryAggregateType.Minimum, TestContext.Current.CancellationToken);
        Assert.Equal(RetrievalMode.MinimumWithTime, fake.LastAggregateMode);
        Assert.Equal(TimeSpan.FromSeconds(60), fake.LastAggregateInterval);
    }

    [Fact]
    public async Task ReadAtTime_aligns_one_snapshot_per_timestamp_with_gaps_Bad()
    {
        var fake = new FakeHistorianGatewayClient();
        var t0 = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        var t1 = t0.AddSeconds(1);
        fake.AtTimeSamples = new[] { new HistorianSample { NumericValue = 5.0, OpcQuality = 192, Timestamp = Timestamp.FromDateTime(t0) } };
        var ds = new GatewayHistorianDataSource(fake, NullLogger<GatewayHistorianDataSource>.Instance);
        var r = await ds.ReadAtTimeAsync("T", new[] { t0, t1 }, TestContext.Current.CancellationToken);
        Assert.Equal(2, r.Samples.Count);          // exactly one per requested ts, in order
        Assert.Equal(5.0, r.Samples[0].Value);
        Assert.Equal(0x80000000u, r.Samples[1].StatusCode); // gap → Bad at requested ts
    }

    [Fact]
    public async Task Empty_window_is_not_a_fault()
    {
        var fake = new FakeHistorianGatewayClient { RawSamples = Array.Empty<HistorianSample>() };
        var ds = new GatewayHistorianDataSource(fake, NullLogger<GatewayHistorianDataSource>.Instance);
        var r = await ds.ReadRawAsync("T", default, default, 10, TestContext.Current.CancellationToken);
        Assert.Empty(r.Samples);                    // GoodNoData-empty, no throw
    }

    [Fact]
    public async Task Disposing_data_source_disposes_client()
    {
        var fake = new FakeHistorianGatewayClient();
        var ds = new GatewayHistorianDataSource(fake, NullLogger<GatewayHistorianDataSource>.Instance);
        await ds.DisposeAsync();
        Assert.Equal(1, fake.DisposeCallCount);
    }

    [Fact]
    public void Dispose_via_sync_path_disposes_client()
    {
        var fake = new FakeHistorianGatewayClient();
        var ds = new GatewayHistorianDataSource(fake, NullLogger<GatewayHistorianDataSource>.Instance);
        ((IDisposable)ds).Dispose();
        Assert.Equal(1, fake.DisposeCallCount);
    }

    // Ts(...) builds a Google.Protobuf.WellKnownTypes.Timestamp from UTC parts.
    private static Timestamp Ts(int y, int mo, int d, int h, int mi, int s)
        => Timestamp.FromDateTime(new DateTime(y, mo, d, h, mi, s, DateTimeKind.Utc));
}
