using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using ArchestrA.MxAccess;
using CliFx;
using CliFx.Attributes;
using CliFx.Infrastructure;
using MxAccess.Cli.Mx;
using MxAccess.Cli.Output;
using Newtonsoft.Json;

namespace MxAccess.Cli.Commands
{
    /// Minimal MxAccess smoke test on a fresh STA thread. Bypasses MxSession,
    /// the dispatched-from-CliFx STA thread, and the DoEvents-in-WaitForUpdate
    /// pump used by read/write/subscribe — everything is inline and explicit.
    /// Used to isolate whether the COM stack is reaching this process at all
    /// when the higher-level commands time out for unclear reasons.
    [Command("diag", Description = "Diagnostic smoke test on a private STA thread: register / add-item / advise / pump.")]
    public sealed class DiagCommand : ICommand
    {
        [CommandParameter(0, Name = "tag", Description = "Tag reference to probe.")]
        public string Tag { get; init; }

        [CommandOption("seconds", 's', Description = "How long to pump the message loop. Default 5.")]
        public double Seconds { get; init; } = 5.0;

        [CommandOption("llm-json", Description = "Emit a JSON envelope { query, ok, result } instead of human-readable phases.")]
        public bool LlmJson { get; init; }

        public ValueTask ExecuteAsync(IConsole console)
        {
            var report = new DiagReport();

            var t = new Thread(() => RunOnSta(report));
            t.SetApartmentState(ApartmentState.STA);
            t.IsBackground = false;
            t.Start();
            t.Join();

            var query = new
            {
                command = "diag",
                tag     = Tag,
                seconds = Seconds,
            };

            if (LlmJson)
            {
                Envelope.Write(console, query, ok: report.Ok, results: new object[] { report.ToJsonObject() });
            }
            else
            {
                WriteHuman(console, report);
            }
            if (!report.Ok) Environment.ExitCode = 1;
            return default;
        }

        private void RunOnSta(DiagReport report)
        {
            try
            {
                var proxy = new LMXProxyServerClass();

                proxy.OnDataChange += (int hSrv, int hItem, object value, int quality, object ts, ref MXSTATUS_PROXY[] vars) =>
                {
                    report.Events.Add(new DiagEvent
                    {
                        Kind     = "DataChange",
                        Value    = value,
                        Quality  = quality,
                        Statuses = MxStatusInfo.From(vars),
                    });
                };
                proxy.OperationComplete += (int hSrv, int hItem, ref MXSTATUS_PROXY[] vars) =>
                {
                    report.Events.Add(new DiagEvent
                    {
                        Kind     = "OperationComplete",
                        Statuses = MxStatusInfo.From(vars),
                    });
                };

                report.HServer = proxy.Register("mxa-diag");
                report.HItem   = proxy.AddItem(report.HServer, Tag);
                proxy.Advise(report.HServer, report.HItem);

                var deadline = DateTime.UtcNow.AddSeconds(Seconds);
                while (DateTime.UtcNow < deadline)
                {
                    Application.DoEvents();
                    Thread.Sleep(50);
                }

                proxy.UnAdvise(report.HServer, report.HItem);
                proxy.RemoveItem(report.HServer, report.HItem);
                proxy.Unregister(report.HServer);

                // Ok = at least one event arrived AND no event reported a
                // non-Ok / non-Pending status. A DataChange with a
                // ConfigurationError (bogus tag) counts as a failure.
                report.Ok = report.Events.Count > 0;
                foreach (var e in report.Events)
                {
                    if (e.Statuses == null) continue;
                    foreach (var s in e.Statuses)
                    {
                        if (s.Category != MxStatusCategory.MxCategoryOk &&
                            s.Category != MxStatusCategory.MxCategoryPending)
                        {
                            report.Ok = false;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                report.Ok = false;
                report.Error = $"{ex.GetType().Name}: {ex.Message}";
            }
        }

        private void WriteHuman(IConsole console, DiagReport report)
        {
            string status;
            if (report.Error != null)        status = "error";
            else if (report.Ok)              status = "ok";
            else if (report.Events.Count == 0) status = "no-events";
            else                             status = "error";
            console.Output.WriteLine($"diag {Tag}  ({Seconds:F1}s, {status})");

            if (report.HServer != 0) console.Output.WriteLine($"  register   hServer={report.HServer}");
            if (report.HItem   != 0) console.Output.WriteLine($"  add-item   hItem={report.HItem}");

            for (int i = 0; i < report.Events.Count; i++)
            {
                var e = report.Events[i];
                if (e.Kind == "DataChange")
                {
                    console.Output.WriteLine($"  event #{i + 1,-2} {e.Kind,-18} value={e.Value} quality={e.Quality}");
                }
                else
                {
                    console.Output.WriteLine($"  event #{i + 1,-2} {e.Kind,-18}");
                }
                foreach (var s in e.Statuses ?? Array.Empty<MxStatusInfo>())
                {
                    console.Output.WriteLine($"               status {s.Category} success={s.Success} detail={s.Detail} detectedBy={s.DetectedBy}");
                }
            }

            int dataChanges  = 0, opCompletes = 0;
            foreach (var e in report.Events) { if (e.Kind == "DataChange") dataChanges++; else opCompletes++; }
            console.Output.WriteLine($"  totals     DataChange={dataChanges}, OperationComplete={opCompletes}");

            if (report.Error != null)
                console.Error.WriteLine($"  error      {report.Error}");
        }

        // ---- Internal collectors ----

        private sealed class DiagReport
        {
            public int HServer;
            public int HItem;
            public bool Ok;
            public string Error;
            public List<DiagEvent> Events { get; } = new List<DiagEvent>();

            public object ToJsonObject()
            {
                int dc = 0, op = 0;
                foreach (var e in Events) { if (e.Kind == "DataChange") dc++; else op++; }
                return new
                {
                    register   = new { hServer = HServer },
                    add_item   = new { hItem = HItem },
                    events     = Events,
                    totals     = new { data_change = dc, operation_complete = op },
                    error      = Error,
                };
            }
        }

        private sealed class DiagEvent
        {
            public string Kind { get; init; }
            public object Value { get; init; }
            public int Quality { get; init; }
            [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
            public MxStatusInfo[] Statuses { get; init; }
        }
    }
}
