using CliFx.Exceptions;
using Opc.Ua;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Client.CLI.Commands;
using ZB.MOM.WW.OtOpcUa.Client.CLI.Tests.Fakes;
using ZB.MOM.WW.OtOpcUa.Client.Shared.Models;

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

public class ShelveCommandTests
{
    /// <summary>Verifies that OneShot shelving produces the correct kind and zero duration.</summary>
    [Fact]
    public async Task Execute_OneShotKind_PassesOneShotToService()
    {
        var fakeService = new FakeOpcUaClientService { ShelveAlarmResult = StatusCodes.Good };
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new ShelveCommand(factory)
        {
            Url = "opc.tcp://localhost:4840",
            NodeId = "ns=2;s=MyAlarm",
            Kind = "OneShot",
            DurationSeconds = 0
        };

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

        fakeService.ShelveAlarmCalls.Count.ShouldBe(1);
        fakeService.ShelveAlarmCalls[0].Kind.ShouldBe(ShelveKind.OneShot);
        fakeService.ShelveAlarmCalls[0].DurationSeconds.ShouldBe(0);
        fakeService.ShelveAlarmCalls[0].ConditionNodeId.ShouldBe("ns=2;s=MyAlarm");
        var output = TestConsoleHelper.GetOutput(console);
        output.ShouldContain("successful");
    }

    /// <summary>Verifies that Timed shelving passes the correct duration to the service.</summary>
    [Fact]
    public async Task Execute_TimedKind_PassesDurationToService()
    {
        var fakeService = new FakeOpcUaClientService { ShelveAlarmResult = StatusCodes.Good };
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new ShelveCommand(factory)
        {
            Url = "opc.tcp://localhost:4840",
            NodeId = "ns=2;s=MyAlarm",
            Kind = "Timed",
            DurationSeconds = 300
        };

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

        fakeService.ShelveAlarmCalls.Count.ShouldBe(1);
        fakeService.ShelveAlarmCalls[0].Kind.ShouldBe(ShelveKind.Timed);
        fakeService.ShelveAlarmCalls[0].DurationSeconds.ShouldBe(300);
    }

    /// <summary>Verifies that Unshelve produces the Unshelve kind.</summary>
    [Fact]
    public async Task Execute_UnshelveKind_PassesUnshelveToService()
    {
        var fakeService = new FakeOpcUaClientService { ShelveAlarmResult = StatusCodes.Good };
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new ShelveCommand(factory)
        {
            Url = "opc.tcp://localhost:4840",
            NodeId = "ns=2;s=MyAlarm",
            Kind = "Unshelve",
            DurationSeconds = 0
        };

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

        fakeService.ShelveAlarmCalls.Count.ShouldBe(1);
        fakeService.ShelveAlarmCalls[0].Kind.ShouldBe(ShelveKind.Unshelve);
    }

    /// <summary>Verifies that kind parsing is case-insensitive (e.g. "oneshot" works).</summary>
    [Fact]
    public async Task Execute_KindCaseInsensitive_ParsesCorrectly()
    {
        var fakeService = new FakeOpcUaClientService { ShelveAlarmResult = StatusCodes.Good };
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new ShelveCommand(factory)
        {
            Url = "opc.tcp://localhost:4840",
            NodeId = "ns=2;s=MyAlarm",
            Kind = "oneshot",
            DurationSeconds = 0
        };

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

        fakeService.ShelveAlarmCalls.Count.ShouldBe(1);
        fakeService.ShelveAlarmCalls[0].Kind.ShouldBe(ShelveKind.OneShot);
    }

    /// <summary>Verifies that an invalid Kind value throws CommandException.</summary>
    [Fact]
    public async Task Execute_InvalidKind_ThrowsCommandException()
    {
        var fakeService = new FakeOpcUaClientService();
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new ShelveCommand(factory)
        {
            Url = "opc.tcp://localhost:4840",
            NodeId = "ns=2;s=MyAlarm",
            Kind = "SuperShelve",
            DurationSeconds = 0
        };

        using var console = TestConsoleHelper.CreateConsole();
        var ex = await Should.ThrowAsync<CommandException>(async () => await command.ExecuteAsync(console));
        ex.Message.ShouldContain("kind", Case.Insensitive);
    }

    /// <summary>Verifies that Timed with zero duration throws CommandException (missing --duration).</summary>
    [Fact]
    public async Task Execute_TimedWithZeroDuration_ThrowsCommandException()
    {
        var fakeService = new FakeOpcUaClientService();
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new ShelveCommand(factory)
        {
            Url = "opc.tcp://localhost:4840",
            NodeId = "ns=2;s=MyAlarm",
            Kind = "Timed",
            DurationSeconds = 0
        };

        using var console = TestConsoleHelper.CreateConsole();
        var ex = await Should.ThrowAsync<CommandException>(async () => await command.ExecuteAsync(console));
        ex.Message.ShouldContain("duration", Case.Insensitive);
    }

    /// <summary>Verifies that a bad status code from the service prints a failure message.</summary>
    [Fact]
    public async Task Execute_BadStatus_PrintsFailureMessage()
    {
        var fakeService = new FakeOpcUaClientService { ShelveAlarmResult = StatusCodes.BadNotSupported };
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new ShelveCommand(factory)
        {
            Url = "opc.tcp://localhost:4840",
            NodeId = "ns=2;s=MyAlarm",
            Kind = "OneShot",
            DurationSeconds = 0
        };

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

        var output = TestConsoleHelper.GetOutput(console);
        output.ShouldContain("failed");
    }

    /// <summary>Verifies that the command disconnects in the finally block.</summary>
    [Fact]
    public async Task Execute_DisconnectsInFinally()
    {
        var fakeService = new FakeOpcUaClientService();
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new ShelveCommand(factory)
        {
            Url = "opc.tcp://localhost:4840",
            NodeId = "ns=2;s=MyAlarm",
            Kind = "OneShot",
            DurationSeconds = 0
        };

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

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