using CliFx.Exceptions;
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;

/// <summary>
/// Regression tests for Client.CLI-006: predictable operator-input errors must surface as
/// CliFx CommandException with a clean message, not raw FormatException/ArgumentException
/// with a stack trace.
/// </summary>
public class InputValidationErrorsTests
{
    /// <summary>Verifies that HistoryReadCommand with invalid start time throws CommandException.</summary>
    [Fact]
    public async Task HistoryReadCommand_InvalidStartTime_ThrowsCommandException()
    {
        var fakeService = new FakeOpcUaClientService();
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new HistoryReadCommand(factory)
        {
            Url = "opc.tcp://localhost:4840",
            NodeId = "ns=2;s=N",
            StartTime = "not-a-date"
        };

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

    /// <summary>Verifies that HistoryReadCommand with invalid end time throws CommandException.</summary>
    [Fact]
    public async Task HistoryReadCommand_InvalidEndTime_ThrowsCommandException()
    {
        var fakeService = new FakeOpcUaClientService();
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new HistoryReadCommand(factory)
        {
            Url = "opc.tcp://localhost:4840",
            NodeId = "ns=2;s=N",
            EndTime = "garbage"
        };

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

    /// <summary>Verifies that HistoryReadCommand with invalid aggregate throws CommandException.</summary>
    [Fact]
    public async Task HistoryReadCommand_InvalidAggregate_ThrowsCommandException()
    {
        var fakeService = new FakeOpcUaClientService();
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new HistoryReadCommand(factory)
        {
            Url = "opc.tcp://localhost:4840",
            NodeId = "ns=2;s=N",
            Aggregate = "NotARealAggregate"
        };

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

    /// <summary>Verifies that ReadCommand with invalid node ID throws CommandException.</summary>
    [Fact]
    public async Task ReadCommand_InvalidNodeId_ThrowsCommandException()
    {
        var fakeService = new FakeOpcUaClientService();
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new ReadCommand(factory)
        {
            Url = "opc.tcp://localhost:4840",
            NodeId = "not-a-node-id"
        };

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

    /// <summary>Verifies that SubscribeCommand with invalid node ID throws CommandException.</summary>
    [Fact]
    public async Task SubscribeCommand_InvalidNodeId_ThrowsCommandException()
    {
        var fakeService = new FakeOpcUaClientService();
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new SubscribeCommand(factory)
        {
            Url = "opc.tcp://localhost:4840",
            NodeId = "not-a-node-id"
        };

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