using NATS.Server.JetStream;
using NATS.Server.JetStream.Models;

namespace NATS.Server.JetStream.Tests.JetStream.Consumers;

/// <summary>
/// Tests for consumer pause/resume with auto-resume timer.
/// Go reference: consumer.go (pause/resume).
/// </summary>
public class ConsumerPauseResumeTests
{
    private static ConsumerManager CreateManager() => new();

    private static void CreateConsumer(ConsumerManager mgr, string stream, string name)
    {
        mgr.CreateOrUpdate(stream, new ConsumerConfig { DurableName = name });
    }

    [Fact]
    public void Pause_with_deadline_sets_paused()
    {
        var mgr = CreateManager();
        CreateConsumer(mgr, "test-stream", "test-consumer");

        var until = DateTime.UtcNow.AddSeconds(5);
        mgr.Pause("test-stream", "test-consumer", until);

        mgr.IsPaused("test-stream", "test-consumer").ShouldBeTrue();
        mgr.GetPauseUntil("test-stream", "test-consumer").ShouldBe(until);
    }

    [Fact]
    public void Resume_clears_pause()
    {
        var mgr = CreateManager();
        CreateConsumer(mgr, "test-stream", "test-consumer");

        mgr.Pause("test-stream", "test-consumer", DateTime.UtcNow.AddSeconds(5));
        mgr.Resume("test-stream", "test-consumer");

        mgr.IsPaused("test-stream", "test-consumer").ShouldBeFalse();
        mgr.GetPauseUntil("test-stream", "test-consumer").ShouldBeNull();
    }

    [Fact]
    public async Task Pause_auto_resumes_after_deadline()
    {
        var mgr = CreateManager();
        CreateConsumer(mgr, "test-stream", "test-consumer");

        // Use a semaphore to synchronize on the actual timer callback rather than a blind delay.
        using var resumed = new SemaphoreSlim(0, 1);
        mgr.OnAutoResumed += (_, _) => resumed.Release();

        mgr.Pause("test-stream", "test-consumer", DateTime.UtcNow.AddMilliseconds(100));

        var signalled = await resumed.WaitAsync(TimeSpan.FromSeconds(5));
        signalled.ShouldBeTrue("auto-resume timer did not fire within 5 seconds");

        mgr.IsPaused("test-stream", "test-consumer").ShouldBeFalse();
    }

    [Fact]
    public void IsPaused_returns_false_for_unknown_consumer()
    {
        var mgr = CreateManager();
        mgr.IsPaused("unknown", "unknown").ShouldBeFalse();
    }

    [Fact]
    public void GetPauseUntil_returns_null_for_unknown_consumer()
    {
        var mgr = CreateManager();
        mgr.GetPauseUntil("unknown", "unknown").ShouldBeNull();
    }

    [Fact]
    public void Resume_returns_false_for_unknown_consumer()
    {
        var mgr = CreateManager();
        mgr.Resume("unknown", "unknown").ShouldBeFalse();
    }

    [Fact]
    public void Pause_returns_false_for_unknown_consumer()
    {
        var mgr = CreateManager();
        mgr.Pause("unknown", "unknown", DateTime.UtcNow.AddSeconds(5)).ShouldBeFalse();
    }

    [Fact]
    public void IsPaused_auto_resumes_expired_deadline()
    {
        var mgr = CreateManager();
        CreateConsumer(mgr, "test-stream", "c1");

        // Pause with a deadline in the past
        mgr.Pause("test-stream", "c1", DateTime.UtcNow.AddMilliseconds(-100));

        // IsPaused should detect the expired deadline and auto-resume
        mgr.IsPaused("test-stream", "c1").ShouldBeFalse();
    }
}
