using System;
using System.Collections.Generic;
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 HistoryReadProcessedTests
{
    [Fact]
    public async Task ReturnsDisabledError_When_NoHistorianConfigured()
    {
        using var pump = new StaPump("Test.Sta");
        await pump.WaitForStartedAsync();
        var mx = new MxAccessClient(pump, new MxProxyAdapter(), "processed-test");
        using var backend = new MxAccessGalaxyBackend(
            new GalaxyRepository(new GalaxyRepositoryOptions { ConnectionString = "Server=.;Database=ZB;Integrated Security=True;" }),
            mx,
            historian: null);

        var resp = await backend.HistoryReadProcessedAsync(new HistoryReadProcessedRequest
        {
            TagReference = "T",
            StartUtcUnixMs = 0,
            EndUtcUnixMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
            IntervalMs = 1000,
            AggregateColumn = "Average",
        }, CancellationToken.None);

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

    [Fact]
    public async Task Rejects_NonPositiveInterval()
    {
        using var pump = new StaPump("Test.Sta");
        await pump.WaitForStartedAsync();
        var mx = new MxAccessClient(pump, new MxProxyAdapter(), "processed-test");
        var fake = new FakeHistorianDataSource();
        using var backend = new MxAccessGalaxyBackend(
            new GalaxyRepository(new GalaxyRepositoryOptions { ConnectionString = "Server=.;Database=ZB;Integrated Security=True;" }),
            mx,
            fake);

        var resp = await backend.HistoryReadProcessedAsync(new HistoryReadProcessedRequest
        {
            TagReference = "T",
            IntervalMs = 0,
            AggregateColumn = "Average",
        }, CancellationToken.None);

        resp.Success.ShouldBeFalse();
        resp.Error.ShouldContain("IntervalMs");
    }

    [Fact]
    public async Task Maps_AggregateSample_With_Value_To_Good()
    {
        using var pump = new StaPump("Test.Sta");
        await pump.WaitForStartedAsync();
        var mx = new MxAccessClient(pump, new MxProxyAdapter(), "processed-test");
        var fake = new FakeHistorianDataSource(new HistorianAggregateSample
        {
            Value = 12.34,
            TimestampUtc = new DateTime(2026, 4, 18, 10, 0, 0, DateTimeKind.Utc),
        });
        using var backend = new MxAccessGalaxyBackend(
            new GalaxyRepository(new GalaxyRepositoryOptions { ConnectionString = "Server=.;Database=ZB;Integrated Security=True;" }),
            mx,
            fake);

        var resp = await backend.HistoryReadProcessedAsync(new HistoryReadProcessedRequest
        {
            TagReference = "T",
            StartUtcUnixMs = 0,
            EndUtcUnixMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
            IntervalMs = 60_000,
            AggregateColumn = "Average",
        }, CancellationToken.None);

        resp.Success.ShouldBeTrue();
        resp.Values.Length.ShouldBe(1);
        resp.Values[0].StatusCode.ShouldBe(0u); // Good
        resp.Values[0].ValueBytes.ShouldNotBeNull();
        MessagePackSerializer.Deserialize<double>(resp.Values[0].ValueBytes!).ShouldBe(12.34);
        fake.LastAggregateColumn.ShouldBe("Average");
        fake.LastIntervalMs.ShouldBe(60_000d);
    }

    [Fact]
    public async Task Maps_Null_Bucket_To_BadNoData()
    {
        using var pump = new StaPump("Test.Sta");
        await pump.WaitForStartedAsync();
        var mx = new MxAccessClient(pump, new MxProxyAdapter(), "processed-test");
        var fake = new FakeHistorianDataSource(new HistorianAggregateSample
        {
            Value = null,
            TimestampUtc = DateTime.UtcNow,
        });
        using var backend = new MxAccessGalaxyBackend(
            new GalaxyRepository(new GalaxyRepositoryOptions { ConnectionString = "Server=.;Database=ZB;Integrated Security=True;" }),
            mx,
            fake);

        var resp = await backend.HistoryReadProcessedAsync(new HistoryReadProcessedRequest
        {
            TagReference = "T",
            IntervalMs = 1000,
            AggregateColumn = "Minimum",
        }, CancellationToken.None);

        resp.Success.ShouldBeTrue();
        resp.Values.Length.ShouldBe(1);
        resp.Values[0].StatusCode.ShouldBe(0x800E0000u); // BadNoData
        resp.Values[0].ValueBytes.ShouldBeNull();
    }

    private sealed class FakeHistorianDataSource : IHistorianDataSource
    {
        private readonly HistorianAggregateSample[] _samples;
        public string? LastAggregateColumn { get; private set; }
        public double LastIntervalMs { get; private set; }

        public FakeHistorianDataSource(params HistorianAggregateSample[] samples) => _samples = 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 intervalMs, string col, CancellationToken ct)
        {
            LastAggregateColumn = col;
            LastIntervalMs = intervalMs;
            return Task.FromResult(new List<HistorianAggregateSample>(_samples));
        }

        public Task<List<HistorianSample>> ReadAtTimeAsync(string tag, DateTime[] ts, CancellationToken ct)
            => Task.FromResult(new List<HistorianSample>());

        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() { }
    }
}
