// Go ref: TestJetStreamClusterXxx — jetstream_cluster_long_test.go
// Covers: high-volume publish/consume cycles, many sequential fetches, many consumers,
// many streams, repeated publish-ack-fetch cycles, stepdowns during publishing,
// alternating publish+stepdown, create-publish-delete sequences, ack tracking across
// failovers, batch-1 iteration, mixed multi-stream operations, rapid meta stepdowns,
// large R1 message volumes, max-messages stream limits, consumer pending correctness.
using System.Text;
using NATS.Server.JetStream.Api;
using NATS.Server.JetStream.Cluster;
using NATS.Server.JetStream.Models;
using NATS.Server.TestUtilities;

namespace NATS.Server.JetStream.Tests.JetStream.Cluster;

/// <summary>
/// Long-running JetStream cluster tests covering high-volume scenarios,
/// repeated failover cycles, many-stream/many-consumer environments, and
/// limit enforcement under sustained load.
/// Ported from Go jetstream_cluster_long_test.go.
/// All tests are marked [Trait("Category", "LongRunning")].
/// </summary>
public class JsClusterLongRunningTests
{
    // ---------------------------------------------------------------
    // Go ref: TestJetStreamClusterLong5000MessagesR3 — jetstream_cluster_long_test.go
    // ---------------------------------------------------------------

    [Fact]
    [Trait("Category", "LongRunning")]
    public async Task Five_thousand_messages_in_R3_stream_maintain_consistency()
    {
        // Go ref: TestJetStreamClusterLong5000MessagesR3 — jetstream_cluster_long_test.go
        await using var cluster = await JetStreamClusterFixture.StartAsync(3);

        await cluster.CreateStreamAsync("LONG5K", ["long5k.>"], replicas: 3);

        for (var i = 0; i < 5000; i++)
        {
            var ack = await cluster.PublishAsync("long5k.data", $"msg-{i}");
            ack.ErrorCode.ShouldBeNull();
            ack.Seq.ShouldBe((ulong)(i + 1));
        }

        var state = await cluster.GetStreamStateAsync("LONG5K");
        state.Messages.ShouldBe(5000UL);
        state.FirstSeq.ShouldBe(1UL);
        state.LastSeq.ShouldBe(5000UL);
    }

    // ---------------------------------------------------------------
    // Go ref: TestJetStreamClusterLong100SequentialFetchesOf50 — jetstream_cluster_long_test.go
    // ---------------------------------------------------------------

    [Fact]
    [Trait("Category", "LongRunning")]
    public async Task One_hundred_sequential_fetches_of_fifty_messages_each()
    {
        // Go ref: TestJetStreamClusterLong100SequentialFetchesOf50 — jetstream_cluster_long_test.go
        await using var cluster = await JetStreamClusterFixture.StartAsync(3);

        await cluster.CreateStreamAsync("SEQFETCH", ["sf.>"], replicas: 3);
        await cluster.CreateConsumerAsync("SEQFETCH", "batcher", filterSubject: "sf.>");

        // Pre-publish 5000 messages
        for (var i = 0; i < 5000; i++)
            await cluster.PublishAsync("sf.event", $"msg-{i}");

        var totalFetched = 0;
        for (var batch = 0; batch < 100; batch++)
        {
            var result = await cluster.FetchAsync("SEQFETCH", "batcher", 50);
            result.Messages.Count.ShouldBe(50);
            totalFetched += result.Messages.Count;

            // Verify sequences are contiguous within each batch
            for (var j = 1; j < result.Messages.Count; j++)
                result.Messages[j].Sequence.ShouldBe(result.Messages[j - 1].Sequence + 1);
        }

        totalFetched.ShouldBe(5000);
    }

    // ---------------------------------------------------------------
    // Go ref: TestJetStreamClusterLong50ConsumersOnSameStream — jetstream_cluster_long_test.go
    // ---------------------------------------------------------------

    [Fact]
    [Trait("Category", "LongRunning")]
    public async Task Fifty_consumers_on_same_stream_all_see_all_messages()
    {
        // Go ref: TestJetStreamClusterLong50ConsumersOnSameStream — jetstream_cluster_long_test.go
        await using var cluster = await JetStreamClusterFixture.StartAsync(3);

        await cluster.CreateStreamAsync("FIFTYCONSUMERS", ["fc.>"], replicas: 3);

        for (var i = 0; i < 100; i++)
            await cluster.PublishAsync("fc.event", $"msg-{i}");

        for (var c = 0; c < 50; c++)
            await cluster.CreateConsumerAsync("FIFTYCONSUMERS", $"cons{c}", filterSubject: "fc.>");

        // Each consumer should see all 100 messages independently
        for (var c = 0; c < 50; c++)
        {
            var batch = await cluster.FetchAsync("FIFTYCONSUMERS", $"cons{c}", 100);
            batch.Messages.Count.ShouldBe(100);
        }
    }

    // ---------------------------------------------------------------
    // Go ref: TestJetStreamClusterLong20StreamsIn5NodeCluster — jetstream_cluster_long_test.go
    // ---------------------------------------------------------------

    [Fact]
    [Trait("Category", "LongRunning")]
    public async Task Twenty_streams_in_five_node_cluster_are_independent()
    {
        // Go ref: TestJetStreamClusterLong20StreamsIn5NodeCluster — jetstream_cluster_long_test.go
        await using var cluster = await JetStreamClusterFixture.StartAsync(5);

        for (var i = 0; i < 20; i++)
            await cluster.CreateStreamAsync($"IND{i}", [$"ind{i}.>"], replicas: 3);

        // Publish to each stream
        for (var i = 0; i < 20; i++)
            for (var j = 0; j < 10; j++)
                await cluster.PublishAsync($"ind{i}.event", $"stream{i}-msg{j}");

        // Verify each stream is independent
        for (var i = 0; i < 20; i++)
        {
            var state = await cluster.GetStreamStateAsync($"IND{i}");
            state.Messages.ShouldBe(10UL);
        }

        var accountInfo = await cluster.RequestAsync(JetStreamApiSubjects.Info, "{}");
        accountInfo.AccountInfo!.Streams.ShouldBe(20);
    }

    // ---------------------------------------------------------------
    // Go ref: TestJetStreamClusterLongPublishAckFetchCycle100Times — jetstream_cluster_long_test.go
    // ---------------------------------------------------------------

    [Fact]
    [Trait("Category", "LongRunning")]
    public async Task Publish_ack_fetch_cycle_repeated_100_times()
    {
        // Go ref: TestJetStreamClusterLongPublishAckFetchCycle100Times — jetstream_cluster_long_test.go
        await using var cluster = await JetStreamClusterFixture.StartAsync(3);

        await cluster.CreateStreamAsync("PAFCYCLE", ["paf.>"], replicas: 3);
        await cluster.CreateConsumerAsync("PAFCYCLE", "cycler", filterSubject: "paf.>",
            ackPolicy: AckPolicy.All);

        for (var cycle = 0; cycle < 100; cycle++)
        {
            // Publish one message per cycle
            var ack = await cluster.PublishAsync("paf.event", $"cycle-{cycle}");
            ack.ErrorCode.ShouldBeNull();

            // Fetch one message
            var batch = await cluster.FetchAsync("PAFCYCLE", "cycler", 1);
            batch.Messages.Count.ShouldBe(1);
            batch.Messages[0].Sequence.ShouldBe(ack.Seq);

            // Ack it
            cluster.AckAll("PAFCYCLE", "cycler", ack.Seq);
        }

        var finalState = await cluster.GetStreamStateAsync("PAFCYCLE");
        finalState.Messages.ShouldBe(100UL);
    }

    // ---------------------------------------------------------------
    // Go ref: TestJetStreamClusterLong10StepdownsDuringPublish — jetstream_cluster_long_test.go
    // ---------------------------------------------------------------

    [Fact]
    [Trait("Category", "LongRunning")]
    public async Task Ten_stepdowns_during_continuous_publish_preserve_all_messages()
    {
        // Go ref: TestJetStreamClusterLong10StepdownsDuringPublish — jetstream_cluster_long_test.go
        await using var cluster = await JetStreamClusterFixture.StartAsync(3);

        await cluster.CreateStreamAsync("STEPDURINGPUB", ["sdp.>"], replicas: 3);

        var totalPublished = 0;

        // Publish 50 messages per batch, then step down (10 iterations = 500 msgs + 10 stepdowns)
        for (var sd = 0; sd < 10; sd++)
        {
            for (var i = 0; i < 50; i++)
            {
                var ack = await cluster.PublishAsync("sdp.event", $"batch{sd}-msg{i}");
                ack.ErrorCode.ShouldBeNull();
                totalPublished++;
            }

            (await cluster.StepDownStreamLeaderAsync("STEPDURINGPUB")).Success.ShouldBeTrue();
        }

        var state = await cluster.GetStreamStateAsync("STEPDURINGPUB");
        state.Messages.ShouldBe((ulong)totalPublished);
        state.LastSeq.ShouldBe((ulong)totalPublished);
    }

    // ---------------------------------------------------------------
    // Go ref: TestJetStreamClusterLongAlternatingPublishAndStepdown — jetstream_cluster_long_test.go
    // ---------------------------------------------------------------

    [Fact]
    [Trait("Category", "LongRunning")]
    public async Task Alternating_publish_and_stepdown_20_iterations_preserves_monotonic_sequence()
    {
        // Go ref: TestJetStreamClusterLongAlternatingPublishAndStepdown — jetstream_cluster_long_test.go
        await using var cluster = await JetStreamClusterFixture.StartAsync(3);

        await cluster.CreateStreamAsync("ALTPUBSD", ["aps.>"], replicas: 3);

        var allSeqs = new List<ulong>();

        for (var iter = 0; iter < 20; iter++)
        {
            var ack = await cluster.PublishAsync("aps.event", $"iter-{iter}");
            ack.ErrorCode.ShouldBeNull();
            allSeqs.Add(ack.Seq);

            (await cluster.StepDownStreamLeaderAsync("ALTPUBSD")).Success.ShouldBeTrue();
        }

        // Verify strictly monotonically increasing sequences across all stepdowns
        for (var i = 1; i < allSeqs.Count; i++)
            allSeqs[i].ShouldBeGreaterThan(allSeqs[i - 1]);

        allSeqs[^1].ShouldBe(20UL);
    }

    // ---------------------------------------------------------------
    // Go ref: TestJetStreamClusterLongCreatePublishDelete20Streams — jetstream_cluster_long_test.go
    // ---------------------------------------------------------------

    [Fact]
    [Trait("Category", "LongRunning")]
    public async Task Create_publish_delete_20_streams_sequentially()
    {
        // Go ref: TestJetStreamClusterLongCreatePublishDelete20Streams — jetstream_cluster_long_test.go
        await using var cluster = await JetStreamClusterFixture.StartAsync(3);

        for (var i = 0; i < 20; i++)
        {
            var streamName = $"SEQ{i}";

            var create = await cluster.CreateStreamAsync(streamName, [$"seq{i}.>"], replicas: 3);
            create.Error.ShouldBeNull();

            for (var j = 0; j < 10; j++)
                await cluster.PublishAsync($"seq{i}.event", $"msg-{j}");

            var state = await cluster.GetStreamStateAsync(streamName);
            state.Messages.ShouldBe(10UL);

            var del = await cluster.RequestAsync($"{JetStreamApiSubjects.StreamDelete}{streamName}", "{}");
            del.Success.ShouldBeTrue();
        }

        // All streams deleted
        var accountInfo = await cluster.RequestAsync(JetStreamApiSubjects.Info, "{}");
        accountInfo.AccountInfo!.Streams.ShouldBe(0);
    }

    // ---------------------------------------------------------------
    // Go ref: TestJetStreamClusterLongConsumerAckAfter10Failovers — jetstream_cluster_long_test.go
    // ---------------------------------------------------------------

    [Fact]
    [Trait("Category", "LongRunning")]
    public async Task Consumer_ack_tracking_correct_after_ten_leader_failovers()
    {
        // Go ref: TestJetStreamClusterLongConsumerAckAfter10Failovers — jetstream_cluster_long_test.go
        await using var cluster = await JetStreamClusterFixture.StartAsync(3);

        await cluster.CreateStreamAsync("ACKFAIL", ["af.>"], replicas: 3);
        await cluster.CreateConsumerAsync("ACKFAIL", "tracker", filterSubject: "af.>",
            ackPolicy: AckPolicy.All);

        // Pre-publish 100 messages
        for (var i = 0; i < 100; i++)
            await cluster.PublishAsync("af.event", $"msg-{i}");

        // Fetch and ack in batches across 10 failovers
        var ackedThrough = 0UL;
        for (var failover = 0; failover < 10; failover++)
        {
            var batch = await cluster.FetchAsync("ACKFAIL", "tracker", 10);
            batch.Messages.Count.ShouldBe(10);

            var lastSeq = batch.Messages[^1].Sequence;
            cluster.AckAll("ACKFAIL", "tracker", lastSeq);
            ackedThrough = lastSeq;

            (await cluster.StepDownStreamLeaderAsync("ACKFAIL")).Success.ShouldBeTrue();
        }

        ackedThrough.ShouldBe(100UL);
    }

    // ---------------------------------------------------------------
    // Go ref: TestJetStreamClusterLongFetchBatch1Iterated500Times — jetstream_cluster_long_test.go
    // ---------------------------------------------------------------

    [Fact]
    [Trait("Category", "LongRunning")]
    public async Task Fetch_with_batch_1_iterated_500_times_reads_all_messages()
    {
        // Go ref: TestJetStreamClusterLongFetchBatch1Iterated500Times — jetstream_cluster_long_test.go
        await using var cluster = await JetStreamClusterFixture.StartAsync(3);

        await cluster.CreateStreamAsync("BATCH1ITER", ["b1i.>"], replicas: 3);
        await cluster.CreateConsumerAsync("BATCH1ITER", "one_at_a_time", filterSubject: "b1i.>");

        for (var i = 0; i < 500; i++)
            await cluster.PublishAsync("b1i.event", $"msg-{i}");

        var allSeqs = new List<ulong>();
        for (var i = 0; i < 500; i++)
        {
            var batch = await cluster.FetchAsync("BATCH1ITER", "one_at_a_time", 1);
            batch.Messages.Count.ShouldBe(1);
            allSeqs.Add(batch.Messages[0].Sequence);
        }

        // All 500 sequences read, strictly increasing
        allSeqs.Count.ShouldBe(500);
        for (var i = 1; i < allSeqs.Count; i++)
            allSeqs[i].ShouldBeGreaterThan(allSeqs[i - 1]);
    }

    // ---------------------------------------------------------------
    // Go ref: TestJetStreamClusterLongMixedMultiStreamOps — jetstream_cluster_long_test.go
    // ---------------------------------------------------------------

    [Fact]
    [Trait("Category", "LongRunning")]
    public async Task Mixed_ops_five_streams_100_messages_each_consumers_fetch_all()
    {
        // Go ref: TestJetStreamClusterLongMixedMultiStreamOps — jetstream_cluster_long_test.go
        await using var cluster = await JetStreamClusterFixture.StartAsync(3);

        // Create 5 streams
        for (var s = 0; s < 5; s++)
            await cluster.CreateStreamAsync($"MIXED{s}", [$"mixed{s}.>"], replicas: 3);

        // Publish 100 messages to each
        for (var s = 0; s < 5; s++)
            for (var i = 0; i < 100; i++)
                await cluster.PublishAsync($"mixed{s}.event", $"stream{s}-msg{i}");

        // Create one consumer per stream
        for (var s = 0; s < 5; s++)
            await cluster.CreateConsumerAsync($"MIXED{s}", $"reader{s}", filterSubject: $"mixed{s}.>");

        // Fetch all messages from each stream consumer
        for (var s = 0; s < 5; s++)
        {
            var batch = await cluster.FetchAsync($"MIXED{s}", $"reader{s}", 100);
            batch.Messages.Count.ShouldBe(100);
            batch.Messages[0].Sequence.ShouldBe(1UL);
            batch.Messages[^1].Sequence.ShouldBe(100UL);
        }

        var info = await cluster.RequestAsync(JetStreamApiSubjects.Info, "{}");
        info.AccountInfo!.Streams.ShouldBe(5);
        info.AccountInfo.Consumers.ShouldBe(5);
    }

    // ---------------------------------------------------------------
    // Go ref: TestJetStreamClusterLongRapidMetaStepdowns — jetstream_cluster_long_test.go
    // ---------------------------------------------------------------

    [Fact]
    [Trait("Category", "LongRunning")]
    public async Task Rapid_meta_stepdowns_20_times_all_streams_remain_accessible()
    {
        // Go ref: TestJetStreamClusterLongRapidMetaStepdowns — jetstream_cluster_long_test.go
        await using var cluster = await JetStreamClusterFixture.StartAsync(3);

        // Create streams before stepdowns
        for (var i = 0; i < 5; i++)
            await cluster.CreateStreamAsync($"RAPID{i}", [$"rapid{i}.>"], replicas: 3);

        var leaderVersions = new List<long>();
        var initialState = cluster.GetMetaState();
        leaderVersions.Add(initialState!.LeadershipVersion);

        // Perform 20 rapid meta stepdowns
        for (var sd = 0; sd < 20; sd++)
        {
            cluster.StepDownMetaLeader();
            var state = cluster.GetMetaState();
            leaderVersions.Add(state!.LeadershipVersion);
        }

        // Leadership version must monotonically increase
        for (var i = 1; i < leaderVersions.Count; i++)
            leaderVersions[i].ShouldBeGreaterThan(leaderVersions[i - 1]);

        // All streams still accessible
        var names = await cluster.RequestAsync(JetStreamApiSubjects.StreamNames, "{}");
        names.StreamNames!.Count.ShouldBe(5);
        for (var i = 0; i < 5; i++)
            names.StreamNames.ShouldContain($"RAPID{i}");
    }

    // ---------------------------------------------------------------
    // Go ref: TestJetStreamClusterLong10000MessagesR1 — jetstream_cluster_long_test.go
    // ---------------------------------------------------------------

    [Fact]
    [Trait("Category", "LongRunning")]
    public async Task Ten_thousand_small_messages_in_R1_stream()
    {
        // Go ref: TestJetStreamClusterLong10000MessagesR1 — jetstream_cluster_long_test.go
        await using var cluster = await JetStreamClusterFixture.StartAsync(3);

        await cluster.CreateStreamAsync("R1HUGE", ["r1h.>"], replicas: 1);

        for (var i = 0; i < 10000; i++)
        {
            var ack = await cluster.PublishAsync("r1h.event", $"x{i}");
            ack.ErrorCode.ShouldBeNull();
        }

        var state = await cluster.GetStreamStateAsync("R1HUGE");
        state.Messages.ShouldBe(10000UL);
        state.LastSeq.ShouldBe(10000UL);
    }

    // ---------------------------------------------------------------
    // Go ref: TestJetStreamClusterLongMaxMessagesLimit — jetstream_cluster_long_test.go
    // ---------------------------------------------------------------

    [Fact]
    [Trait("Category", "LongRunning")]
    public async Task Stream_with_max_messages_100_has_exactly_100_after_1000_publishes()
    {
        // Go ref: TestJetStreamClusterLongMaxMessagesLimit — jetstream_cluster_long_test.go
        await using var cluster = await JetStreamClusterFixture.StartAsync(3);

        var cfg = new StreamConfig
        {
            Name = "MAXLIMIT",
            Subjects = ["ml.>"],
            Replicas = 3,
            MaxMsgs = 100,
        };
        cluster.CreateStreamDirect(cfg);

        for (var i = 0; i < 1000; i++)
            await cluster.PublishAsync("ml.event", $"msg-{i}");

        var state = await cluster.GetStreamStateAsync("MAXLIMIT");
        // MaxMsgs=100: only the latest 100 messages retained (old ones discarded)
        state.Messages.ShouldBeLessThanOrEqualTo(100UL);
        state.Messages.ShouldBeGreaterThan(0UL);
    }

    // ---------------------------------------------------------------
    // Go ref: TestJetStreamClusterLongConsumerPendingWithMaxMessages — jetstream_cluster_long_test.go
    // ---------------------------------------------------------------

    [Fact]
    [Trait("Category", "LongRunning")]
    public async Task Consumer_on_max_messages_stream_tracks_correct_pending()
    {
        // Go ref: TestJetStreamClusterLongConsumerPendingWithMaxMessages — jetstream_cluster_long_test.go
        await using var cluster = await JetStreamClusterFixture.StartAsync(3);

        var cfg = new StreamConfig
        {
            Name = "MAXPEND",
            Subjects = ["mp.>"],
            Replicas = 3,
            MaxMsgs = 50,
        };
        cluster.CreateStreamDirect(cfg);

        // Publish 200 messages (150 will be evicted by MaxMsgs)
        for (var i = 0; i < 200; i++)
            await cluster.PublishAsync("mp.event", $"msg-{i}");

        var state = await cluster.GetStreamStateAsync("MAXPEND");
        // Stream retains at most 50 messages
        state.Messages.ShouldBeLessThanOrEqualTo(50UL);

        // Create consumer after publishes (starts at current first seq)
        await cluster.CreateConsumerAsync("MAXPEND", "latecons", filterSubject: "mp.>",
            ackPolicy: AckPolicy.None);

        var batch = await cluster.FetchAsync("MAXPEND", "latecons", 100);
        // Consumer should see only retained messages
        ((ulong)batch.Messages.Count).ShouldBeLessThanOrEqualTo(state.Messages);
    }
}
