using System.CommandLine;
using System.CommandLine.Parsing;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.SignalR.Client;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;

namespace ZB.MOM.WW.ScadaBridge.CLI.Commands;

public static class DebugCommands
{
    /// <summary>Builds the <c>debug</c> command with its subcommands using the given shared CLI options.</summary>
    /// <param name="urlOption">Shared management URL option.</param>
    /// <param name="formatOption">Shared output format option.</param>
    /// <param name="usernameOption">Shared username option for authentication.</param>
    /// <param name="passwordOption">Shared password option for authentication.</param>
    /// <returns>The configured <c>debug</c> command with snapshot and stream subcommands registered.</returns>
    public static Command Build(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
    {
        var command = new Command("debug") { Description = "Runtime debugging" };

        command.Add(BuildSnapshot(urlOption, formatOption, usernameOption, passwordOption));
        command.Add(BuildStream(urlOption, formatOption, usernameOption, passwordOption));

        return command;
    }

    private static Command BuildSnapshot(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
    {
        var idOption = new Option<int>("--id") { Description = "Instance ID", Required = true };
        var cmd = new Command("snapshot") { Description = "Get a point-in-time snapshot of instance attribute values and alarm states" };
        cmd.Add(idOption);
        cmd.SetAction(async (ParseResult result) =>
        {
            return await CommandHelpers.ExecuteCommandAsync(
                result, urlOption, formatOption, usernameOption, passwordOption,
                new DebugSnapshotCommand(result.GetValue(idOption)));
        });
        return cmd;
    }

    private static Command BuildStream(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
    {
        var idOption = new Option<int>("--id") { Description = "Instance ID", Required = true };
        var cmd = new Command("stream") { Description = "Stream live attribute values and alarm states in real-time (Ctrl+C to stop)" };
        cmd.Add(idOption);
        cmd.SetAction(async (ParseResult result) =>
        {
            var instanceId = result.GetValue(idOption);
            var config = CliConfig.Load();
            var format = CommandHelpers.ResolveFormat(result, formatOption, config);

            var url = result.GetValue(urlOption);
            if (string.IsNullOrWhiteSpace(url))
                url = config.ManagementUrl;

            if (string.IsNullOrWhiteSpace(url))
            {
                OutputFormatter.WriteError(
                    "No management URL specified. Use --url, set SCADABRIDGE_MANAGEMENT_URL, or add 'managementUrl' to ~/.scadabridge/config.json.",
                    "NO_URL");
                return 1;
            }

            if (!CommandHelpers.IsValidManagementUrl(url))
            {
                OutputFormatter.WriteError(
                    $"Invalid management URL '{url}'. Expected an absolute http/https URL (e.g. http://localhost:9001).",
                    "INVALID_URL");
                return 1;
            }

            var username = CommandHelpers.ResolveCredential(result.GetValue(usernameOption), config.Username);
            var password = CommandHelpers.ResolveCredential(result.GetValue(passwordOption), config.Password);

            if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
            {
                OutputFormatter.WriteError(
                    "Credentials required. Use --username/--password or set SCADABRIDGE_USERNAME/SCADABRIDGE_PASSWORD.",
                    "NO_CREDENTIALS");
                return 1;
            }

            return await StreamDebugAsync(url, username, password, instanceId, format);
        });
        return cmd;
    }

    private static async Task<int> StreamDebugAsync(string baseUrl, string username, string password, int instanceId, string format)
    {
        var hubUrl = baseUrl.TrimEnd('/') + "/hubs/debug-stream";
        var credentials = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"));

        var connection = new HubConnectionBuilder()
            .WithUrl(hubUrl, options =>
            {
                options.Headers.Add("Authorization", $"Basic {credentials}");
            })
            .WithAutomaticReconnect()
            .Build();

        // CancellationTokenSource owns a WaitHandle and must be disposed.
        using var cts = new CancellationTokenSource();
        var exitTcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);

        Console.CancelKeyPress += (_, e) =>
        {
            e.Cancel = true;
            cts.Cancel();
        };

        var isTable = string.Equals(format, "table", StringComparison.OrdinalIgnoreCase);

        // Register event handlers
        connection.On<JsonElement>("OnSnapshot", snapshot =>
        {
            if (isTable)
            {
                Console.WriteLine("=== Initial Snapshot ===");
                PrintSnapshotTable(snapshot);
                Console.WriteLine("=== Streaming (Ctrl+C to stop) ===");
            }
            else
            {
                var obj = new { type = "snapshot", data = snapshot };
                Console.WriteLine(JsonSerializer.Serialize(obj, new JsonSerializerOptions { WriteIndented = false }));
            }
        });

        connection.On<JsonElement>("OnAttributeChanged", changed =>
        {
            if (isTable)
            {
                var name = changed.TryGetProperty("attributeName", out var n) ? n.GetString() : "?";
                var value = changed.TryGetProperty("value", out var v) ? v.ToString() : "?";
                var quality = changed.TryGetProperty("quality", out var q) ? q.GetString() : "?";
                var ts = changed.TryGetProperty("timestamp", out var t) ? t.GetString() : "?";
                Console.WriteLine($"  ATTR  {name,-30} {value,-20} {quality,-10} {ts}");
            }
            else
            {
                var obj = new { type = "attributeChanged", data = changed };
                Console.WriteLine(JsonSerializer.Serialize(obj, new JsonSerializerOptions { WriteIndented = false }));
            }
        });

        connection.On<JsonElement>("OnAlarmChanged", changed =>
        {
            if (isTable)
            {
                var name = changed.TryGetProperty("alarmName", out var n) ? n.GetString() : "?";
                var state = changed.TryGetProperty("state", out var s) ? s.ToString() : "?";
                var priority = changed.TryGetProperty("priority", out var p) ? p.ToString() : "?";
                var ts = changed.TryGetProperty("timestamp", out var t) ? t.GetString() : "?";
                Console.WriteLine($"  ALARM {name,-30} {state,-20} P{priority,-9} {ts}");
            }
            else
            {
                var obj = new { type = "alarmChanged", data = changed };
                Console.WriteLine(JsonSerializer.Serialize(obj, new JsonSerializerOptions { WriteIndented = false }));
            }
        });

        connection.On<string>("OnStreamTerminated", reason =>
        {
            Console.Error.WriteLine($"Stream terminated: {reason}");
            exitTcs.TrySetResult(1);
        });

        connection.Closed += ex =>
        {
            if (!cts.IsCancellationRequested)
            {
                Console.Error.WriteLine(ex != null
                    ? $"Connection lost: {ex.Message}"
                    : "Connection closed.");
            }
            exitTcs.TrySetResult(cts.IsCancellationRequested ? 0 : 1);
            return Task.CompletedTask;
        };

        connection.Reconnecting += ex =>
        {
            Console.Error.WriteLine($"Reconnecting... ({ex?.Message})");
            return Task.CompletedTask;
        };

        connection.Reconnected += _ =>
        {
            Console.Error.WriteLine("Reconnected. Re-subscribing...");
            return connection.InvokeAsync("SubscribeInstance", instanceId);
        };

        // Connect and subscribe
        try
        {
            await connection.StartAsync(cts.Token);
        }
        catch (Exception ex)
        {
            // Ctrl+C during connect throws OperationCanceledException — that is
            // a graceful user cancellation, not a connection failure.
            var failure = DebugStreamHelpers.ClassifyConnectFailure(ex, cts.IsCancellationRequested);
            if (failure.IsCancellation)
            {
                await connection.DisposeAsync();
                return failure.ExitCode;
            }

            OutputFormatter.WriteError($"Connection failed: {ex.Message}", "CONNECTION_FAILED");
            await connection.DisposeAsync();
            return failure.ExitCode;
        }

        try
        {
            await connection.InvokeAsync("SubscribeInstance", instanceId, cts.Token);
        }
        catch (Exception ex)
        {
            OutputFormatter.WriteError($"Subscribe failed: {ex.Message}", "SUBSCRIBE_FAILED");
            await connection.DisposeAsync();
            return 1;
        }

        if (isTable)
        {
            Console.WriteLine($"Connected to instance {instanceId}. Waiting for data...");
        }

        // Wait for cancellation (Ctrl+C) or stream termination
        try
        {
            await exitTcs.Task.WaitAsync(cts.Token);
        }
        catch (OperationCanceledException)
        {
            // Ctrl+C — graceful shutdown
        }

        try
        {
            await connection.InvokeAsync("UnsubscribeInstance");
        }
        catch
        {
            // Best effort
        }

        await connection.DisposeAsync();

        // Resolve the exit code from a single authoritative source. A result
        // set by OnStreamTerminated/Closed always wins; a brief grace period covers a
        // termination racing with Ctrl+C. Pure Ctrl+C (no result) is a graceful exit 0.
        return await DebugStreamHelpers.ResolveStreamExitCodeAsync(exitTcs.Task);
    }

    private static void PrintSnapshotTable(JsonElement snapshot)
    {
        if (snapshot.TryGetProperty("attributeValues", out var attrs) && attrs.ValueKind == JsonValueKind.Array)
        {
            Console.WriteLine("  Attributes:");
            Console.WriteLine($"  {"Name",-30} {"Value",-20} {"Quality",-10} Timestamp");
            Console.WriteLine($"  {new string('-', 30)} {new string('-', 20)} {new string('-', 10)} {new string('-', 25)}");
            foreach (var av in attrs.EnumerateArray())
            {
                var name = av.TryGetProperty("attributeName", out var n) ? n.GetString() : "?";
                var value = av.TryGetProperty("value", out var v) ? v.ToString() : "?";
                var quality = av.TryGetProperty("quality", out var q) ? q.GetString() : "?";
                var ts = av.TryGetProperty("timestamp", out var t) ? t.GetString() : "?";
                Console.WriteLine($"  {name,-30} {value,-20} {quality,-10} {ts}");
            }
        }

        if (snapshot.TryGetProperty("alarmStates", out var alarms) && alarms.ValueKind == JsonValueKind.Array)
        {
            Console.WriteLine("  Alarms:");
            Console.WriteLine($"  {"Name",-30} {"State",-20} {"Priority",-10} Timestamp");
            Console.WriteLine($"  {new string('-', 30)} {new string('-', 20)} {new string('-', 10)} {new string('-', 25)}");
            foreach (var al in alarms.EnumerateArray())
            {
                var name = al.TryGetProperty("alarmName", out var n) ? n.GetString() : "?";
                var state = al.TryGetProperty("state", out var s) ? s.ToString() : "?";
                var priority = al.TryGetProperty("priority", out var p) ? p.ToString() : "?";
                var ts = al.TryGetProperty("timestamp", out var t) ? t.GetString() : "?";
                Console.WriteLine($"  {name,-30} {state,-20} P{priority,-9} {ts}");
            }
        }
    }
}
