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

/// <summary>
/// Pure, testable helpers for the <c>debug stream</c> command. The SignalR-driven
/// <see cref="DebugCommands"/> body itself cannot be unit-tested without a live hub, so
/// the decision logic — connect-failure classification and exit-code
/// resolution after stream termination — is extracted here.
/// </summary>
internal static class DebugStreamHelpers
{
    /// <summary>
    /// The maximum time <see cref="ResolveStreamExitCodeAsync"/> waits for an in-flight
    /// <c>TrySetResult</c> (from <c>OnStreamTerminated</c>/<c>Closed</c>) to land after
    /// the wait was cancelled by Ctrl+C, so a termination racing with cancellation is
    /// observed deterministically rather than depending on scheduling.
    /// </summary>
    internal static readonly TimeSpan ExitGracePeriod = TimeSpan.FromMilliseconds(250);

    /// <summary>Outcome of classifying an exception thrown while connecting.</summary>
    internal readonly record struct ConnectFailure(bool IsCancellation, int ExitCode);

    /// <summary>
    /// Classifies an exception thrown by <c>HubConnection.StartAsync</c>. A
    /// cancellation exception that coincides with a user-requested cancellation
    /// (Ctrl+C during connect) is a graceful shutdown — exit 0, no error printed.
    /// Anything else is a genuine connection failure — exit 1.
    /// </summary>
    /// <param name="ex">The exception thrown by HubConnection.StartAsync.</param>
    /// <param name="cancellationRequested">True when the user requested cancellation (Ctrl+C) before the exception was thrown.</param>
    /// <returns>A <see cref="ConnectFailure"/> describing whether the failure was a cancellation and the appropriate exit code.</returns>
    internal static ConnectFailure ClassifyConnectFailure(Exception ex, bool cancellationRequested)
    {
        if (cancellationRequested && ex is OperationCanceledException)
            return new ConnectFailure(IsCancellation: true, ExitCode: 0);

        return new ConnectFailure(IsCancellation: false, ExitCode: 1);
    }

    /// <summary>
    /// Resolves the <c>debug stream</c> exit code from a single authoritative source —
    /// the <c>exitTcs</c> task. If a result was set by <c>OnStreamTerminated</c> or the
    /// <c>Closed</c> handler it is always preferred (even when Ctrl+C also fired);
    /// a brief grace period covers a termination that races with cancellation. If no
    /// result is ever produced (pure Ctrl+C), the stream ended gracefully — exit 0.
    /// </summary>
    /// <param name="exitTask">The task whose result is the intended exit code, set by OnStreamTerminated or the Closed handler.</param>
    /// <returns>A task that resolves to the process exit code (0 for graceful exit or pure Ctrl+C, non-zero for error).</returns>
    internal static async Task<int> ResolveStreamExitCodeAsync(Task<int> exitTask)
    {
        if (exitTask.IsCompletedSuccessfully)
            return exitTask.Result;

        var completed = await Task.WhenAny(exitTask, Task.Delay(ExitGracePeriod));
        if (ReferenceEquals(completed, exitTask) && exitTask.IsCompletedSuccessfully)
            return exitTask.Result;

        return 0;
    }
}
