// Tests for MqttQoS1Tracker (Gap 6.3 — JetStream-backed QoS 1/2 tracking).
// Go reference: golang/nats-server/server/mqtt.go mqttProcessPub (~line 1200).

using NATS.Server.Mqtt;
using Shouldly;

namespace NATS.Server.Mqtt.Tests.Mqtt;

public sealed class MqttQoSTrackingTests
{
    // ── QoS 1 Tracker ────────────────────────────────────────────────────────

    [Fact]
    public void Register_assigns_packet_id()
    {
        // Go reference: server/mqtt.go mqttProcessPub — assigns non-zero packet ID for QoS 1
        var tracker = new MqttQoS1Tracker();

        var id = tracker.Register("sensors/temp", [0x01, 0x02]);

        id.ShouldNotBe((ushort)0);
    }

    [Fact]
    public void Register_increments_packet_id()
    {
        // Go reference: server/mqtt.go — each outgoing QoS 1 message gets a unique packet ID
        var tracker = new MqttQoS1Tracker();

        var id1 = tracker.Register("sensors/temp", [0x01]);
        var id2 = tracker.Register("sensors/humidity", [0x02]);

        id1.ShouldNotBe(id2);
    }

    [Fact]
    public void Acknowledge_removes_pending()
    {
        // Go reference: server/mqtt.go mqttProcessPubAck — removes message from pending set
        var tracker = new MqttQoS1Tracker();
        var id = tracker.Register("sensors/temp", [0xAB]);

        tracker.PendingCount.ShouldBe(1);
        var removed = tracker.Acknowledge(id);

        removed.ShouldNotBeNull();
        tracker.PendingCount.ShouldBe(0);
    }

    [Fact]
    public void Acknowledge_returns_null_for_unknown()
    {
        // Go reference: server/mqtt.go — PUBACK for unknown packet ID is silently ignored
        var tracker = new MqttQoS1Tracker();

        var result = tracker.Acknowledge(9999);

        result.ShouldBeNull();
    }

    [Fact]
    public void PendingCount_reflects_current_state()
    {
        // Register 3 messages, acknowledge 1, expect count of 2
        var tracker = new MqttQoS1Tracker();
        var id1 = tracker.Register("a/b", [1]);
        tracker.Register("c/d", [2]);
        tracker.Register("e/f", [3]);

        tracker.Acknowledge(id1);

        tracker.PendingCount.ShouldBe(2);
    }

    [Fact]
    public void IsPending_true_for_registered()
    {
        // Go reference: server/mqtt.go — registered QoS 1 message is in the pending set
        var tracker = new MqttQoS1Tracker();
        var id = tracker.Register("topic/x", [0xFF]);

        tracker.IsPending(id).ShouldBeTrue();
    }

    [Fact]
    public void IsPending_false_after_acknowledge()
    {
        // Go reference: server/mqtt.go — message is removed from pending after PUBACK
        var tracker = new MqttQoS1Tracker();
        var id = tracker.Register("topic/x", [0xFF]);
        tracker.Acknowledge(id);

        tracker.IsPending(id).ShouldBeFalse();
    }

    [Fact]
    public void GetPendingForRedelivery_returns_all_pending()
    {
        // Go reference: server/mqtt.go reconnect path — all unacked messages are redelivered
        var tracker = new MqttQoS1Tracker();
        tracker.Register("a", [1]);
        tracker.Register("b", [2]);
        tracker.Register("c", [3]);

        var pending = tracker.GetPendingForRedelivery();

        pending.Count.ShouldBe(3);
    }

    [Fact]
    public void GetPendingForRedelivery_increments_delivery_count()
    {
        // Go reference: server/mqtt.go reconnect redelivery — DUP flag set, delivery count increments
        var tracker = new MqttQoS1Tracker();
        tracker.Register("test/topic", [0xDE, 0xAD]);

        var pending = tracker.GetPendingForRedelivery();

        pending[0].DeliveryCount.ShouldBe(2);
    }

    [Fact]
    public void Clear_removes_all_pending()
    {
        // Go reference: server/mqtt.go session cleanup — all pending messages discarded on clean session
        var tracker = new MqttQoS1Tracker();
        tracker.Register("x", [1]);
        tracker.Register("y", [2]);
        tracker.Register("z", [3]);

        tracker.Clear();

        tracker.PendingCount.ShouldBe(0);
    }
}
