using System.Threading.Tasks;
using ZB.MOM.WW.ScadaBridge.CLI.Commands;

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

/// <summary>
/// Regression tests for the testable pieces of <c>DebugCommands.StreamDebugAsync</c>:
/// CLI-010 (Ctrl+C during connect misreported as a connection failure) and
/// CLI-012 (non-deterministic exit code after stream termination).
/// </summary>
public class DebugStreamTests
{
    // --- CLI-010: connect-failure classification --------------------------------------

    [Fact]
    public void ClassifyConnectFailure_OperationCanceled_IsTreatedAsCancellation()
    {
        // Ctrl+C while StartAsync is still establishing the connection throws
        // OperationCanceledException — this is a graceful cancellation, not a failure.
        var result = DebugStreamHelpers.ClassifyConnectFailure(
            new OperationCanceledException(), cancellationRequested: true);

        Assert.True(result.IsCancellation);
        Assert.Equal(0, result.ExitCode);
    }

    [Fact]
    public void ClassifyConnectFailure_TaskCanceled_WhenCancelRequested_IsCancellation()
    {
        var result = DebugStreamHelpers.ClassifyConnectFailure(
            new TaskCanceledException(), cancellationRequested: true);

        Assert.True(result.IsCancellation);
        Assert.Equal(0, result.ExitCode);
    }

    [Fact]
    public void ClassifyConnectFailure_RealException_IsConnectionFailure()
    {
        var result = DebugStreamHelpers.ClassifyConnectFailure(
            new HttpRequestException("connection refused"), cancellationRequested: false);

        Assert.False(result.IsCancellation);
        Assert.Equal(1, result.ExitCode);
    }

    [Fact]
    public void ClassifyConnectFailure_CanceledExceptionButNoCancelRequested_IsConnectionFailure()
    {
        // A cancellation that did not originate from the user (e.g. a server-side abort)
        // is still a real connection failure.
        var result = DebugStreamHelpers.ClassifyConnectFailure(
            new OperationCanceledException(), cancellationRequested: false);

        Assert.False(result.IsCancellation);
        Assert.Equal(1, result.ExitCode);
    }

    // --- CLI-012: deterministic exit-code resolution ----------------------------------

    [Fact]
    public async Task ResolveStreamExitCodeAsync_TerminationResultSet_PrefersThatResult()
    {
        // OnStreamTerminated set exitTcs to 1 — that must win even on the Ctrl+C path.
        var tcs = new TaskCompletionSource<int>();
        tcs.SetResult(1);

        var code = await DebugStreamHelpers.ResolveStreamExitCodeAsync(tcs.Task);

        Assert.Equal(1, code);
    }

    [Fact]
    public async Task ResolveStreamExitCodeAsync_NoResult_ReturnsZero()
    {
        // Pure Ctrl+C: exitTcs never completed — graceful shutdown, exit 0.
        var tcs = new TaskCompletionSource<int>();

        var code = await DebugStreamHelpers.ResolveStreamExitCodeAsync(tcs.Task);

        Assert.Equal(0, code);
    }

    [Fact]
    public async Task ResolveStreamExitCodeAsync_ResultArrivesDuringGrace_IsObserved()
    {
        // A stream termination racing with Ctrl+C: the result lands shortly after the
        // wait was cancelled. The grace period must let it be observed deterministically.
        var tcs = new TaskCompletionSource<int>();
        _ = Task.Run(async () =>
        {
            await Task.Delay(20);
            tcs.TrySetResult(1);
        });

        var code = await DebugStreamHelpers.ResolveStreamExitCodeAsync(tcs.Task);

        Assert.Equal(1, code);
    }
}
