using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Client.CLI.Commands;
using ZB.MOM.WW.OtOpcUa.Client.CLI.Tests.Fakes;

namespace ZB.MOM.WW.OtOpcUa.Client.CLI.Tests;

public class AlarmsCommandTests
{
    /// <summary>Verifies that Execute subscribes to alarms.</summary>
    [Fact]
    public async Task Execute_SubscribesToAlarms()
    {
        var fakeService = new FakeOpcUaClientService();
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new AlarmsCommand(factory)
        {
            Url = "opc.tcp://localhost:4840",
            Interval = 2000
        };

        using var console = TestConsoleHelper.CreateConsole();

        var task = Task.Run(async () => { await command.ExecuteAsync(console); });

        await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10));
        console.RequestCancellation();
        await task;

        fakeService.SubscribeAlarmsCalls.Count.ShouldBe(1);
        fakeService.SubscribeAlarmsCalls[0].IntervalMs.ShouldBe(2000);
        fakeService.SubscribeAlarmsCalls[0].SourceNodeId.ShouldBeNull();
    }

    /// <summary>Verifies that Execute with node passes source node ID.</summary>
    [Fact]
    public async Task Execute_WithNode_PassesSourceNodeId()
    {
        var fakeService = new FakeOpcUaClientService();
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new AlarmsCommand(factory)
        {
            Url = "opc.tcp://localhost:4840",
            NodeId = "ns=2;s=AlarmSource"
        };

        using var console = TestConsoleHelper.CreateConsole();

        var task = Task.Run(async () => { await command.ExecuteAsync(console); });

        await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10));
        console.RequestCancellation();
        await task;

        fakeService.SubscribeAlarmsCalls.Count.ShouldBe(1);
        fakeService.SubscribeAlarmsCalls[0].SourceNodeId.ShouldNotBeNull();
        fakeService.SubscribeAlarmsCalls[0].SourceNodeId!.Identifier.ShouldBe("AlarmSource");
    }

    /// <summary>Verifies that Execute with refresh requests condition refresh.</summary>
    [Fact]
    public async Task Execute_WithRefresh_RequestsConditionRefresh()
    {
        var fakeService = new FakeOpcUaClientService();
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new AlarmsCommand(factory)
        {
            Url = "opc.tcp://localhost:4840",
            Refresh = true
        };

        using var console = TestConsoleHelper.CreateConsole();

        var task = Task.Run(async () => { await command.ExecuteAsync(console); });

        await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10));
        console.RequestCancellation();
        await task;

        fakeService.RequestConditionRefreshCalled.ShouldBeTrue();
        var output = TestConsoleHelper.GetOutput(console);
        output.ShouldContain("Condition refresh requested.");
    }

    /// <summary>Verifies that refresh failure prints error.</summary>
    [Fact]
    public async Task Execute_RefreshFailure_PrintsError()
    {
        var fakeService = new FakeOpcUaClientService
        {
            ConditionRefreshException = new NotSupportedException("Not supported")
        };
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new AlarmsCommand(factory)
        {
            Url = "opc.tcp://localhost:4840",
            Refresh = true
        };

        using var console = TestConsoleHelper.CreateConsole();

        var task = Task.Run(async () => { await command.ExecuteAsync(console); });

        await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10));
        console.RequestCancellation();
        await task;

        var output = TestConsoleHelper.GetOutput(console);
        output.ShouldContain("Condition refresh not supported:");
    }

    /// <summary>Verifies that Execute unsubscribes on cancellation.</summary>
    [Fact]
    public async Task Execute_UnsubscribesOnCancellation()
    {
        var fakeService = new FakeOpcUaClientService();
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new AlarmsCommand(factory)
        {
            Url = "opc.tcp://localhost:4840"
        };

        using var console = TestConsoleHelper.CreateConsole();

        var task = Task.Run(async () => { await command.ExecuteAsync(console); });

        await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10));
        console.RequestCancellation();
        await task;

        fakeService.UnsubscribeAlarmsCalled.ShouldBeTrue();
    }

    /// <summary>Verifies that Execute disconnects in finally block.</summary>
    [Fact]
    public async Task Execute_DisconnectsInFinally()
    {
        var fakeService = new FakeOpcUaClientService();
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new AlarmsCommand(factory)
        {
            Url = "opc.tcp://localhost:4840"
        };

        using var console = TestConsoleHelper.CreateConsole();

        var task = Task.Run(async () => { await command.ExecuteAsync(console); });

        await fakeService.SubscribeAlarmsInvoked.WaitAsync(TimeSpan.FromSeconds(10));
        console.RequestCancellation();
        await task;

        fakeService.DisconnectCalled.ShouldBeTrue();
        fakeService.DisposeCalled.ShouldBeTrue();
    }
}