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-batch", Description =
        "Subscribe to many tags from a JSONL input file (or stdin via '-') and stream " +
        "OnDataChange events for a duration. Same streaming output as `subscribe`.")]
    public sealed class SubscribeBatchCommand : ICommand
    {
        [CommandParameter(0, Name = "input",
            Description = "Path to a JSONL file. Use '-' to read JSONL from stdin. " +
                          "Each line is either a bare tag string or {\"tag\":\"...\"}.")]
        public string Input { 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 (string.IsNullOrWhiteSpace(Input))
                throw new CommandException("Input path is required (use '-' for stdin).", 2);
            if (Seconds <= 0)
                throw new CommandException("--seconds must be positive.", 2);

            // Parse + validate the whole input before opening an LMX session.
            var entries = new List<BatchReadEntry>();
            var parseErrors = new List<string>();
            try
            {
                foreach (var (lineNo, json) in JsonlInputReader.ReadLines(Input))
                {
                    try { entries.Add(JsonlInputReader.ParseReadEntry(lineNo, json)); }
                    catch (InvalidBatchEntryException ex)
                    {
                        parseErrors.Add($"line {ex.LineNumber}: {ex.Message}");
                    }
                }
            }
            catch (InvalidBatchInputException ex)
            {
                console.Error.WriteLine($"[ERR] {ex.Message}");
                Environment.ExitCode = 2;
                return default;
            }

            if (parseErrors.Count > 0)
            {
                foreach (var msg in parseErrors) console.Error.WriteLine("[ERR] " + msg);
                Environment.ExitCode = 2;
                return default;
            }

            if (entries.Count == 0)
            {
                console.Error.WriteLine("[ERR] empty-input");
                Environment.ExitCode = 2;
                return default;
            }

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

            using var session = new MxSession(ClientName);
            var items = new List<MxItem>();
            try
            {
                foreach (var e in entries)
                {
                    try
                    {
                        var item = session.AddItem(e.Tag);
                        item.Advise();
                        items.Add(item);
                    }
                    catch (Exception ex)
                    {
                        // Match SubscribeCommand's tolerance: a bad reference
                        // shouldn't kill the whole stream. Report it inline.
                        console.Error.WriteLine($"[ERR] line {e.LineNumber} add-item {e.Tag}: {ex.Message}");
                    }
                }

                if (items.Count == 0)
                {
                    console.Error.WriteLine("[ERR] no tags successfully subscribed");
                    Environment.ExitCode = 1;
                    return default;
                }

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

                while (DateTime.UtcNow < deadline && emitted < Max)
                {
                    var remaining = deadline - DateTime.UtcNow;
                    if (remaining <= TimeSpan.Zero) break;

                    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})");
            }
        }
    }
}
