using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Client.CLI.Commands;
using ZB.MOM.WW.OtOpcUa.Client.CLI.Tests.Fakes;
using BrowseResult = ZB.MOM.WW.OtOpcUa.Client.Shared.Models.BrowseResult;

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

public class BrowseCommandTests
{
    /// <summary>Verifies that Execute prints browse results correctly.</summary>
    [Fact]
    public async Task Execute_PrintsBrowseResults()
    {
        var fakeService = new FakeOpcUaClientService
        {
            BrowseResults = new List<BrowseResult>
            {
                new("ns=2;s=Obj1", "Object1", "Object", true),
                new("ns=2;s=Var1", "Variable1", "Variable", false),
                new("ns=2;s=Meth1", "Method1", "Method", false)
            }
        };
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new BrowseCommand(factory)
        {
            Url = "opc.tcp://localhost:4840"
        };

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

        var output = TestConsoleHelper.GetOutput(console);
        output.ShouldContain("[Object] Object1 (NodeId: ns=2;s=Obj1)");
        output.ShouldContain("[Variable] Variable1 (NodeId: ns=2;s=Var1)");
        output.ShouldContain("[Method] Method1 (NodeId: ns=2;s=Meth1)");
    }

    /// <summary>Verifies that Execute browses from the specified node ID.</summary>
    [Fact]
    public async Task Execute_BrowsesFromSpecifiedNode()
    {
        var fakeService = new FakeOpcUaClientService
        {
            BrowseResults = new List<BrowseResult>()
        };
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new BrowseCommand(factory)
        {
            Url = "opc.tcp://localhost:4840",
            NodeId = "ns=2;s=StartNode"
        };

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

        fakeService.BrowseNodeIds.Count.ShouldBe(1);
        fakeService.BrowseNodeIds[0].ShouldNotBeNull();
        fakeService.BrowseNodeIds[0]!.Identifier.ShouldBe("StartNode");
    }

    /// <summary>Verifies that Execute browses from null node when not specified.</summary>
    [Fact]
    public async Task Execute_DefaultBrowsesFromNull()
    {
        var fakeService = new FakeOpcUaClientService
        {
            BrowseResults = new List<BrowseResult>()
        };
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new BrowseCommand(factory)
        {
            Url = "opc.tcp://localhost:4840"
        };

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

        fakeService.BrowseNodeIds.Count.ShouldBe(1);
        fakeService.BrowseNodeIds[0].ShouldBeNull();
    }

    /// <summary>Verifies that Execute browses only a single level when not recursive.</summary>
    [Fact]
    public async Task Execute_NonRecursive_BrowsesSingleLevel()
    {
        var fakeService = new FakeOpcUaClientService
        {
            BrowseResults = new List<BrowseResult>
            {
                new("ns=2;s=Child", "Child", "Object", true)
            }
        };
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new BrowseCommand(factory)
        {
            Url = "opc.tcp://localhost:4840",
            Depth = 5 // Should be ignored without recursive flag
        };

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

        // Only the root level browse should happen, not child
        fakeService.BrowseNodeIds.Count.ShouldBe(1);
    }

    /// <summary>Verifies that Execute browses child nodes when recursive flag is set.</summary>
    [Fact]
    public async Task Execute_Recursive_BrowsesChildren()
    {
        var fakeService = new FakeOpcUaClientService();
        // Override browse to return children only on first call
        // We can't easily do this with the simple fake, but the default returns results with HasChildren=true
        // which will trigger child browse with recursive=true, depth=2
        var factory = new FakeOpcUaClientServiceFactory(fakeService);
        var command = new BrowseCommand(factory)
        {
            Url = "opc.tcp://localhost:4840",
            Recursive = true,
            Depth = 2
        };

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

        // Root browse + child browse (for Node1 which HasChildren=true)
        fakeService.BrowseNodeIds.Count.ShouldBeGreaterThan(1);
    }

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

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

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