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 OpcUaClientAlarmTests
{
    /// <summary>Verifies that MapSeverity buckets per OPC UA Part 9 guidance.</summary>
    /// <param name="opcSev">The OPC UA severity value (1–1000).</param>
    /// <param name="expected">The expected mapped alarm severity.</param>
    [Theory]
    [InlineData((ushort)1, AlarmSeverity.Low)]
    [InlineData((ushort)200, AlarmSeverity.Low)]
    [InlineData((ushort)201, AlarmSeverity.Medium)]
    [InlineData((ushort)500, AlarmSeverity.Medium)]
    [InlineData((ushort)501, AlarmSeverity.High)]
    [InlineData((ushort)800, AlarmSeverity.High)]
    [InlineData((ushort)801, AlarmSeverity.Critical)]
    [InlineData((ushort)1000, AlarmSeverity.Critical)]
    public void MapSeverity_buckets_per_OPC_UA_Part_9_guidance(ushort opcSev, AlarmSeverity expected)
    {
        OpcUaClientDriver.MapSeverity(opcSev).ShouldBe(expected);
    }

    /// <summary>Verifies that MapSeverity zero maps to Low.</summary>
    [Fact]
    public void MapSeverity_zero_maps_to_Low()
    {
        // 0 isn't in OPC UA's 1-1000 range but we handle it gracefully as Low.
        OpcUaClientDriver.MapSeverity(0).ShouldBe(AlarmSeverity.Low);
    }

    /// <summary>Verifies that SubscribeAlarmsAsync without initialize throws InvalidOperationException.</summary>
    [Fact]
    public async Task SubscribeAlarmsAsync_without_initialize_throws_InvalidOperationException()
    {
        using var drv = new OpcUaClientDriver(new OpcUaClientDriverOptions(), "opcua-alarm-uninit");
        await Should.ThrowAsync<InvalidOperationException>(async () =>
            await drv.SubscribeAlarmsAsync([], TestContext.Current.CancellationToken));
    }

    /// <summary>Verifies that UnsubscribeAlarmsAsync with unknown handle is noop.</summary>
    [Fact]
    public async Task UnsubscribeAlarmsAsync_with_unknown_handle_is_noop()
    {
        using var drv = new OpcUaClientDriver(new OpcUaClientDriverOptions(), "opcua-alarm-unknown");
        // Parallels the subscribe handle path — session-drop races shouldn't crash the caller.
        await drv.UnsubscribeAlarmsAsync(new FakeAlarmHandle(), TestContext.Current.CancellationToken);
    }

    /// <summary>Verifies that AcknowledgeAsync without initialize throws InvalidOperationException.</summary>
    [Fact]
    public async Task AcknowledgeAsync_without_initialize_throws_InvalidOperationException()
    {
        using var drv = new OpcUaClientDriver(new OpcUaClientDriverOptions(), "opcua-ack-uninit");
        await Should.ThrowAsync<InvalidOperationException>(async () =>
            await drv.AcknowledgeAsync(
                [new AlarmAcknowledgeRequest("ns=2;s=Src", "ns=2;s=Cond", "operator ack")],
                TestContext.Current.CancellationToken));
    }

    /// <summary>Verifies that AcknowledgeAsync with empty batch is noop even without init.</summary>
    [Fact]
    public async Task AcknowledgeAsync_with_empty_batch_is_noop_even_without_init()
    {
        // Empty batch short-circuits before touching the session, so it's safe pre-init. This
        // keeps batch-ack callers from needing to guard the list size themselves.
        using var drv = new OpcUaClientDriver(new OpcUaClientDriverOptions(), "opcua-ack-empty");
        await drv.AcknowledgeAsync([], TestContext.Current.CancellationToken);
    }

    private sealed class FakeAlarmHandle : IAlarmSubscriptionHandle
    {
        /// <summary>Gets the diagnostic identifier for this alarm handle.</summary>
        public string DiagnosticId => "fake-alarm";
    }
}
