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 SubscribeCommandTests
{
    /// <summary>Verifies that ExecuteAsync subscribes with the correct parameters.</summary>
    [Fact]
    public async Task Execute_SubscribesWithCorrectParameters()
    {
        var fakeService = new FakeOpcUaClientService();
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new SubscribeCommand(factory)
        {
            Url = "opc.tcp://localhost:4840",
            NodeId = "ns=2;s=TestVar",
            Interval = 500
        };

        using var console = TestConsoleHelper.CreateConsole();

        // The subscribe command waits for cancellation. We need to cancel it.
        // Use the console's cancellation to trigger stop.
        var task = Task.Run(async () => { await command.ExecuteAsync(console); });

        // Wait until the command has actually subscribed (deterministic — no fixed-sleep race), then cancel
        await fakeService.SubscribeInvoked.WaitAsync(TimeSpan.FromSeconds(10));
        console.RequestCancellation();
        await task;

        fakeService.SubscribeCalls.Count.ShouldBe(1);
        fakeService.SubscribeCalls[0].IntervalMs.ShouldBe(500);
        fakeService.SubscribeCalls[0].NodeId.Identifier.ShouldBe("TestVar");
    }

    /// <summary>Verifies that ExecuteAsync unsubscribes when cancellation is requested.</summary>
    [Fact]
    public async Task Execute_UnsubscribesOnCancellation()
    {
        var fakeService = new FakeOpcUaClientService();
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new SubscribeCommand(factory)
        {
            Url = "opc.tcp://localhost:4840",
            NodeId = "ns=2;s=TestVar"
        };

        using var console = TestConsoleHelper.CreateConsole();

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

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

        fakeService.UnsubscribeCalls.Count.ShouldBe(1);
    }

    /// <summary>Verifies that ExecuteAsync disconnects and disposes in a finally block.</summary>
    [Fact]
    public async Task Execute_DisconnectsInFinally()
    {
        var fakeService = new FakeOpcUaClientService();
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new SubscribeCommand(factory)
        {
            Url = "opc.tcp://localhost:4840",
            NodeId = "ns=2;s=TestVar"
        };

        using var console = TestConsoleHelper.CreateConsole();

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

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

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

    /// <summary>Verifies that ExecuteAsync prints the correct subscription message.</summary>
    [Fact]
    public async Task Execute_PrintsSubscriptionMessage()
    {
        var fakeService = new FakeOpcUaClientService();
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new SubscribeCommand(factory)
        {
            Url = "opc.tcp://localhost:4840",
            NodeId = "ns=2;s=TestVar",
            Interval = 2000
        };

        using var console = TestConsoleHelper.CreateConsole();

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

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

        var output = TestConsoleHelper.GetOutput(console);
        // CLI now prints aggregate form "Subscribed to {count}/{total} nodes (interval: ...)" rather than
        // the single-node form the original test asserted — the command supports multi-node now.
        output.ShouldContain("Subscribed to 1/1 nodes (interval: 2000ms)");
    }
}