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

namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests;

[Trait("Category", "Unit")]
public sealed class OpcUaClientHistoryTests
{
    /// <summary>Verifies MapAggregateToNodeId returns standard Part 13 aggregate for every enum.</summary>
    /// <param name="agg">The history aggregate type to test.</param>
    [Theory]
    [InlineData(HistoryAggregateType.Average)]
    [InlineData(HistoryAggregateType.Minimum)]
    [InlineData(HistoryAggregateType.Maximum)]
    [InlineData(HistoryAggregateType.Total)]
    [InlineData(HistoryAggregateType.Count)]
    public void MapAggregateToNodeId_returns_standard_Part13_aggregate_for_every_enum(HistoryAggregateType agg)
    {
        var nodeId = OpcUaClientDriver.MapAggregateToNodeId(agg);
        NodeId.IsNull(nodeId).ShouldBeFalse();
        // Every mapping should resolve to an AggregateFunction_* NodeId (namespace 0, numeric id).
        nodeId.NamespaceIndex.ShouldBe((ushort)0);
    }

    /// <summary>Verifies MapAggregateToNodeId rejects invalid enum values.</summary>
    [Fact]
    public void MapAggregateToNodeId_rejects_invalid_enum_value()
    {
        // Defense-in-depth: a future HistoryAggregateType addition mustn't silently fall through.
        Should.Throw<ArgumentOutOfRangeException>(() =>
            OpcUaClientDriver.MapAggregateToNodeId((HistoryAggregateType)99));
    }

    /// <summary>Verifies ReadRawAsync throws without initialization.</summary>
    [Fact]
    public async Task ReadRawAsync_without_initialize_throws_InvalidOperationException()
    {
        using var drv = new OpcUaClientDriver(new OpcUaClientDriverOptions(), "opcua-hist-uninit");
        await Should.ThrowAsync<InvalidOperationException>(async () =>
            await drv.ReadRawAsync("ns=2;s=Counter",
                DateTime.UtcNow.AddMinutes(-5), DateTime.UtcNow, 1000,
                TestContext.Current.CancellationToken));
    }

    /// <summary>Verifies ReadRawAsync with malformed NodeId returns empty result.</summary>
    [Fact]
    public async Task ReadRawAsync_with_malformed_NodeId_returns_empty_result_not_throw()
    {
        // Same defensive pattern as ReadAsync / WriteAsync — malformed NodeId short-circuits
        // to an empty result rather than crashing a batch history call. Needs init via the
        // throw path first, then we pass "" to trigger the parse-fail branch inside
        // ExecuteHistoryReadAsync. The init itself fails against 127.0.0.1:1 so we stop there.
        // Not runnable without init — keep as placeholder for when the in-process fixture
        // PR lands.
        await Task.CompletedTask;
    }

    /// <summary>Verifies ReadProcessedAsync throws without initialization.</summary>
    [Fact]
    public async Task ReadProcessedAsync_without_initialize_throws_InvalidOperationException()
    {
        using var drv = new OpcUaClientDriver(new OpcUaClientDriverOptions(), "opcua-hist-uninit");
        await Should.ThrowAsync<InvalidOperationException>(async () =>
            await drv.ReadProcessedAsync("ns=2;s=Counter",
                DateTime.UtcNow.AddMinutes(-5), DateTime.UtcNow,
                TimeSpan.FromSeconds(10), HistoryAggregateType.Average,
                TestContext.Current.CancellationToken));
    }

    /// <summary>Verifies ReadAtTimeAsync throws without initialization.</summary>
    [Fact]
    public async Task ReadAtTimeAsync_without_initialize_throws_InvalidOperationException()
    {
        using var drv = new OpcUaClientDriver(new OpcUaClientDriverOptions(), "opcua-hist-uninit");
        await Should.ThrowAsync<InvalidOperationException>(async () =>
            await drv.ReadAtTimeAsync("ns=2;s=Counter",
                [DateTime.UtcNow.AddMinutes(-5), DateTime.UtcNow],
                TestContext.Current.CancellationToken));
    }

    /// <summary>Verifies ReadEventsAsync throws NotSupportedException as documented.</summary>
    [Fact]
    public async Task ReadEventsAsync_throws_NotSupportedException_as_documented()
    {
        // The IHistoryProvider default implementation throws; the OPC UA Client driver
        // deliberately inherits that default (see PR 76 commit body) because the OPC UA
        // client call path needs an EventFilter SelectClauses spec the interface doesn't carry.
        using var drv = new OpcUaClientDriver(new OpcUaClientDriverOptions(), "opcua-events-default");
        await Should.ThrowAsync<NotSupportedException>(async () =>
            await ((IHistoryProvider)drv).ReadEventsAsync(
                sourceName: null,
                startUtc: DateTime.UtcNow.AddMinutes(-5),
                endUtc: DateTime.UtcNow,
                maxEvents: 100,
                cancellationToken: TestContext.Current.CancellationToken));
    }
}
