using NATS.Server.JetStream.Consumers;

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

/// <summary>
/// Tests for enhanced AckProcessor with RedeliveryTracker integration.
/// Go reference: consumer.go:4854 (processInboundAcks).
/// </summary>
public class AckProcessorEnhancedTests
{
    [Fact]
    public void ProcessAck_removes_from_pending()
    {
        var tracker = new RedeliveryTracker(maxDeliveries: 5, ackWaitMs: 30000);
        var processor = new AckProcessor(tracker);

        processor.Register(1, "deliver.subj");
        processor.PendingCount.ShouldBe(1);

        processor.ProcessAck(1);
        processor.PendingCount.ShouldBe(0);
    }

    [Fact]
    public void ProcessNak_schedules_redelivery()
    {
        var tracker = new RedeliveryTracker(maxDeliveries: 5, ackWaitMs: 30000);
        var processor = new AckProcessor(tracker);

        processor.Register(1, "deliver.subj");
        processor.ProcessNak(1, delayMs: 500);

        processor.PendingCount.ShouldBe(1); // still pending until redelivered
    }

    [Fact]
    public void ProcessTerm_removes_permanently()
    {
        var tracker = new RedeliveryTracker(maxDeliveries: 5, ackWaitMs: 30000);
        var processor = new AckProcessor(tracker);

        processor.Register(1, "deliver.subj");
        processor.ProcessTerm(1);

        processor.PendingCount.ShouldBe(0);
        processor.TerminatedCount.ShouldBe(1);
    }

    [Fact]
    public void ProcessProgress_resets_deadline_to_full_ack_wait()
    {
        // Go: consumer.go — processAckProgress (+WPI): resets deadline to UtcNow + ackWait
        // Verify the invariant: after ProcessProgress, the deadline is strictly in the future
        // by at least (ackWait - epsilon) milliseconds, without relying on wall-clock delays.
        var ackWaitMs = 1000;
        var tracker = new RedeliveryTracker(maxDeliveries: 5, ackWaitMs: ackWaitMs);
        var processor = new AckProcessor(tracker);

        processor.Register(1, "deliver.subj");

        var before = DateTimeOffset.UtcNow;
        processor.ProcessProgress(1);
        var after = DateTimeOffset.UtcNow;

        var deadline = processor.GetDeadline(1);

        // Deadline must be at least (before + ackWait) and at most (after + ackWait + epsilon)
        deadline.ShouldBeGreaterThanOrEqualTo(before.AddMilliseconds(ackWaitMs));
        deadline.ShouldBeLessThanOrEqualTo(after.AddMilliseconds(ackWaitMs + 50));
    }

    [Fact]
    public void MaxAckPending_blocks_new_registrations()
    {
        var tracker = new RedeliveryTracker(maxDeliveries: 5, ackWaitMs: 30000);
        var processor = new AckProcessor(tracker, maxAckPending: 2);

        processor.Register(1, "d.1");
        processor.Register(2, "d.2");
        processor.CanRegister().ShouldBeFalse();
    }

    [Fact]
    public void CanRegister_true_when_unlimited()
    {
        var tracker = new RedeliveryTracker(maxDeliveries: 5, ackWaitMs: 30000);
        var processor = new AckProcessor(tracker); // maxAckPending=0 means unlimited

        processor.Register(1, "d.1");
        processor.CanRegister().ShouldBeTrue();
    }

    [Fact]
    public void ParseAckType_identifies_all_types()
    {
        AckProcessor.ParseAckType("+ACK"u8).ShouldBe(AckType.Ack);
        AckProcessor.ParseAckType("-NAK"u8).ShouldBe(AckType.Nak);
        AckProcessor.ParseAckType("+TERM"u8).ShouldBe(AckType.Term);
        AckProcessor.ParseAckType("+WPI"u8).ShouldBe(AckType.Progress);
    }

    [Fact]
    public void ParseAckType_returns_unknown_for_invalid()
    {
        AckProcessor.ParseAckType("GARBAGE"u8).ShouldBe(AckType.Unknown);
        AckProcessor.ParseAckType(""u8).ShouldBe(AckType.Unknown);
    }

    [Fact]
    public void GetDeadline_returns_min_for_unknown_sequence()
    {
        var tracker = new RedeliveryTracker(maxDeliveries: 5, ackWaitMs: 1000);
        var processor = new AckProcessor(tracker);

        // Unknown sequence should return DateTimeOffset.MinValue
        processor.GetDeadline(999).ShouldBe(DateTimeOffset.MinValue);
    }
}
