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

namespace MxAccess.Cli.Commands
{
    [Command("subscribe", Description = "Subscribe to one or more tags and stream OnDataChange events for a duration.")]
    public sealed class SubscribeCommand : ICommand
    {
        [CommandParameter(0, Name = "tags", Description = "One or more tag references.")]
        public IReadOnlyList<string> Tags { get; init; }

        [CommandOption("seconds", 's', Description = "How long to keep the subscription open, in seconds. Default 10.")]
        public double Seconds { get; init; } = 10.0;

        [CommandOption("max", Description = "Hard cap on emitted events. Default 1000.")]
        public int Max { get; init; } = 1000;

        [CommandOption("client", Description = "MxAccess client name. Default 'mxa'.")]
        public string ClientName { get; init; } = "mxa";

        [CommandOption("llm-json", Description = "Emit a JSON Lines stream of events (one JSON object per line) instead of human-readable lines.")]
        public bool LlmJson { get; init; }

        public ValueTask ExecuteAsync(IConsole console)
        {
            if (Tags == null || Tags.Count == 0)
                throw new CommandException("At least one tag reference is required.", 2);
            if (Seconds <= 0)
                throw new CommandException("--seconds must be positive.", 2);

            var deadline = DateTime.UtcNow.AddSeconds(Seconds);
            var emitted = 0;

            using var session = new MxSession(ClientName);
            var items = new List<MxItem>();
            try
            {
                foreach (var t in Tags)
                {
                    var item = session.AddItem(t);
                    item.Advise();
                    items.Add(item);
                }

                if (!LlmJson)
                    console.Output.WriteLine($"[INFO] Subscribed to {items.Count} tag(s). Streaming for {Seconds:F1}s. Ctrl-C to stop early.");

                // Note: the first OnDataChange arrives ~3-8s after Advise() while
                // LMX resolves the reference and binds to the engine. Plan windows
                // accordingly — short --seconds values may miss the initial value.
                while (DateTime.UtcNow < deadline && emitted < Max)
                {
                    var remaining = deadline - DateTime.UtcNow;
                    if (remaining <= TimeSpan.Zero) break;

                    // Re-use the same wait primitive that read uses. Match every
                    // DataChange event so the loop emits each one as it arrives.
                    if (!session.WaitForUpdate(
                            u => u.Kind == MxUpdateKind.DataChange,
                            remaining, out var u))
                        break;

                    EmitOne(console, u);
                    emitted++;
                }

                if (!LlmJson)
                    console.Output.WriteLine($"[INFO] {emitted} event(s) emitted; subscription closed.");
            }
            finally
            {
                foreach (var item in items) item.Dispose();
            }
            return default;
        }

        private void EmitOne(IConsole console, MxUpdate u)
        {
            if (LlmJson)
            {
                var obj = new
                {
                    tag       = u.ItemReference,
                    ok        = u.IsOk,
                    value     = u.Value,
                    quality   = u.Quality,
                    timestamp = u.Timestamp,
                    statuses  = u.Statuses,
                };
                console.Output.WriteLine(JsonConvert.SerializeObject(obj, Formatting.None));
            }
            else
            {
                var ts = u.Timestamp.HasValue ? u.Timestamp.Value.ToString("HH:mm:ss.fff") : "??:??:??.???";
                var flag = u.IsOk ? "OK " : "ERR";
                console.Output.WriteLine($"[{ts}] [{flag}] {u.ItemReference} = {u.Value} (q={u.Quality})");
            }
        }
    }
}
