using Opc.Ua;
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 ReadCommandTests
{
    /// <summary>Verifies that execute prints the read value.</summary>
    [Fact]
    public async Task Execute_PrintsReadValue()
    {
        var sourceTime = new DateTime(2025, 6, 15, 10, 30, 0, DateTimeKind.Utc);
        var serverTime = new DateTime(2025, 6, 15, 10, 30, 1, DateTimeKind.Utc);
        var fakeService = new FakeOpcUaClientService
        {
            ReadValueResult = new DataValue(
                new Variant("Hello"),
                StatusCodes.Good,
                sourceTime,
                serverTime)
        };
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new ReadCommand(factory)
        {
            Url = "opc.tcp://localhost:4840",
            NodeId = "ns=2;s=TestNode"
        };

        using var console = TestConsoleHelper.CreateConsole();
        await command.ExecuteAsync(console);

        var output = TestConsoleHelper.GetOutput(console);
        output.ShouldContain("Node:        ns=2;s=TestNode");
        output.ShouldContain("Value:       Hello");
        output.ShouldContain("Status:");
        output.ShouldContain("Source Time:");
        output.ShouldContain("Server Time:");
    }

    /// <summary>Verifies that execute calls read value with correct node ID.</summary>
    [Fact]
    public async Task Execute_CallsReadValueWithCorrectNodeId()
    {
        var fakeService = new FakeOpcUaClientService();
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new ReadCommand(factory)
        {
            Url = "opc.tcp://localhost:4840",
            NodeId = "ns=2;s=MyVariable"
        };

        using var console = TestConsoleHelper.CreateConsole();
        await command.ExecuteAsync(console);

        fakeService.ReadNodeIds.Count.ShouldBe(1);
        fakeService.ReadNodeIds[0].Identifier.ShouldBe("MyVariable");
    }

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

        using var console = TestConsoleHelper.CreateConsole();
        await command.ExecuteAsync(console);

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

    /// <summary>Verifies that execute disconnects even on read error.</summary>
    [Fact]
    public async Task Execute_DisconnectsEvenOnReadError()
    {
        var fakeService = new FakeOpcUaClientService
        {
            ReadException = new InvalidOperationException("Read failed")
        };
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new ReadCommand(factory)
        {
            Url = "opc.tcp://localhost:4840",
            NodeId = "ns=2;s=TestNode"
        };

        using var console = TestConsoleHelper.CreateConsole();
        await Should.ThrowAsync<InvalidOperationException>(async () => await command.ExecuteAsync(console));

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