using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using CliFx.Attributes;
using Shouldly;
using Xunit;
using ZB.MOM.WW.GRAccess.Cli.Commands;
using ZB.MOM.WW.GRAccess.Cli.Protocol;

namespace ZB.MOM.WW.GRAccess.Cli.Tests.Commands
{
    public class GRAccessSurfaceCommandTests
    {
        [Theory]
        [InlineData(typeof(GalaxyInfoCommand), "galaxy info")]
        [InlineData(typeof(GalaxyImportScriptLibraryCommand), "galaxy import-script-library")]
        [InlineData(typeof(ObjectGetCommand), "object get")]
        [InlineData(typeof(ObjectQueryNameCommand), "object query-name")]
        [InlineData(typeof(ObjectScriptsCreateCommand), "object scripts create")]
        [InlineData(typeof(ObjectScriptsSettingsSetCommand), "object scripts settings set")]
        [InlineData(typeof(ObjectScriptsSetCommand), "object scripts set")]
        [InlineData(typeof(ObjectScriptsDeleteCommand), "object scripts delete")]
        [InlineData(typeof(TemplateDeriveCommand), "template derive")]
        [InlineData(typeof(InstanceDeployCommand), "instance deploy")]
        [InlineData(typeof(ObjectsExportCommand), "objects export")]
        [InlineData(typeof(ToolsetListCommand), "toolset list")]
        [InlineData(typeof(ScriptLibraryExportCommand), "script-library export")]
        [InlineData(typeof(SecurityRolesCommand), "security roles")]
        [InlineData(typeof(SettingsLocaleGetCommand), "settings locale get")]
        public void Command_IsRegistered(Type commandType, string expectedName)
        {
            var attr = (CommandAttribute)Attribute.GetCustomAttribute(
                commandType,
                typeof(CommandAttribute));

            attr.ShouldNotBeNull();
            attr.Name.ShouldBe(expectedName);
        }

        [Fact]
        public void PipeRequest_StoresStructuredArgs()
        {
            var request = PipeRequest.Execute(
                "objects",
                "checkout",
                new Dictionary<string, object>
                {
                    ["name"] = new[] { "A", "B" },
                    ["confirm"] = true
                });

            request.Args["name"].ShouldBeAssignableTo<string[]>();
            ((string[])request.Args["name"]).ShouldBe(new[] { "A", "B" });
            request.Args["confirm"].ShouldBe(true);
        }

        [Fact]
        public void ConfirmedCommand_IncludesConfirmationArgs()
        {
            var command = new ObjectCheckoutCommand
            {
                GalaxyName = "ZB",
                ObjectName = "DEV",
                Confirm = true,
                ConfirmTarget = "DEV"
            };

            var args = command.Args();

            args["confirm"].ShouldBe(true);
            args["confirm-target"].ShouldBe("DEV");
            args["name"].ShouldBe("DEV");
        }

        [Fact]
        public void BulkCommand_PreservesMultipleNames()
        {
            var command = new ObjectsCheckoutCommand
            {
                GalaxyName = "ZB",
                Names = new[] { "A", "B" },
                Confirm = true,
                ConfirmTarget = "A,B"
            };

            var names = ((IReadOnlyList<string>)command.Args()["name"]).ToArray();

            names.ShouldBe(new[] { "A", "B" });
        }

        [Fact]
        public void UdaCommands_DefaultToValidWritableCategory()
        {
            new ObjectUdaAddCommand().Args()["category"].ShouldBe("MxCategoryWriteable_USC");
            new ObjectUdaUpdateCommand().Args()["category"].ShouldBe("MxCategoryWriteable_USC");
        }

        [Fact]
        public void TemplateDelete_DefaultsToNonCascadingForceOption()
        {
            new TemplateDeleteCommand().Args()["force-option"].ShouldBe("dontForceTemplateDelete");
        }

        [Fact]
        public void InstanceDelete_DefaultsToDeleteCleanupForceOption()
        {
            new InstanceDeleteCommand().Args()["force-option"].ShouldBe("undeployIfDeployed");
        }

        [Theory]
        [InlineData("EForceDeleteTemplateOption", "dontForceTemplateDelete", "dontForceTemplateDelete")]
        [InlineData("EForceDeleteTemplateOption", "galaxy_dontForceTemplateDelete", "dontForceTemplateDelete")]
        [InlineData("MxAttributeCategory", "mxcategorywriteable_usc", "MxCategoryWriteable_USC")]
        public void DispatcherEnumParser_AcceptsCaseInsensitiveNamesAndGalaxyPrefixAliases(
            string enumType,
            string value,
            string expected)
        {
            var dispatcher = typeof(GalaxyInfoCommand).Assembly.GetType("ZB.MOM.WW.GRAccess.Cli.GRAccess.GRAccessCommandDispatcher");
            var enumValue = dispatcher.GetMethod("EnumValue", BindingFlags.Static | BindingFlags.NonPublic, null, new[] { typeof(string), typeof(string) }, null)
                .Invoke(null, new object[] { enumType, value });

            enumValue.ToString().ShouldBe(expected);
        }

        [Theory]
        [InlineData("AnalogLimitAlarm", "AnalogExtension")]
        [InlineData("analoglimitalarm", "AnalogExtension")]
        [InlineData("HistoryExtension", "HistoryExtension")]
        public void DispatcherExtensionType_NormalizesLegacyAliases(string value, string expected)
        {
            var dispatcher = typeof(GalaxyInfoCommand).Assembly.GetType("ZB.MOM.WW.GRAccess.Cli.GRAccess.GRAccessCommandDispatcher");
            var extensionType = dispatcher.GetMethod("ExtensionType", BindingFlags.Static | BindingFlags.NonPublic)
                .Invoke(null, new object[] { value });

            extensionType.ShouldBe(expected);
        }

        [Fact]
        public void DispatcherAtomicObjectEdit_UsesExpectedLifecycle()
        {
            var source = DispatcherSource();
            var helper = source.Substring(source.IndexOf("private static string AtomicObjectEdit", StringComparison.Ordinal));

            helper.IndexOf("obj.CheckOut();", StringComparison.Ordinal).ShouldBeLessThan(
                helper.IndexOf("var summary = mutation(obj);", StringComparison.Ordinal));
            helper.IndexOf("var summary = mutation(obj);", StringComparison.Ordinal).ShouldBeLessThan(
                helper.IndexOf("obj.Save();", StringComparison.Ordinal));
            helper.IndexOf("obj.Save();", StringComparison.Ordinal).ShouldBeLessThan(
                helper.IndexOf("obj.CheckIn(string.Empty);", StringComparison.Ordinal));
            helper.ShouldContain("obj.UndoCheckOut();");
        }

        [Fact]
        public void DispatcherSingleObjectMutations_UseAtomicEditHelper()
        {
            var source = DispatcherSource();

            source.ShouldContain("return AtomicObjectEdit(obj, editObj =>");
            source.ShouldContain("return AtomicObjectEdit(FindSingleObject(galaxy, Kind(args), Arg(args, \"name\")), obj => ObjectScriptCreate");
            source.ShouldContain("return AtomicObjectEdit(FindSingleObject(galaxy, Kind(args), Arg(args, \"name\")), obj => ObjectScriptSet");
            source.ShouldContain("return AtomicObjectEdit(FindSingleObject(galaxy, Kind(args), Arg(args, \"name\")), obj => ObjectScriptSettingsSet");
        }

        [Fact]
        public void DispatcherScriptDeleteMutation_RequiresConfirmation()
        {
            var source = DispatcherSource();
            var branch = source.Substring(
                source.IndexOf("case \"scripts-delete\":", StringComparison.Ordinal),
                source.IndexOf("case \"scripts-set\":", StringComparison.Ordinal) - source.IndexOf("case \"scripts-delete\":", StringComparison.Ordinal));

            branch.ShouldContain("RequireConfirm(args, Arg(args, \"name\"));");
        }

        // Removed: tests asserting EnsureMutableViaSetValue and "package-only"
        // wording in command descriptions. Both reflected an earlier (incorrect)
        // belief that IAttribute.SetValue silently no-ops on
        // MxCategoryPackageOnly_Lockable script-text fields. Live round-trip
        // (analysis/ide-edit-investigation/probe_setvalue/) and AVEVA Tech Note
        // 537 confirm writes via ConfigurableAttributes[<script>.<field>]
        // .SetValue persist; the apparent "no-op" was a MxValueDetails reader
        // gap, not a write-side failure. The safety check + asserting tests
        // were reverted in commits 4e242ca / e4e5425 + the corrective commit.

        [Fact]
        public void DispatcherElapsedMilliseconds_UseDocumentedHundredNanosecondUnits()
        {
            var source = DispatcherSource();

            source.ShouldContain("var elapsedMs = Int64ToElapsedTime(Convert.ToInt64(milliseconds * 10000d));");
            source.ShouldContain("mxValue.PutElapsedTime(ref elapsedMs);");
        }

        [Fact]
        public void ScriptCommandDescriptions_DocumentTheTN537Pattern()
        {
            var set = (CommandAttribute)Attribute.GetCustomAttribute(
                typeof(ObjectScriptsSetCommand),
                typeof(CommandAttribute));
            var settings = (CommandAttribute)Attribute.GetCustomAttribute(
                typeof(ObjectScriptsSettingsSetCommand),
                typeof(CommandAttribute));

            set.ShouldNotBeNull();
            settings.ShouldNotBeNull();
            set.Description.ShouldNotBeNull();
            settings.Description.ShouldNotBeNull();
            // Set-body uses TN-537's ConfigurableAttributes pattern.
            set.Description!.ShouldContain("TN-537");
            // Settings command writes through ConfigurableAttributes too.
            settings.Description!.ShouldContain("ConfigurableAttributes");
        }

        private static string DispatcherSource()
        {
            var directory = new DirectoryInfo(Directory.GetCurrentDirectory());
            while (directory != null)
            {
                var sourcePath = Path.Combine(
                    directory.FullName,
                    "src",
                    "ZB.MOM.WW.GRAccess.Cli",
                    "GRAccess",
                    "GRAccessCommandDispatcher.cs");

                if (File.Exists(sourcePath))
                    return File.ReadAllText(sourcePath);

                directory = directory.Parent;
            }

            throw new FileNotFoundException("Could not locate GRAccessCommandDispatcher.cs from the test working directory.");
        }
    }
}
