using System;
using System.Collections.Generic;
using System.Linq;

namespace ZB.MOM.WW.GRAccess.Cli.Infrastructure
{
    public sealed class CommandArgumentCapability
    {
        public string Name { get; set; } = string.Empty;
        public string Type { get; set; } = "string";
        public bool Required { get; set; }
        public IReadOnlyList<string> Values { get; set; } = Array.Empty<string>();
    }

    public sealed class CommandCapability
    {
        public string Name { get; set; } = string.Empty;
        public string Command { get; set; } = string.Empty;
        public string Subcommand { get; set; } = string.Empty;
        public bool Mutates { get; set; }
        public bool RequiresConfirm { get; set; }
        public string ConfirmTarget { get; set; } = string.Empty;
        public bool RoutesThroughSession { get; set; } = true;
        public bool SupportsJson { get; set; } = true;
        public bool SupportsLlmJson { get; set; } = true;
        public string OutputSchema { get; set; } = "Text";
        public IReadOnlyList<CommandArgumentCapability> Args { get; set; } = Array.Empty<CommandArgumentCapability>();
    }

    public sealed class CommandValidationResult
    {
        public bool Valid => Errors.Count == 0;
        public List<string> Errors { get; } = new List<string>();
        public CommandCapability Capability { get; set; }
    }

    public static class CommandCapabilityRegistry
    {
        private static readonly IReadOnlyList<CommandCapability> _commands = Build();

        public static IReadOnlyList<CommandCapability> Commands => _commands;

        public static CommandCapability Find(string name)
        {
            return _commands.FirstOrDefault(c => string.Equals(c.Name, NormalizeName(name), StringComparison.OrdinalIgnoreCase));
        }

        public static CommandCapability Find(string command, string subcommand)
        {
            command = NormalizeToken(command);
            subcommand = NormalizeToken(subcommand);
            return _commands.FirstOrDefault(c =>
                string.Equals(c.Command, command, StringComparison.OrdinalIgnoreCase)
                && string.Equals(c.Subcommand, subcommand, StringComparison.OrdinalIgnoreCase));
        }

        public static CommandValidationResult Validate(string commandName, IDictionary<string, object> args, bool requireMutationConfirmation)
        {
            var result = new CommandValidationResult();
            var capability = Find(commandName);
            if (capability == null)
            {
                result.Errors.Add($"Unknown command '{commandName}'.");
                return result;
            }

            result.Capability = capability;
            args = args ?? new Dictionary<string, object>();

            foreach (var arg in capability.Args.Where(a => a.Required))
            {
                if (!args.TryGetValue(arg.Name, out var value) || value == null || string.IsNullOrWhiteSpace(Convert.ToString(value)))
                    result.Errors.Add($"Missing required argument '{arg.Name}'.");
            }

            if (requireMutationConfirmation && capability.RequiresConfirm)
            {
                if (!ReadBool(args, "confirm"))
                    result.Errors.Add("Missing required confirm=true.");

                var expected = ExpectedConfirmTarget(capability, args);
                var actual = ReadString(args, "confirm-target");
                if (!string.IsNullOrWhiteSpace(expected) && !string.Equals(expected, actual, StringComparison.OrdinalIgnoreCase))
                    result.Errors.Add($"confirm-target must be '{expected}'.");
            }

            return result;
        }

        public static string ExpectedConfirmTarget(CommandCapability capability, IDictionary<string, object> args)
        {
            if (capability == null || args == null)
                return string.Empty;

            switch (capability.ConfirmTarget)
            {
                case "name": return ReadString(args, "name");
                case "galaxy": return ReadString(args, "galaxy");
                case "file": return ReadString(args, "file");
                case "path": return ReadString(args, "path");
                case "output": return ReadString(args, "output");
                case "default": return ReadString(args, "default");
                case "bulk":
                    var names = ReadList(args, "name").ToList();
                    if (names.Any()) return string.Join(",", names);
                    return string.Join(",", ReadList(args, "pattern"));
                case "template":
                    return ReadString(args, "template");
                default:
                    return capability.ConfirmTarget ?? string.Empty;
            }
        }

        public static (string Command, string Subcommand) SplitCommandName(string commandName)
        {
            var normalized = NormalizeName(commandName);
            var capability = Find(normalized);
            if (capability != null)
                return (capability.Command, capability.Subcommand);

            var parts = normalized.Split(new[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries);
            return parts.Length == 1 ? (parts[0], string.Empty) : (parts[0], parts[1].Replace(' ', '-'));
        }

        private static IReadOnlyList<CommandCapability> Build()
        {
            var list = new List<CommandCapability>();

            void Add(string name, bool mutates = false, string confirmTarget = "", bool session = true, string schema = "Object", params CommandArgumentCapability[] args)
            {
                var split = SplitStatic(name);
                list.Add(new CommandCapability
                {
                    Name = name,
                    Command = split.Command,
                    Subcommand = split.Subcommand,
                    Mutates = mutates,
                    RequiresConfirm = mutates,
                    ConfirmTarget = confirmTarget,
                    RoutesThroughSession = session,
                    OutputSchema = schema,
                    Args = args
                });
            }

            var galaxy = Arg("galaxy", required: true);
            var node = Arg("node");
            var name = Arg("name", required: true);
            var type = Arg("type", "enum", false, "all", "template", "instance");
            var json = Arg("json", "bool");
            var llm = Arg("llm-json", "bool");
            var confirm = Arg("confirm", "bool");
            var confirmTarget = Arg("confirm-target");

            Add("capabilities", session: false, schema: "CapabilityList", args: new[] { json, llm });
            Add("validate", session: false, schema: "ValidationResult", args: new[] { Arg("request", required: true), llm });
            Add("batch", session: true, schema: "BatchResult", args: new[] { Arg("file", required: true), Arg("mode"), llm });

            Add("galaxy list", session: false, schema: "StringArray", args: new[] { node, json, llm });
            Add("galaxy info", args: new[] { galaxy, node, json, llm });
            Add("galaxy sync", args: new[] { galaxy, node, json, llm });
            Add("galaxy cdi-version", args: new[] { galaxy, node, json, llm });
            Add("galaxy defaults get", args: new[] { galaxy, node, Arg("default", required: true), json, llm });
            Add("galaxy defaults set", true, "default", args: new[] { galaxy, node, Arg("default", required: true), Arg("value", required: true), confirm, confirmTarget, llm });
            Add("galaxy backup", true, "file", args: new[] { galaxy, node, Arg("file", required: true), confirm, confirmTarget, llm });
            Add("galaxy restore", true, "file", args: new[] { galaxy, node, Arg("file", required: true), Arg("restore-older", "bool"), confirm, confirmTarget, llm });
            Add("galaxy migrate", true, "galaxy", args: new[] { galaxy, node, confirm, confirmTarget, llm });
            Add("galaxy import-objects", true, "file", args: new[] { galaxy, node, Arg("file", required: true), Arg("overwrite", "bool"), confirm, confirmTarget, llm });
            Add("galaxy import-objects-ex", true, "file", args: new[] { galaxy, node, Arg("file", required: true), Arg("version-conflict", required: true), Arg("name-conflict", required: true), Arg("append-name"), confirm, confirmTarget, llm });
            Add("galaxy import-script-library", true, "path", args: new[] { galaxy, node, Arg("path", required: true), confirm, confirmTarget, llm });
            Add("galaxy export-all", args: new[] { galaxy, node, Arg("output", required: true), Arg("export-type"), json, llm });
            Add("galaxy grload", true, "file", args: new[] { galaxy, node, Arg("file", required: true), Arg("mode"), confirm, confirmTarget, llm });
            Add("galaxy create", true, "galaxy", session: false, args: new[] { Arg("galaxy", required: true), node, confirm, confirmTarget });
            Add("galaxy create-from-template", true, "galaxy", session: false, args: new[] { Arg("galaxy", required: true), node, Arg("template", required: true), confirm, confirmTarget });
            Add("galaxy delete", true, "galaxy", session: false, args: new[] { Arg("galaxy", required: true), node, confirm, confirmTarget });

            Add("object list", args: new[] { galaxy, node, type, Arg("pattern"), json, llm });
            Add("template list", args: new[] { galaxy, node, Arg("pattern"), json, llm });
            Add("instance list", args: new[] { galaxy, node, Arg("pattern"), json, llm });
            Add("object get", args: new[] { galaxy, node, name, type, json, llm });
            Add("object snapshot", schema: "ObjectSnapshot", args: new[] { galaxy, node, name, type, llm });
            Add("object lineage", schema: "ObjectLineage", args: new[] { galaxy, node, name, type, json, llm });
            Add("object children", schema: "ObjectChildren", args: new[] { galaxy, node, name, type, json, llm });
            Add("object query-name", args: new[] { galaxy, node, Arg("name", "array", true), type, json, llm });
            Add("object query-condition", args: new[] { galaxy, node, Arg("condition"), Arg("value"), type, json, llm });
            Add("object query-multi", args: new[] { galaxy, node, Arg("pattern", "array"), type, json, llm });
            Add("object attributes", args: new[] { galaxy, node, name, type, Arg("configurable", "bool"), json, llm });
            Add("object extended-attributes", args: new[] { galaxy, node, name, type, Arg("attribute"), Arg("level", "int"), json, llm });
            Add("object help-url", args: new[] { galaxy, node, name, type, json, llm });
            Add("object checkout", true, "name", args: new[] { galaxy, node, name, type, confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("object checkin", true, "name", args: new[] { galaxy, node, name, type, Arg("comment"), confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("object undo-checkout", true, "name", args: new[] { galaxy, node, name, type, confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("object save", true, "name", args: new[] { galaxy, node, name, type, confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("object unload", true, "name", args: new[] { galaxy, node, name, type, confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("object set", true, "name", args: new[] { galaxy, node, name, type, Arg("property", required: true), Arg("value", required: true), confirm, confirmTarget, llm, Arg("dry-run", "bool") });

            Add("object attribute get", args: new[] { galaxy, node, name, type, Arg("attribute", required: true), json, llm });
            Add("object attribute set", true, "name", args: new[] { galaxy, node, name, type, Arg("attribute", required: true), Arg("value", required: true), Arg("data-type"), confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("object attribute value get", args: new[] { galaxy, node, name, type, Arg("attribute", required: true), json, llm });
            Add("object attribute value set", true, "name", args: new[] { galaxy, node, name, type, Arg("attribute", required: true), Arg("value", required: true), Arg("data-type"), confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("object attribute lock", true, "name", args: new[] { galaxy, node, name, type, Arg("attribute", required: true), Arg("locked", required: true), confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("object attribute security", true, "name", args: new[] { galaxy, node, name, type, Arg("attribute", required: true), Arg("security", required: true), confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("object attribute buffer", true, "name", args: new[] { galaxy, node, name, type, Arg("attribute", required: true), Arg("has-buffer", "bool"), confirm, confirmTarget, llm, Arg("dry-run", "bool") });

            Add("template derive", true, "name", args: new[] { galaxy, node, name, type, Arg("new-name", required: true), Arg("create-contained", "bool"), confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("template instantiate", true, "name", args: new[] { galaxy, node, name, type, Arg("new-name", required: true), Arg("create-contained", "bool"), confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("template delete", true, "name", args: new[] { galaxy, node, name, type, Arg("force-option"), confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("instance delete", true, "name", args: new[] { galaxy, node, name, type, Arg("force-option"), confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("instance deploy", true, "name", args: new[] { galaxy, node, name, type, confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("instance undeploy", true, "name", args: new[] { galaxy, node, name, type, confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("instance upload", true, "name", args: new[] { galaxy, node, name, type, confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("instance assign-area", true, "name", args: new[] { galaxy, node, name, Arg("area", required: true), confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("instance assign-engine", true, "name", args: new[] { galaxy, node, name, Arg("engine", required: true), confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("instance assign-container", true, "name", args: new[] { galaxy, node, name, Arg("container", required: true), confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("io assign", true, "name", args: new[] { galaxy, node, name, Arg("attribute", required: true), Arg("value", required: true), Arg("data-type"), confirm, confirmTarget, llm, Arg("dry-run", "bool") });

            Add("objects checkout", true, "bulk", args: new[] { galaxy, node, Arg("name", "array"), Arg("pattern", "array"), type, confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("objects checkin", true, "bulk", args: new[] { galaxy, node, Arg("name", "array"), Arg("pattern", "array"), type, Arg("comment"), confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("objects undo-checkout", true, "bulk", args: new[] { galaxy, node, Arg("name", "array"), Arg("pattern", "array"), type, confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("objects deploy", true, "bulk", args: new[] { galaxy, node, Arg("name", "array"), Arg("pattern", "array"), type, confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("objects undeploy", true, "bulk", args: new[] { galaxy, node, Arg("name", "array"), Arg("pattern", "array"), type, confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("objects upload", true, "bulk", args: new[] { galaxy, node, Arg("name", "array"), Arg("pattern", "array"), type, confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("objects delete", true, "bulk", args: new[] { galaxy, node, Arg("name", "array"), Arg("pattern", "array"), type, confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("objects export", true, "output", args: new[] { galaxy, node, Arg("name", "array"), Arg("pattern", "array"), type, Arg("output", required: true), Arg("export-type"), confirm, confirmTarget, llm });
            Add("objects export-protected", true, "output", args: new[] { galaxy, node, Arg("name", "array"), Arg("pattern", "array"), type, Arg("output", required: true), confirm, confirmTarget, llm });

            Add("object scripts list", args: new[] { galaxy, node, name, type, json, llm });
            Add("object scripts get", args: new[] { galaxy, node, name, type, Arg("script", required: true), json, llm });
            Add("object scripts set", true, "name", args: new[] { galaxy, node, name, type, Arg("script", required: true), Arg("file", required: true), confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("object scripts create", true, "name", args: new[] { galaxy, node, name, type, Arg("script", required: true), Arg("file"), Arg("trigger-period-ms"), Arg("trigger-type"), Arg("expression"), Arg("lock-trigger-period", "bool"), confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("object scripts settings set", true, "name", args: new[] { galaxy, node, name, type, Arg("script", required: true), Arg("trigger-period-ms"), Arg("trigger-type"), Arg("expression"), Arg("lock-trigger-period", "bool"), confirm, confirmTarget, llm, Arg("dry-run", "bool") });

            Add("area list", args: new[] { galaxy, node, Arg("pattern"), json, llm });
            Add("area create", true, "template", args: new[] { galaxy, node, Arg("template", required: true), Arg("name", required: true), Arg("create-contained", "bool"), confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("engine list", args: new[] { galaxy, node, Arg("pattern"), json, llm });
            Add("engine create", true, "template", args: new[] { galaxy, node, Arg("template", required: true), Arg("name", required: true), Arg("create-contained", "bool"), confirm, confirmTarget, llm, Arg("dry-run", "bool") });

            Add("toolset list", args: new[] { galaxy, node, json, llm });
            Add("toolset tree", args: new[] { galaxy, node, json, llm });
            Add("toolset add", true, "name", args: new[] { galaxy, node, name, confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("toolset delete", true, "name", args: new[] { galaxy, node, name, confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("toolset rename", true, "name", args: new[] { galaxy, node, name, Arg("new-name", required: true), confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("toolset move", true, "name", args: new[] { galaxy, node, name, Arg("parent", required: true), confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("script-library list", args: new[] { galaxy, node, json, llm });
            Add("script-library add", true, "path", args: new[] { galaxy, node, Arg("path", required: true), confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("script-library import", true, "path", args: new[] { galaxy, node, Arg("path", required: true), confirm, confirmTarget, llm, Arg("dry-run", "bool") });
            Add("script-library export", true, "output", args: new[] { galaxy, node, name, Arg("output", required: true), confirm, confirmTarget, llm });
            Add("security info", args: new[] { galaxy, node, json, llm });
            Add("security roles", args: new[] { galaxy, node, json, llm });
            Add("security users", args: new[] { galaxy, node, json, llm });
            Add("security groups", args: new[] { galaxy, node, json, llm });
            Add("security permissions", args: new[] { galaxy, node, Arg("role", required: true), json, llm });
            Add("settings locale get", args: new[] { galaxy, node, json, llm });
            Add("settings time-master get", args: new[] { galaxy, node, json, llm });

            return list.OrderBy(c => c.Name, StringComparer.OrdinalIgnoreCase).ToList();
        }

        private static CommandArgumentCapability Arg(string name, string type = "string", bool required = false, params string[] values)
        {
            return new CommandArgumentCapability { Name = name, Type = type, Required = required, Values = values };
        }

        private static (string Command, string Subcommand) SplitStatic(string name)
        {
            var normalized = NormalizeName(name);
            var first = normalized.Split(' ')[0];
            return (first, normalized.Substring(first.Length).Trim().Replace(' ', '-'));
        }

        private static string NormalizeName(string value)
        {
            return string.Join(" ", (value ?? string.Empty).Trim().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
        }

        private static string NormalizeToken(string value)
        {
            return (value ?? string.Empty).Trim().ToLowerInvariant();
        }

        private static bool ReadBool(IDictionary<string, object> args, string key)
        {
            return args.TryGetValue(key, out var value) && value != null && (value is bool b ? b : bool.TryParse(Convert.ToString(value), out var parsed) && parsed);
        }

        private static string ReadString(IDictionary<string, object> args, string key)
        {
            return args.TryGetValue(key, out var value) && value != null ? Convert.ToString(value) ?? string.Empty : string.Empty;
        }

        private static IEnumerable<string> ReadList(IDictionary<string, object> args, string key)
        {
            if (!args.TryGetValue(key, out var value) || value == null)
                yield break;

            if (value is string s)
            {
                foreach (var item in s.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
                    yield return item.Trim();
                yield break;
            }

            if (value is System.Collections.IEnumerable enumerable)
            {
                foreach (var item in enumerable)
                    if (item != null)
                        yield return Convert.ToString(item);
            }
        }
    }
}
