using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using CliFx;
using CliFx.Attributes;
using CliFx.Exceptions;
using CliFx.Infrastructure;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using ZB.MOM.WW.GRAccess.Cli.GRAccess;
using ZB.MOM.WW.GRAccess.Cli.Infrastructure;
using ZB.MOM.WW.GRAccess.Cli.Protocol;
using ZB.MOM.WW.GRAccess.Cli.Session;

namespace ZB.MOM.WW.GRAccess.Cli.Commands
{
    [Command("capabilities", Description = "List machine-readable command capabilities")]
    public sealed class CapabilitiesCommand : ICommand
    {
        [CommandOption("json", Description = "Output as JSON")]
        public bool Json { get; init; }

        [CommandOption("llm-json", Description = "Output stable machine JSON envelope")]
        public bool LlmJson { get; init; }

        public async ValueTask ExecuteAsync(IConsole console)
        {
            var data = new
            {
                Version = 1,
                Commands = CommandCapabilityRegistry.Commands
            };

            if (LlmJson)
            {
                await console.Output.WriteLineAsync(LlmResponse.Ok("capabilities", string.Empty, string.Empty, data)).ConfigureAwait(false);
                return;
            }

            if (Json)
            {
                await console.Output.WriteLineAsync(JsonConvert.SerializeObject(data, Formatting.Indented)).ConfigureAwait(false);
                return;
            }

            foreach (var command in CommandCapabilityRegistry.Commands)
                await console.Output.WriteLineAsync($"{command.Name}\tmutates={command.Mutates}\tsession={command.RoutesThroughSession}").ConfigureAwait(false);
        }
    }

    [Command("validate", Description = "Validate a machine command or batch plan JSON file")]
    public sealed class ValidateCommand : ICommand
    {
        [CommandOption("request", Description = "Plan JSON file path", IsRequired = true)]
        public string Request { get; init; }

        [CommandOption("llm-json", Description = "Output stable machine JSON envelope")]
        public bool LlmJson { get; init; }

        public async ValueTask ExecuteAsync(IConsole console)
        {
            var plan = BatchPlan.Load(Request);
            var result = BatchValidator.Validate(plan, requireMutationConfirmation: true);
            var output = new { result.Valid, Steps = result.Steps };

            if (LlmJson)
                await console.Output.WriteLineAsync(LlmResponse.Ok("validate", plan.Galaxy, Request, output)).ConfigureAwait(false);
            else
                await console.Output.WriteLineAsync(JsonConvert.SerializeObject(output, Formatting.Indented)).ConfigureAwait(false);
        }
    }

    [Command("batch", Description = "Validate or execute a machine command plan")]
    public sealed class BatchCommand : ICommand
    {
        [CommandOption("file", Description = "Plan JSON file path", IsRequired = true)]
        public string File { get; init; }

        [CommandOption("mode", Description = "validate or execute")]
        public string Mode { get; init; } = "validate";

        [CommandOption("llm-json", Description = "Output stable machine JSON envelope")]
        public bool LlmJson { get; init; }

        public async ValueTask ExecuteAsync(IConsole console)
        {
            var plan = BatchPlan.Load(File);
            var validation = BatchValidator.Validate(plan, requireMutationConfirmation: true);

            if (!string.Equals(Mode, "execute", StringComparison.OrdinalIgnoreCase))
            {
                await WriteBatchOutput(console, plan, new { validation.Valid, Steps = validation.Steps }, validation.Valid).ConfigureAwait(false);
                return;
            }

            if (!validation.Valid)
            {
                await WriteBatchOutput(console, plan, new { validation.Valid, Steps = validation.Steps }, false).ConfigureAwait(false);
                throw new CommandException("Batch validation failed.", 1);
            }

            var outputs = new List<object>();
            for (var i = 0; i < plan.Commands.Count; i++)
            {
                var step = plan.Commands[i];
                try
                {
                    var output = await ExecuteStepAsync(plan, step).ConfigureAwait(false);
                    outputs.Add(new { Index = i, step.Command, Success = true, Output = ParseOutput(output) });
                }
                catch (Exception ex)
                {
                    outputs.Add(new { Index = i, step.Command, Success = false, Error = ex.Message });
                    await WriteBatchOutput(console, plan, new { Valid = true, Executed = outputs }, false).ConfigureAwait(false);
                    throw new CommandException(ex.Message, 1);
                }
            }

            await WriteBatchOutput(console, plan, new { Valid = true, Executed = outputs }, true).ConfigureAwait(false);
        }

        private async Task WriteBatchOutput(IConsole console, BatchPlan plan, object data, bool success)
        {
            if (LlmJson)
            {
                var response = new LlmResponse
                {
                    Success = success,
                    Command = "batch",
                    Galaxy = plan.Galaxy ?? string.Empty,
                    Target = File ?? string.Empty,
                    Data = data,
                    ExitCode = success ? 0 : 1
                };
                await console.Output.WriteLineAsync(LlmResponse.Serialize(response)).ConfigureAwait(false);
                return;
            }

            await console.Output.WriteLineAsync(JsonConvert.SerializeObject(data, Formatting.Indented)).ConfigureAwait(false);
        }

        private static async Task<string> ExecuteStepAsync(BatchPlan plan, BatchStep step)
        {
            var args = step.MaterializedArgs(plan);
            args["llm-json"] = true;
            args["json"] = false;

            var split = CommandCapabilityRegistry.SplitCommandName(step.Command);
            var galaxy = ReadString(args, "galaxy", plan.Galaxy);
            var node = ReadString(args, "node", plan.Node);

            if (SessionClient.TryConnect(galaxy, out var client))
            {
                using (client)
                {
                    var response = await client.SendCommandAsync(PipeRequest.Execute(split.Command, split.Subcommand, args)).ConfigureAwait(false);
                    if (!response.Success)
                        throw new InvalidOperationException(response.Error);
                    return response.Output;
                }
            }

            if (string.IsNullOrWhiteSpace(node))
                throw new InvalidOperationException("No active session found. Provide a top-level or step node for one-shot batch execution.");

            using (var connection = new GRAccessConnection(galaxy, node))
            {
                connection.Connect();
                return GRAccessCommandDispatcher.Execute(connection.Galaxy, split.Command, split.Subcommand, args);
            }
        }

        private static object ParseOutput(string output)
        {
            if (string.IsNullOrWhiteSpace(output))
                return null;

            try { return JToken.Parse(output); }
            catch { return output; }
        }

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

    public sealed class BatchPlan
    {
        public string Galaxy { get; set; } = string.Empty;
        public string Node { get; set; } = string.Empty;
        public List<BatchStep> Commands { get; set; } = new List<BatchStep>();

        public static BatchPlan Load(string path)
        {
            var root = JObject.Parse(System.IO.File.ReadAllText(path));
            if (root["commands"] == null && root["command"] != null)
                root = new JObject { ["commands"] = new JArray(root) };

            var plan = root.ToObject<BatchPlan>() ?? new BatchPlan();
            if (plan.Commands == null)
                plan.Commands = new List<BatchStep>();
            return plan;
        }
    }

    public sealed class BatchStep
    {
        public string Command { get; set; } = string.Empty;
        public Dictionary<string, object> Args { get; set; } = new Dictionary<string, object>();

        public Dictionary<string, object> MaterializedArgs(BatchPlan plan)
        {
            var args = Args == null
                ? new Dictionary<string, object>()
                : new Dictionary<string, object>(Args, StringComparer.OrdinalIgnoreCase);

            if (!args.ContainsKey("galaxy") && !string.IsNullOrWhiteSpace(plan.Galaxy))
                args["galaxy"] = plan.Galaxy;
            if (!args.ContainsKey("node") && !string.IsNullOrWhiteSpace(plan.Node))
                args["node"] = plan.Node;

            return args;
        }
    }

    public sealed class BatchValidationSummary
    {
        public bool Valid => Steps.All(s => s.Valid);
        public List<BatchStepValidation> Steps { get; } = new List<BatchStepValidation>();
    }

    public sealed class BatchStepValidation
    {
        public int Index { get; set; }
        public string Command { get; set; } = string.Empty;
        public bool Valid { get; set; }
        public IReadOnlyList<string> Errors { get; set; } = Array.Empty<string>();
        public bool Mutates { get; set; }
        public string ConfirmationTargetRule { get; set; } = string.Empty;
    }

    public static class BatchValidator
    {
        public static BatchValidationSummary Validate(BatchPlan plan, bool requireMutationConfirmation)
        {
            var summary = new BatchValidationSummary();
            for (var i = 0; i < plan.Commands.Count; i++)
            {
                var step = plan.Commands[i];
                var args = step.MaterializedArgs(plan);
                var result = CommandCapabilityRegistry.Validate(step.Command, args, requireMutationConfirmation);
                summary.Steps.Add(new BatchStepValidation
                {
                    Index = i,
                    Command = step.Command,
                    Valid = result.Valid,
                    Errors = result.Errors,
                    Mutates = result.Capability?.Mutates ?? false,
                    ConfirmationTargetRule = result.Capability?.ConfirmTarget ?? string.Empty
                });
            }

            if (plan.Commands.Count == 0)
                summary.Steps.Add(new BatchStepValidation { Index = -1, Command = string.Empty, Valid = false, Errors = new[] { "Plan contains no commands." }, Mutates = false, ConfirmationTargetRule = string.Empty });

            return summary;
        }
    }
}
