using System.CommandLine;
using ScadaLink.CLI.Commands;
using ScadaLink.Commons.Messages.Management;

namespace ScadaLink.CLI.Tests;

/// <summary>
/// Regression tests for CLI-013 — the command-tree wiring was untested. These tests
/// build every command group and assert the tree is well-formed (every leaf has an
/// action, no group is empty), and that every management command record the CLI sends
/// resolves via <see cref="ManagementCommandRegistry"/> (so command-name derivation
/// never throws at runtime).
/// </summary>
public class CommandTreeTests
{
    private static readonly Option<string> Url = new("--url") { Recursive = true };
    private static readonly Option<string> Username = new("--username") { Recursive = true };
    private static readonly Option<string> Password = new("--password") { Recursive = true };
    private static readonly Option<string> Format = CliOptions.CreateFormatOption();

    private static IEnumerable<Command> AllCommandGroups() => new[]
    {
        TemplateCommands.Build(Url, Format, Username, Password),
        InstanceCommands.Build(Url, Format, Username, Password),
        SiteCommands.Build(Url, Format, Username, Password),
        DeployCommands.Build(Url, Format, Username, Password),
        DataConnectionCommands.Build(Url, Format, Username, Password),
        ExternalSystemCommands.Build(Url, Format, Username, Password),
        NotificationCommands.Build(Url, Format, Username, Password),
        SecurityCommands.Build(Url, Format, Username, Password),
        AuditLogCommands.Build(Url, Format, Username, Password),
        HealthCommands.Build(Url, Format, Username, Password),
        DebugCommands.Build(Url, Format, Username, Password),
        SharedScriptCommands.Build(Url, Format, Username, Password),
        DbConnectionCommands.Build(Url, Format, Username, Password),
        ApiMethodCommands.Build(Url, Format, Username, Password),
    };

    private static IEnumerable<Command> LeafCommands(Command command)
    {
        if (command.Subcommands.Count == 0)
        {
            yield return command;
            yield break;
        }

        foreach (var sub in command.Subcommands)
            foreach (var leaf in LeafCommands(sub))
                yield return leaf;
    }

    [Fact]
    public void AllCommandGroups_Build_WithoutThrowing()
    {
        var groups = AllCommandGroups().ToList();
        Assert.Equal(14, groups.Count);
        Assert.All(groups, g => Assert.False(string.IsNullOrWhiteSpace(g.Name)));
    }

    [Fact]
    public void EveryLeafCommand_HasAnAction()
    {
        // A leaf command with no action is dead wiring — invoking it would do nothing.
        var leaves = AllCommandGroups().SelectMany(LeafCommands).ToList();

        Assert.NotEmpty(leaves);
        Assert.All(leaves, leaf =>
            Assert.True(leaf.Action != null, $"Leaf command '{leaf.Name}' has no action."));
    }

    [Fact]
    public void TemplateCompositionDelete_IsKeyedByIdOnly()
    {
        // CLI-015: the in-repo README documented `template composition delete` with
        // --template-id / --instance-name, but the implementation keys deletion by the
        // composition's own integer ID via a single --id option. Pin the real surface.
        var template = TemplateCommands.Build(Url, Format, Username, Password);
        var composition = template.Subcommands.Single(c => c.Name == "composition");
        var delete = composition.Subcommands.Single(c => c.Name == "delete");

        var optionNames = delete.Options.Select(o => o.Name).ToList();
        Assert.Contains("--id", optionNames);
        Assert.DoesNotContain("--template-id", optionNames);
        Assert.DoesNotContain("--instance-name", optionNames);
    }

    [Theory]
    [InlineData(typeof(GetInstanceCommand))]
    [InlineData(typeof(ListSitesCommand))]
    [InlineData(typeof(CreateTemplateCommand))]
    [InlineData(typeof(SetConnectionBindingsCommand))]
    [InlineData(typeof(SetInstanceOverridesCommand))]
    [InlineData(typeof(DebugSnapshotCommand))]
    [InlineData(typeof(MgmtDeployInstanceCommand))]
    [InlineData(typeof(QueryAuditLogCommand))]
    public void CommandPayloadTypes_ResolveViaRegistry(Type commandType)
    {
        // GetCommandName throws ArgumentException for an unregistered type — the CLI
        // calls it for every command it sends, so each must round-trip.
        var name = ManagementCommandRegistry.GetCommandName(commandType);
        Assert.False(string.IsNullOrWhiteSpace(name));
        Assert.Equal(commandType, ManagementCommandRegistry.Resolve(name));
    }
}
