using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MessagePack;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Backend;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Backend.Galaxy;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Backend.Historian;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Backend.MxAccess;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Sta;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Shared.Contracts;

namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests;

[Trait("Category", "Unit")]
public sealed class HistoryReadAtTimeTests
{
    private static MxAccessGalaxyBackend BuildBackend(IHistorianDataSource? historian, StaPump pump) =>
        new(
            new GalaxyRepository(new GalaxyRepositoryOptions { ConnectionString = "Server=.;Database=ZB;Integrated Security=True;" }),
            new MxAccessClient(pump, new MxProxyAdapter(), "attime-test"),
            historian);

    [Fact]
    public async Task Returns_disabled_error_when_no_historian_configured()
    {
        using var pump = new StaPump("Test.Sta");
        await pump.WaitForStartedAsync();
        using var backend = BuildBackend(null, pump);

        var resp = await backend.HistoryReadAtTimeAsync(new HistoryReadAtTimeRequest
        {
            TagReference = "T",
            TimestampsUtcUnixMs = new[] { 1L, 2L },
        }, CancellationToken.None);

        resp.Success.ShouldBeFalse();
        resp.Error.ShouldContain("Historian disabled");
    }

    [Fact]
    public async Task Empty_timestamp_list_short_circuits_to_success_with_no_values()
    {
        using var pump = new StaPump("Test.Sta");
        await pump.WaitForStartedAsync();
        var fake = new FakeHistorian();
        using var backend = BuildBackend(fake, pump);

        var resp = await backend.HistoryReadAtTimeAsync(new HistoryReadAtTimeRequest
        {
            TagReference = "T",
            TimestampsUtcUnixMs = Array.Empty<long>(),
        }, CancellationToken.None);

        resp.Success.ShouldBeTrue();
        resp.Values.ShouldBeEmpty();
        fake.Calls.ShouldBe(0); // no round-trip to SDK for empty timestamp list
    }

    [Fact]
    public async Task Timestamps_survive_Unix_ms_round_trip_to_DateTime()
    {
        using var pump = new StaPump("Test.Sta");
        await pump.WaitForStartedAsync();
        var t1 = new DateTime(2026, 4, 18, 10, 0, 0, DateTimeKind.Utc);
        var t2 = new DateTime(2026, 4, 18, 10, 5, 0, DateTimeKind.Utc);
        var fake = new FakeHistorian(
            new HistorianSample { Value = 100.0, Quality = 192, TimestampUtc = t1 },
            new HistorianSample { Value = 101.5, Quality = 192, TimestampUtc = t2 });
        using var backend = BuildBackend(fake, pump);

        var resp = await backend.HistoryReadAtTimeAsync(new HistoryReadAtTimeRequest
        {
            TagReference = "TankLevel",
            TimestampsUtcUnixMs = new[]
            {
                new DateTimeOffset(t1, TimeSpan.Zero).ToUnixTimeMilliseconds(),
                new DateTimeOffset(t2, TimeSpan.Zero).ToUnixTimeMilliseconds(),
            },
        }, CancellationToken.None);

        resp.Success.ShouldBeTrue();
        resp.Values.Length.ShouldBe(2);
        resp.Values[0].SourceTimestampUtcUnixMs.ShouldBe(new DateTimeOffset(t1, TimeSpan.Zero).ToUnixTimeMilliseconds());
        resp.Values[0].StatusCode.ShouldBe(0u); // Good (quality 192)
        MessagePackSerializer.Deserialize<double>(resp.Values[0].ValueBytes!).ShouldBe(100.0);

        fake.Calls.ShouldBe(1);
        fake.LastTimestamps.Length.ShouldBe(2);
        fake.LastTimestamps[0].ShouldBe(t1);
        fake.LastTimestamps[1].ShouldBe(t2);
    }

    [Fact]
    public async Task Missing_sample_maps_to_Bad_category()
    {
        using var pump = new StaPump("Test.Sta");
        await pump.WaitForStartedAsync();
        // Quality=0 means no sample at that timestamp per HistorianDataSource.ReadAtTimeAsync.
        var fake = new FakeHistorian(new HistorianSample
        {
            Value = null,
            Quality = 0,
            TimestampUtc = DateTime.UtcNow,
        });
        using var backend = BuildBackend(fake, pump);

        var resp = await backend.HistoryReadAtTimeAsync(new HistoryReadAtTimeRequest
        {
            TagReference = "T",
            TimestampsUtcUnixMs = new[] { 1L },
        }, CancellationToken.None);

        resp.Success.ShouldBeTrue();
        resp.Values.Length.ShouldBe(1);
        resp.Values[0].StatusCode.ShouldBe(0x80000000u); // Bad category
        resp.Values[0].ValueBytes.ShouldBeNull();
    }

    private sealed class FakeHistorian : IHistorianDataSource
    {
        private readonly HistorianSample[] _samples;
        public int Calls { get; private set; }
        public DateTime[] LastTimestamps { get; private set; } = Array.Empty<DateTime>();

        public FakeHistorian(params HistorianSample[] samples) => _samples = samples;

        public Task<List<HistorianSample>> ReadAtTimeAsync(string tag, DateTime[] ts, CancellationToken ct)
        {
            Calls++;
            LastTimestamps = ts;
            return Task.FromResult(new List<HistorianSample>(_samples));
        }

        public Task<List<HistorianSample>> ReadRawAsync(string tag, DateTime s, DateTime e, int max, CancellationToken ct)
            => Task.FromResult(new List<HistorianSample>());
        public Task<List<HistorianAggregateSample>> ReadAggregateAsync(string tag, DateTime s, DateTime e, double ms, string col, CancellationToken ct)
            => Task.FromResult(new List<HistorianAggregateSample>());
        public Task<List<HistorianEventDto>> ReadEventsAsync(string? src, DateTime s, DateTime e, int max, CancellationToken ct)
            => Task.FromResult(new List<HistorianEventDto>());
        public HistorianHealthSnapshot GetHealthSnapshot() => new();
        public void Dispose() { }
    }
}
