using NATS.Server.JetStream.MirrorSource;
using NATS.Server.JetStream.Models;
using NATS.Server.JetStream.Storage;
using Shouldly;

namespace NATS.Server.JetStream.Tests.JetStream.Streams;

// Go reference: server/stream.go:3474-3720 (setupSourceConsumer, trySetupSourceConsumer)
// Go reference: server/stream.go:3531 ($JS.API.CONSUMER.CREATE.{sourceName})
// Go reference: server/stream.go:3573-3598 (DeliverPolicy, FilterSubject, AckPolicy assignment)

public class SourceConsumerSetupTests
{
    // -------------------------------------------------------------------------
    // BuildConsumerCreateRequest — FilterSubject
    // Go reference: server/stream.go:3597-3598
    // -------------------------------------------------------------------------

    [Fact]
    public void BuildConsumerCreateRequest_sets_filter_subject()
    {
        var target = new MemStore();
        var coordinator = new SourceCoordinator(target, new StreamSourceConfig
        {
            Name = "SOURCE",
            FilterSubject = "orders.>",
        });

        var req = coordinator.BuildConsumerCreateRequest();

        req.FilterSubject.ShouldBe("orders.>");
    }

    [Fact]
    public void BuildConsumerCreateRequest_no_filter_leaves_null()
    {
        var target = new MemStore();
        var coordinator = new SourceCoordinator(target, new StreamSourceConfig
        {
            Name = "SOURCE",
        });

        var req = coordinator.BuildConsumerCreateRequest();

        req.FilterSubject.ShouldBeNull();
    }

    // -------------------------------------------------------------------------
    // BuildConsumerCreateRequest — DeliverPolicy
    // Go reference: server/stream.go:3573-3582
    // -------------------------------------------------------------------------

    [Fact]
    public void BuildConsumerCreateRequest_starts_from_beginning_when_no_progress()
    {
        var target = new MemStore();
        var coordinator = new SourceCoordinator(target, new StreamSourceConfig
        {
            Name = "SOURCE",
        });

        // LastOriginSequence is 0 — no messages have been processed yet
        coordinator.LastOriginSequence.ShouldBe(0UL);

        var req = coordinator.BuildConsumerCreateRequest();

        req.DeliverPolicy.ShouldBe(DeliverPolicy.All);
    }

    [Fact]
    public async Task BuildConsumerCreateRequest_resumes_from_last_sequence()
    {
        var target = new MemStore();
        var coordinator = new SourceCoordinator(target, new StreamSourceConfig
        {
            Name = "SOURCE",
        });

        // Advance LastOriginSequence by processing messages
        await coordinator.OnOriginAppendAsync(MakeMessage(3, "orders.created", "a"), default);
        await coordinator.OnOriginAppendAsync(MakeMessage(7, "orders.updated", "b"), default);

        coordinator.LastOriginSequence.ShouldBe(7UL);

        var req = coordinator.BuildConsumerCreateRequest();

        req.DeliverPolicy.ShouldBe(DeliverPolicy.ByStartSequence);
        req.OptStartSeq.ShouldBe(8UL); // LastOriginSequence + 1
    }

    // -------------------------------------------------------------------------
    // BuildConsumerCreateRequest — AckPolicy
    // Go reference: server/stream.go:3586
    // -------------------------------------------------------------------------

    [Fact]
    public void BuildConsumerCreateRequest_sets_ack_none()
    {
        var target = new MemStore();
        var coordinator = new SourceCoordinator(target, new StreamSourceConfig
        {
            Name = "SOURCE",
        });

        var req = coordinator.BuildConsumerCreateRequest();

        req.AckPolicy.ShouldBe(AckPolicy.None);
    }

    // -------------------------------------------------------------------------
    // BuildConsumerCreateRequest — Push + FlowControl
    // Go reference: server/stream.go:3589-3592
    // -------------------------------------------------------------------------

    [Fact]
    public void BuildConsumerCreateRequest_enables_push_and_flow_control()
    {
        var target = new MemStore();
        var coordinator = new SourceCoordinator(target, new StreamSourceConfig
        {
            Name = "SOURCE",
        });

        var req = coordinator.BuildConsumerCreateRequest();

        req.Push.ShouldBeTrue();
        req.FlowControl.ShouldBeTrue();
    }

    // -------------------------------------------------------------------------
    // BuildConsumerCreateRequest — HeartbeatMs
    // Go reference: server/stream.go:3593 — sourceHealthHB = 1 * time.Second
    // -------------------------------------------------------------------------

    [Fact]
    public void BuildConsumerCreateRequest_sets_heartbeat()
    {
        var target = new MemStore();
        var coordinator = new SourceCoordinator(target, new StreamSourceConfig
        {
            Name = "SOURCE",
        });

        var req = coordinator.BuildConsumerCreateRequest();

        // HeartbeatInterval is 1 second = 1000 ms
        req.HeartbeatMs.ShouldBe(1000);
    }

    // -------------------------------------------------------------------------
    // BuildConsumerCreateSubject
    // Go reference: server/stream.go:3531
    // -------------------------------------------------------------------------

    [Fact]
    public void BuildConsumerCreateSubject_formats_correctly()
    {
        var target = new MemStore();
        var coordinator = new SourceCoordinator(target, new StreamSourceConfig
        {
            Name = "MY_SOURCE",
        });

        var subject = coordinator.BuildConsumerCreateSubject();

        subject.ShouldBe("$JS.API.CONSUMER.CREATE.MY_SOURCE");
    }

    // -------------------------------------------------------------------------
    // GetDeliverySequence
    // Go reference: server/stream.go si.dseq field
    // -------------------------------------------------------------------------

    [Fact]
    public void GetDeliverySequence_starts_at_zero()
    {
        var target = new MemStore();
        var coordinator = new SourceCoordinator(target, new StreamSourceConfig
        {
            Name = "SOURCE",
        });

        coordinator.GetDeliverySequence.ShouldBe(0UL);
    }

    [Fact]
    public async Task GetDeliverySequence_increments_after_processing()
    {
        var target = new MemStore();
        var coordinator = new SourceCoordinator(target, new StreamSourceConfig
        {
            Name = "SOURCE",
        });

        await coordinator.OnOriginAppendAsync(MakeMessage(1, "orders.created", "x"), default);
        await coordinator.OnOriginAppendAsync(MakeMessage(2, "orders.updated", "y"), default);
        await coordinator.OnOriginAppendAsync(MakeMessage(3, "orders.shipped", "z"), default);

        coordinator.GetDeliverySequence.ShouldBe(3UL);
    }

    // -------------------------------------------------------------------------
    // Helpers
    // -------------------------------------------------------------------------

    private static StoredMessage MakeMessage(ulong seq, string subject, string payload) => new()
    {
        Sequence = seq,
        Subject = subject,
        Payload = System.Text.Encoding.UTF8.GetBytes(payload),
        TimestampUtc = DateTime.UtcNow,
    };
}
