namespace NATS.Server.Core.Tests;

// Go reference: server/client.go (stc channel, stall gate backpressure)

public class StallGateTests
{
    [Fact]
    public void Stall_gate_activates_at_threshold()
    {
        // Go reference: server/client.go stalledRoute — stalls at 75% capacity
        var gate = new NatsClient.StallGate(maxPending: 1000);

        gate.IsStalled.ShouldBeFalse();

        gate.UpdatePending(750); // 75% = threshold
        gate.IsStalled.ShouldBeTrue();

        gate.UpdatePending(500); // below threshold — releases
        gate.IsStalled.ShouldBeFalse();
    }

    [Fact]
    public async Task Stall_gate_blocks_producer()
    {
        // Go reference: server/client.go stc channel blocks sends
        var gate = new NatsClient.StallGate(maxPending: 100);
        gate.UpdatePending(80); // stalled — 80% > 75%

        // Use a TCS to signal that the producer has entered WaitAsync
        var entered = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var released = false;
        var task = Task.Run(async () =>
        {
            entered.SetResult();
            await gate.WaitAsync(TimeSpan.FromSeconds(5));
            released = true;
        });

        // Wait until the producer has reached WaitAsync before asserting
        await entered.Task;
        released.ShouldBeFalse(); // still blocked

        gate.UpdatePending(50); // below threshold — releases

        await task;
        released.ShouldBeTrue();
    }

    [Fact]
    public async Task Stall_gate_timeout_returns_false()
    {
        // Go reference: server/client.go stc timeout → close as slow consumer
        var gate = new NatsClient.StallGate(maxPending: 100);
        gate.UpdatePending(80); // stalled

        var result = await gate.WaitAsync(TimeSpan.FromMilliseconds(50));
        result.ShouldBeFalse(); // timed out, not released
    }

    [Fact]
    public void Stall_gate_not_stalled_below_threshold()
    {
        // Go reference: server/client.go — no stall when below threshold
        var gate = new NatsClient.StallGate(maxPending: 1000);

        gate.UpdatePending(100); // well below 75%
        gate.IsStalled.ShouldBeFalse();

        gate.UpdatePending(749); // just below 75%
        gate.IsStalled.ShouldBeFalse();
    }

    [Fact]
    public async Task Stall_gate_wait_when_not_stalled_returns_immediately()
    {
        // Go reference: server/client.go — no stall, immediate return
        var gate = new NatsClient.StallGate(maxPending: 1000);

        var result = await gate.WaitAsync(TimeSpan.FromSeconds(1));
        result.ShouldBeTrue(); // immediately released — not stalled
    }

    [Fact]
    public void Stall_gate_release_is_idempotent()
    {
        // Release when not stalled should not throw
        var gate = new NatsClient.StallGate(maxPending: 100);

        Should.NotThrow(() => gate.Release());
        Should.NotThrow(() => gate.Release());
    }
}
