using System.Reflection;
using System.Text.Json;
using Sbroenne.ExcelMcp.CLI.Commands;
using Sbroenne.ExcelMcp.Generated;
using Spectre.Console.Cli;
using Xunit;

namespace Sbroenne.ExcelMcp.CLI.Tests.Unit;

[Trait("Layer", "CLI")]
[Trait("Category", "Unit")]
[Trait("Feature", "ActionValidation")]
[Trait("Speed", "Fast")]
[Collection("ConsoleOutput")]
public sealed class ActionValidatorTests
{
    public static IEnumerable<object[]> ActionEnumTypes =>
    [
        [typeof(RangeAction), typeof(ServiceRegistry.Range)],
        [typeof(RangeEditAction), typeof(ServiceRegistry.RangeEdit)],
        [typeof(RangeFormatAction), typeof(ServiceRegistry.RangeFormat)],
        [typeof(RangeLinkAction), typeof(ServiceRegistry.RangeLink)]
    ];

    private static readonly string[] ExpectedCommands =
    [
        "session",
        "sheet",
        "worksheetstyle",
        "range",
        "rangeedit",
        "rangeformat",
        "rangelink",
        "table",
        "tablecolumn",
        "powerquery",
        "pivottable",
        "pivottablefield",
        "pivottablecalc",
        "chart",
        "chartconfig",
        "connection",
        "calculationmode",
        "namedrange",
        "conditionalformat",
        "vba",
        "datamodel",
        "datamodelrelationship",
        "slicer"
    ];

    [Theory]
    [MemberData(nameof(ActionEnumTypes))]
    public void GetValidActions_ReturnsAllActionStrings(Type enumType, Type registryType)
    {
        var expected = GetExpectedActions(enumType, registryType);
        var actual = GetActualActions(registryType);

        Assert.Equal(expected, actual);
    }

    [Fact]
    public void ListActionsCommand_AllCommands_ReturnsExpectedKeys()
    {
        var command = new ListActionsCommand();
        var settings = new ListActionsCommand.Settings();

        var context = new CommandContext(
            Array.Empty<string>(),
            new FakeRemainingArguments(),
            "actions",
            null);
        var executeMethod = typeof(ListActionsCommand).GetMethod(
            "Execute",
            BindingFlags.Instance | BindingFlags.NonPublic)!;
        var output = CaptureOutput(() => (int)executeMethod.Invoke(command, [context, settings, CancellationToken.None])!);
        using var document = JsonDocument.Parse(output);

        Assert.True(document.RootElement.GetProperty("success").GetBoolean());
        var commands = document.RootElement.GetProperty("commands");

        foreach (var expected in ExpectedCommands)
        {
            Assert.True(commands.TryGetProperty(expected, out _), $"Missing command '{expected}'.");
        }
    }

    private static string[] GetExpectedActions(Type enumType, Type registryType)
    {
        // Find ToActionString method in the ServiceRegistry nested type (e.g., ServiceRegistry.Range.ToActionString)
        var actionMethod = registryType
            .GetMethods(BindingFlags.Public | BindingFlags.Static)
            .First(m => m.Name == "ToActionString" && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == enumType);

        var values = Enum.GetValues(enumType);
        var results = new List<string>(values.Length);

        foreach (var value in values)
        {
            var action = actionMethod.Invoke(null, [value]) as string;
            results.Add(action ?? string.Empty);
        }

        return results.OrderBy(action => action, StringComparer.OrdinalIgnoreCase).ToArray();
    }

    private static string[] GetActualActions(Type registryType)
    {
        // Get ValidActions field from the ServiceRegistry nested type (e.g., ServiceRegistry.Range.ValidActions)
        var validActionsField = registryType
            .GetFields(BindingFlags.Public | BindingFlags.Static)
            .First(f => f.Name == "ValidActions");

        var actions = (string[])validActionsField.GetValue(null)!;
        return actions.OrderBy(action => action, StringComparer.OrdinalIgnoreCase).ToArray();
    }

    private static string CaptureOutput(Func<int> action)
    {
        var original = Console.Out;
        using var writer = new StringWriter();
        try
        {
            Console.SetOut(writer);
            action();
            return writer.ToString().Trim();
        }
        finally
        {
            Console.SetOut(original);
        }
    }

    private sealed class FakeRemainingArguments : IRemainingArguments
    {
        private static readonly ILookup<string, string?> EmptyLookup =
            Array.Empty<string>().ToLookup(_ => string.Empty, _ => (string?)null);

        public ILookup<string, string?> Parsed { get; } = EmptyLookup;
        public IReadOnlyList<string> Raw { get; } = Array.Empty<string>();
    }
}




