using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions.Tests.Historian;

/// <summary>
///     The default no-op historian source. Every read returns an empty result with a null
///     continuation point; <see cref="NullHistorianDataSource.GetHealthSnapshot"/> reports a fully
///     disabled shape; and <see cref="NullHistorianDataSource.Instance"/> is the shared singleton.
/// </summary>
public sealed class NullHistorianDataSourceTests
{
    private static CancellationToken Ct => TestContext.Current.CancellationToken;

    private static readonly NullHistorianDataSource Source = NullHistorianDataSource.Instance;

    /// <summary><see cref="NullHistorianDataSource.Instance"/> is a single shared instance.</summary>
    [Fact]
    public void Instance_is_a_singleton()
    {
        NullHistorianDataSource.Instance.ShouldBeSameAs(NullHistorianDataSource.Instance);
    }

    /// <summary>ReadRawAsync returns no samples and a null continuation point.</summary>
    [Fact]
    public async Task ReadRawAsync_returns_empty()
    {
        var result = await Source.ReadRawAsync(
            "WW.Tag", DateTime.UtcNow.AddHours(-1), DateTime.UtcNow, maxValuesPerNode: 100, Ct);

        result.Samples.ShouldBeEmpty();
        result.ContinuationPoint.ShouldBeNull();
    }

    /// <summary>ReadProcessedAsync returns no samples and a null continuation point.</summary>
    [Fact]
    public async Task ReadProcessedAsync_returns_empty()
    {
        var result = await Source.ReadProcessedAsync(
            "WW.Tag", DateTime.UtcNow.AddHours(-1), DateTime.UtcNow,
            TimeSpan.FromSeconds(10), HistoryAggregateType.Average, Ct);

        result.Samples.ShouldBeEmpty();
        result.ContinuationPoint.ShouldBeNull();
    }

    /// <summary>ReadAtTimeAsync returns no samples and a null continuation point.</summary>
    [Fact]
    public async Task ReadAtTimeAsync_returns_empty()
    {
        var result = await Source.ReadAtTimeAsync(
            "WW.Tag", new[] { DateTime.UtcNow }, Ct);

        result.Samples.ShouldBeEmpty();
        result.ContinuationPoint.ShouldBeNull();
    }

    /// <summary>ReadEventsAsync returns no events and a null continuation point.</summary>
    [Fact]
    public async Task ReadEventsAsync_returns_empty()
    {
        var result = await Source.ReadEventsAsync(
            sourceName: null, DateTime.UtcNow.AddHours(-1), DateTime.UtcNow, maxEvents: 0, Ct);

        result.Events.ShouldBeEmpty();
        result.ContinuationPoint.ShouldBeNull();
    }

    /// <summary>GetHealthSnapshot reports a fully-disabled (zeroed, disconnected, no-node) shape.</summary>
    [Fact]
    public void GetHealthSnapshot_is_disabled()
    {
        var snapshot = Source.GetHealthSnapshot();

        snapshot.TotalQueries.ShouldBe(0);
        snapshot.TotalSuccesses.ShouldBe(0);
        snapshot.TotalFailures.ShouldBe(0);
        snapshot.ConsecutiveFailures.ShouldBe(0);
        snapshot.LastSuccessTime.ShouldBeNull();
        snapshot.LastFailureTime.ShouldBeNull();
        snapshot.LastError.ShouldBeNull();
        snapshot.ProcessConnectionOpen.ShouldBeFalse();
        snapshot.EventConnectionOpen.ShouldBeFalse();
        snapshot.ActiveProcessNode.ShouldBeNull();
        snapshot.ActiveEventNode.ShouldBeNull();
        snapshot.Nodes.ShouldBeEmpty();
    }

    /// <summary>Dispose is a safe no-op (idempotent).</summary>
    [Fact]
    public void Dispose_is_a_noop()
    {
        Should.NotThrow(() =>
        {
            Source.Dispose();
            Source.Dispose();
        });
    }
}
