using NATS.Server.JetStream.Consumers;

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

/// <summary>
/// Tests for the new PriorityQueue-based RedeliveryTracker features.
/// Go reference: consumer.go (rdq redelivery queue).
/// </summary>
public class RedeliveryTrackerPriorityQueueTests
{
    [Fact]
    public void Schedule_and_get_due_returns_expired()
    {
        var tracker = new RedeliveryTracker(maxDeliveries: 5, ackWaitMs: 1000);
        var past = DateTimeOffset.UtcNow.AddMilliseconds(-100);

        tracker.Schedule(1, past);
        tracker.Schedule(2, DateTimeOffset.UtcNow.AddSeconds(60)); // future

        var due = tracker.GetDue(DateTimeOffset.UtcNow).ToList();
        due.Count.ShouldBe(1);
        due[0].ShouldBe(1UL);
    }

    [Fact]
    public void Acknowledge_removes_from_queue()
    {
        var tracker = new RedeliveryTracker(maxDeliveries: 5, ackWaitMs: 1000);
        tracker.Schedule(1, DateTimeOffset.UtcNow.AddMilliseconds(-100));

        tracker.Acknowledge(1);

        var due = tracker.GetDue(DateTimeOffset.UtcNow).ToList();
        due.ShouldBeEmpty();
    }

    [Fact]
    public void IsMaxDeliveries_returns_true_at_threshold()
    {
        var tracker = new RedeliveryTracker(maxDeliveries: 3, ackWaitMs: 1000);

        tracker.IncrementDeliveryCount(1);
        tracker.IncrementDeliveryCount(1);
        tracker.IsMaxDeliveries(1).ShouldBeFalse();

        tracker.IncrementDeliveryCount(1);
        tracker.IsMaxDeliveries(1).ShouldBeTrue();
    }

    [Fact]
    public void Backoff_schedule_uses_delivery_count()
    {
        var backoff = new long[] { 100, 500, 2000 };
        var tracker = new RedeliveryTracker(maxDeliveries: 10, ackWaitMs: 1000, backoffMs: backoff);

        // First redeliver: 100ms
        var delay1 = tracker.GetBackoffDelay(deliveryCount: 1);
        delay1.ShouldBe(100L);

        // Second: 500ms
        var delay2 = tracker.GetBackoffDelay(deliveryCount: 2);
        delay2.ShouldBe(500L);

        // Beyond schedule: use last value
        var delay4 = tracker.GetBackoffDelay(deliveryCount: 4);
        delay4.ShouldBe(2000L);
    }

    [Fact]
    public void GetDue_returns_in_deadline_order()
    {
        var tracker = new RedeliveryTracker(maxDeliveries: 5, ackWaitMs: 1000);
        var now = DateTimeOffset.UtcNow;

        tracker.Schedule(3, now.AddMilliseconds(-300));
        tracker.Schedule(1, now.AddMilliseconds(-100));
        tracker.Schedule(2, now.AddMilliseconds(-200));

        var due = tracker.GetDue(now).ToList();
        due.Count.ShouldBe(3);
        due[0].ShouldBe(3UL); // earliest deadline first
        due[1].ShouldBe(2UL);
        due[2].ShouldBe(1UL);
    }

    [Fact]
    public void GetBackoffDelay_with_no_backoff_returns_ackWait()
    {
        var tracker = new RedeliveryTracker(maxDeliveries: 5, ackWaitMs: 2000);
        tracker.GetBackoffDelay(1).ShouldBe(2000L);
        tracker.GetBackoffDelay(5).ShouldBe(2000L);
    }

    [Fact]
    public void IncrementDeliveryCount_for_untracked_seq_starts_at_one()
    {
        var tracker = new RedeliveryTracker(maxDeliveries: 5, ackWaitMs: 1000);
        tracker.IncrementDeliveryCount(42);
        // First increment should make count = 1, so maxDeliveries=5 means not max yet
        tracker.IsMaxDeliveries(42).ShouldBeFalse();
    }

    [Fact]
    public void Acknowledge_also_clears_delivery_count()
    {
        var tracker = new RedeliveryTracker(maxDeliveries: 3, ackWaitMs: 1000);
        tracker.IncrementDeliveryCount(1);
        tracker.IncrementDeliveryCount(1);
        tracker.Acknowledge(1);
        // After ack, delivery count should be cleared
        tracker.IsMaxDeliveries(1).ShouldBeFalse();
    }
}
