// Ported from golang/nats-server/server/jetstream_test.go
// Admin operations: stream/consumer list/names, account info, stream leader stepdown,
// peer info, account purge, server remove, API routing

using System.Text;
using NATS.Server.Auth;
using NATS.Server.JetStream;
using NATS.Server.JetStream.Api;
using NATS.Server.JetStream.Cluster;
using NATS.Server.JetStream.Models;

namespace NATS.Server.Tests.JetStream;

public class JetStreamAdminTests
{
    // Go: TestJetStreamRequestAPI server/jetstream_test.go:5429
    [Fact]
    public async Task Account_info_returns_stream_and_consumer_counts()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("S1", "s1.>");
        _ = await fx.RequestLocalAsync("$JS.API.STREAM.CREATE.S2", """{"subjects":["s2.>"]}""");
        _ = await fx.CreateConsumerAsync("S1", "C1", "s1.>");

        var info = await fx.RequestLocalAsync("$JS.API.INFO", "{}");
        info.Error.ShouldBeNull();
        info.AccountInfo.ShouldNotBeNull();
        info.AccountInfo!.Streams.ShouldBe(2);
        info.AccountInfo.Consumers.ShouldBe(1);
    }

    // Go: TestJetStreamRequestAPI — account info with zero
    [Fact]
    public void Account_info_empty_returns_zero_counts()
    {
        var router = new JetStreamApiRouter(new StreamManager(), new ConsumerManager());
        var resp = router.Route("$JS.API.INFO", "{}"u8);

        resp.Error.ShouldBeNull();
        resp.AccountInfo.ShouldNotBeNull();
        resp.AccountInfo!.Streams.ShouldBe(0);
        resp.AccountInfo.Consumers.ShouldBe(0);
    }

    // Go: TestJetStreamFilteredStreamNames server/jetstream_test.go:5392
    [Fact]
    public async Task Stream_names_returns_all_streams()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("ALPHA", "alpha.>");
        _ = await fx.RequestLocalAsync("$JS.API.STREAM.CREATE.BETA", """{"subjects":["beta.>"]}""");
        _ = await fx.RequestLocalAsync("$JS.API.STREAM.CREATE.GAMMA", """{"subjects":["gamma.>"]}""");

        var names = await fx.RequestLocalAsync("$JS.API.STREAM.NAMES", "{}");
        names.StreamNames.ShouldNotBeNull();
        names.StreamNames!.Count.ShouldBe(3);
        names.StreamNames.ShouldContain("ALPHA");
        names.StreamNames.ShouldContain("BETA");
        names.StreamNames.ShouldContain("GAMMA");
    }

    // Go: TestJetStreamFilteredStreamNames — names sorted
    [Fact]
    public async Task Stream_names_are_sorted()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("ZZZ", "zzz.>");
        _ = await fx.RequestLocalAsync("$JS.API.STREAM.CREATE.AAA", """{"subjects":["aaa.>"]}""");
        _ = await fx.RequestLocalAsync("$JS.API.STREAM.CREATE.MMM", """{"subjects":["mmm.>"]}""");

        var names = await fx.RequestLocalAsync("$JS.API.STREAM.NAMES", "{}");
        names.StreamNames![0].ShouldBe("AAA");
        names.StreamNames[1].ShouldBe("MMM");
        names.StreamNames[2].ShouldBe("ZZZ");
    }

    // Go: TestJetStreamStreamList
    [Fact]
    public async Task Stream_list_returns_same_as_names()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("L1", "l1.>");
        _ = await fx.RequestLocalAsync("$JS.API.STREAM.CREATE.L2", """{"subjects":["l2.>"]}""");

        var names = await fx.RequestLocalAsync("$JS.API.STREAM.NAMES", "{}");
        var list = await fx.RequestLocalAsync("$JS.API.STREAM.LIST", "{}");

        list.StreamNames!.Count.ShouldBe(names.StreamNames!.Count);
    }

    // Go: TestJetStreamFilteredStreamNames — empty after delete all
    [Fact]
    public async Task Stream_names_empty_after_all_deleted()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("DEL1", "del1.>");
        _ = await fx.RequestLocalAsync("$JS.API.STREAM.CREATE.DEL2", """{"subjects":["del2.>"]}""");

        _ = await fx.RequestLocalAsync("$JS.API.STREAM.DELETE.DEL1", "{}");
        _ = await fx.RequestLocalAsync("$JS.API.STREAM.DELETE.DEL2", "{}");

        var names = await fx.RequestLocalAsync("$JS.API.STREAM.NAMES", "{}");
        names.StreamNames!.Count.ShouldBe(0);
    }

    // Go: TestJetStreamConsumerList
    [Fact]
    public async Task Consumer_names_returns_all_consumers()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("CL", "cl.>");
        _ = await fx.CreateConsumerAsync("CL", "A", "cl.a");
        _ = await fx.CreateConsumerAsync("CL", "B", "cl.b");
        _ = await fx.CreateConsumerAsync("CL", "C", "cl.c");

        var names = await fx.RequestLocalAsync("$JS.API.CONSUMER.NAMES.CL", "{}");
        names.ConsumerNames.ShouldNotBeNull();
        names.ConsumerNames!.Count.ShouldBe(3);
    }

    // Go: TestJetStreamConsumerList — names sorted
    [Fact]
    public async Task Consumer_names_are_sorted()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("CS", "cs.>");
        _ = await fx.CreateConsumerAsync("CS", "ZZZ", "cs.>");
        _ = await fx.CreateConsumerAsync("CS", "AAA", "cs.>");
        _ = await fx.CreateConsumerAsync("CS", "MMM", "cs.>");

        var names = await fx.RequestLocalAsync("$JS.API.CONSUMER.NAMES.CS", "{}");
        names.ConsumerNames![0].ShouldBe("AAA");
        names.ConsumerNames[1].ShouldBe("MMM");
        names.ConsumerNames[2].ShouldBe("ZZZ");
    }

    // Go: TestJetStreamConsumerList — list matches names
    [Fact]
    public async Task Consumer_list_returns_same_as_names()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("CLM", "clm.>");
        _ = await fx.CreateConsumerAsync("CLM", "C1", "clm.>");
        _ = await fx.CreateConsumerAsync("CLM", "C2", "clm.>");

        var names = await fx.RequestLocalAsync("$JS.API.CONSUMER.NAMES.CLM", "{}");
        var list = await fx.RequestLocalAsync("$JS.API.CONSUMER.LIST.CLM", "{}");

        list.ConsumerNames!.Count.ShouldBe(names.ConsumerNames!.Count);
    }

    // Go: TestJetStreamConsumerList — empty after delete all
    [Fact]
    public async Task Consumer_names_empty_after_all_deleted()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("CD", "cd.>");
        _ = await fx.CreateConsumerAsync("CD", "C1", "cd.>");

        _ = await fx.RequestLocalAsync("$JS.API.CONSUMER.DELETE.CD.C1", "{}");

        var names = await fx.RequestLocalAsync("$JS.API.CONSUMER.NAMES.CD", "{}");
        names.ConsumerNames!.Count.ShouldBe(0);
    }

    // Go: TestJetStreamStreamLeaderStepdown
    [Fact]
    public async Task Stream_leader_stepdown_returns_success()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("SLD", "sld.>");

        var resp = await fx.RequestLocalAsync("$JS.API.STREAM.LEADER.STEPDOWN.SLD", "{}");
        resp.Success.ShouldBeTrue();
    }

    // Go: TestJetStreamStreamPeerRemove
    [Fact]
    public async Task Stream_peer_remove_returns_success()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("SPR", "spr.>");

        var resp = await fx.RequestLocalAsync("$JS.API.STREAM.PEER.REMOVE.SPR", "{}");
        resp.Success.ShouldBeTrue();
    }

    // Go: TestJetStreamConsumerLeaderStepdown
    [Fact]
    public async Task Consumer_leader_stepdown_returns_success()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("CLSD", "clsd.>");
        _ = await fx.CreateConsumerAsync("CLSD", "C1", "clsd.>");

        var resp = await fx.RequestLocalAsync("$JS.API.CONSUMER.LEADER.STEPDOWN.CLSD.C1", "{}");
        resp.Success.ShouldBeTrue();
    }

    // Go: TestJetStreamAccountPurge server/jetstream_test.go
    [Fact]
    public async Task Account_purge_returns_success()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("AP", "ap.>");

        var resp = await fx.RequestLocalAsync("$JS.API.ACCOUNT.PURGE.DEFAULT", "{}");
        resp.Success.ShouldBeTrue();
    }

    // Go: TestJetStreamServerRemove
    [Fact]
    public void Server_remove_returns_success()
    {
        var router = new JetStreamApiRouter();
        var resp = router.Route("$JS.API.SERVER.REMOVE", "{}"u8);
        resp.Success.ShouldBeTrue();
    }

    // Go: TestJetStreamAccountStreamMove
    [Fact]
    public async Task Account_stream_move_returns_success()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("ASM", "asm.>");

        var resp = await fx.RequestLocalAsync("$JS.API.ACCOUNT.STREAM.MOVE.MYSTREAM", "{}");
        resp.Success.ShouldBeTrue();
    }

    // Go: TestJetStreamAccountStreamMoveCancel
    [Fact]
    public async Task Account_stream_move_cancel_returns_success()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("ASMC", "asmc.>");

        var resp = await fx.RequestLocalAsync("$JS.API.ACCOUNT.STREAM.MOVE.CANCEL.MYSTREAM", "{}");
        resp.Success.ShouldBeTrue();
    }

    // Go: TestJetStreamRequestAPI — unknown subject
    [Fact]
    public void Unknown_api_subject_returns_not_found()
    {
        var router = new JetStreamApiRouter();
        var resp = router.Route("$JS.API.UNKNOWN.THING", "{}"u8);
        resp.Error.ShouldNotBeNull();
        resp.Error!.Code.ShouldBe(404);
    }

    // Go: TestJetStreamRequestAPI — multiple API calls
    [Fact]
    public async Task Multiple_api_calls_in_sequence()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("MULTI", "multi.>");

        // INFO
        var info = await fx.RequestLocalAsync("$JS.API.INFO", "{}");
        info.AccountInfo.ShouldNotBeNull();

        // STREAM.NAMES
        var names = await fx.RequestLocalAsync("$JS.API.STREAM.NAMES", "{}");
        names.StreamNames.ShouldNotBeNull();

        // STREAM.INFO
        var sInfo = await fx.RequestLocalAsync("$JS.API.STREAM.INFO.MULTI", "{}");
        sInfo.StreamInfo.ShouldNotBeNull();
    }

    // Go: TestJetStreamDisabledLimitsEnforcementJWT server/jetstream_test.go
    [Fact]
    public async Task Jwt_limited_account_enforces_max_streams()
    {
        await using var fx = await JetStreamApiFixture.StartJwtLimitedAccountAsync(maxStreams: 1);

        var s1 = await fx.RequestLocalAsync("$JS.API.STREAM.CREATE.S1", """{"subjects":["s1.>"]}""");
        s1.Error.ShouldBeNull();

        var s2 = await fx.RequestLocalAsync("$JS.API.STREAM.CREATE.S2", """{"subjects":["s2.>"]}""");
        s2.Error.ShouldNotBeNull();
        s2.Error!.Code.ShouldBe(10027);
    }

    // Go: TestJetStreamDisabledLimitsEnforcementJWT — delete frees slot
    [Fact]
    public async Task Jwt_limited_account_delete_frees_slot()
    {
        await using var fx = await JetStreamApiFixture.StartJwtLimitedAccountAsync(maxStreams: 1);

        _ = await fx.RequestLocalAsync("$JS.API.STREAM.CREATE.S1", """{"subjects":["s1.>"]}""");

        _ = await fx.RequestLocalAsync("$JS.API.STREAM.DELETE.S1", "{}");

        var s2 = await fx.RequestLocalAsync("$JS.API.STREAM.CREATE.S2", """{"subjects":["s2.>"]}""");
        s2.Error.ShouldBeNull();
    }

    // Go: TestJetStreamSystemLimits server/jetstream_test.go:4636
    [Fact]
    public async Task Account_info_updates_after_consumer_creation()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("AI", "ai.>");

        var before = await fx.RequestLocalAsync("$JS.API.INFO", "{}");
        before.AccountInfo!.Consumers.ShouldBe(0);

        _ = await fx.CreateConsumerAsync("AI", "C1", "ai.>");

        var after = await fx.RequestLocalAsync("$JS.API.INFO", "{}");
        after.AccountInfo!.Consumers.ShouldBe(1);
    }

    // Go: TestJetStreamSystemLimits — account info updates after stream deletion
    [Fact]
    public async Task Account_info_updates_after_stream_deletion()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("AID", "aid.>");

        var before = await fx.RequestLocalAsync("$JS.API.INFO", "{}");
        before.AccountInfo!.Streams.ShouldBe(1);

        _ = await fx.RequestLocalAsync("$JS.API.STREAM.DELETE.AID", "{}");

        var after = await fx.RequestLocalAsync("$JS.API.INFO", "{}");
        after.AccountInfo!.Streams.ShouldBe(0);
    }

    // Go: TestJetStreamConsumerList — consumer names scoped to stream
    [Fact]
    public async Task Consumer_names_for_non_existent_stream_returns_empty()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("X", "x.>");

        var names = await fx.RequestLocalAsync("$JS.API.CONSUMER.NAMES.NOPE", "{}");
        names.ConsumerNames.ShouldNotBeNull();
        names.ConsumerNames!.Count.ShouldBe(0);
    }

    // Go: TestJetStreamMetaLeaderStepdown
    [Fact]
    public void Meta_leader_stepdown_with_meta_group_returns_success()
    {
        var metaGroup = new JetStreamMetaGroup(3);
        var router = new JetStreamApiRouter(new StreamManager(), new ConsumerManager(), metaGroup);

        var resp = router.Route("$JS.API.META.LEADER.STEPDOWN", "{}"u8);
        resp.Success.ShouldBeTrue();
    }

    // Go: TestJetStreamMetaLeaderStepdown — without meta group
    [Fact]
    public void Meta_leader_stepdown_without_meta_group_returns_not_found()
    {
        var router = new JetStreamApiRouter();
        var resp = router.Route("$JS.API.META.LEADER.STEPDOWN", "{}"u8);
        resp.Error.ShouldNotBeNull();
        resp.Error!.Code.ShouldBe(404);
    }

    // Go: TestJetStreamStreamLeaderStepdown — non-existent stream
    [Fact]
    public async Task Stream_leader_stepdown_non_existent_still_succeeds()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("LS", "ls.>");

        // Stepdown for non-existent stream doesn't error (no-op)
        var resp = await fx.RequestLocalAsync("$JS.API.STREAM.LEADER.STEPDOWN.NOPE", "{}");
        resp.Success.ShouldBeTrue();
    }

    // Go: TestJetStreamConsumerNext — via API router
    [Fact]
    public async Task Consumer_next_via_api_returns_messages()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("NEXT", "next.>");
        _ = await fx.CreateConsumerAsync("NEXT", "C1", "next.>");

        _ = await fx.PublishAndGetAckAsync("next.x", "data1");
        _ = await fx.PublishAndGetAckAsync("next.x", "data2");

        var resp = await fx.RequestLocalAsync(
            "$JS.API.CONSUMER.MSG.NEXT.NEXT.C1",
            """{"batch":2}""");
        resp.PullBatch.ShouldNotBeNull();
        resp.PullBatch!.Messages.Count.ShouldBe(2);
    }

    // Go: TestJetStreamConsumerNext — empty
    [Fact]
    public async Task Consumer_next_with_no_messages_returns_empty()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("NE", "ne.>");
        _ = await fx.CreateConsumerAsync("NE", "C1", "ne.>");

        var resp = await fx.RequestLocalAsync(
            "$JS.API.CONSUMER.MSG.NEXT.NE.C1",
            """{"batch":1}""");
        resp.PullBatch.ShouldNotBeNull();
        resp.PullBatch!.Messages.Count.ShouldBe(0);
    }

    // Go: TestJetStreamStorageSelection
    [Fact]
    public async Task Storage_selection_file()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamConfigAsync(new StreamConfig
        {
            Name = "FILE",
            Subjects = ["file.>"],
            Storage = StorageType.File,
        });

        var backend = await fx.GetStreamBackendTypeAsync("FILE");
        backend.ShouldBe("file");
    }

    // Go: TestJetStreamStorageSelection — memory
    [Fact]
    public async Task Storage_selection_memory()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamConfigAsync(new StreamConfig
        {
            Name = "MEM",
            Subjects = ["mem.>"],
            Storage = StorageType.Memory,
        });

        var backend = await fx.GetStreamBackendTypeAsync("MEM");
        backend.ShouldBe("memory");
    }

    // Go: TestJetStreamStorageSelection — non-existent
    [Fact]
    public async Task Storage_backend_type_for_missing_stream()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("X", "x.>");

        var backend = await fx.GetStreamBackendTypeAsync("NOPE");
        backend.ShouldBe("missing");
    }

    // Go: TestJetStreamConsumerNames — for specific stream
    [Fact]
    public async Task Consumer_names_only_include_target_stream()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("S1", "s1.>");
        _ = await fx.RequestLocalAsync("$JS.API.STREAM.CREATE.S2", """{"subjects":["s2.>"]}""");
        _ = await fx.CreateConsumerAsync("S1", "C1", "s1.>");
        _ = await fx.CreateConsumerAsync("S2", "C2", "s2.>");

        var names = await fx.RequestLocalAsync("$JS.API.CONSUMER.NAMES.S1", "{}");
        names.ConsumerNames!.Count.ShouldBe(1);
        names.ConsumerNames.ShouldContain("C1");
        names.ConsumerNames.ShouldNotContain("C2");
    }

    // Go: TestJetStreamConsumerDelete — delete decrements count
    [Fact]
    public async Task Delete_consumer_decrements_account_info_count()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("DCC", "dcc.>");
        _ = await fx.CreateConsumerAsync("DCC", "C1", "dcc.>");
        _ = await fx.CreateConsumerAsync("DCC", "C2", "dcc.>");

        var before = await fx.RequestLocalAsync("$JS.API.INFO", "{}");
        before.AccountInfo!.Consumers.ShouldBe(2);

        _ = await fx.RequestLocalAsync("$JS.API.CONSUMER.DELETE.DCC.C1", "{}");

        var after = await fx.RequestLocalAsync("$JS.API.INFO", "{}");
        after.AccountInfo!.Consumers.ShouldBe(1);
    }

    // Go: TestJetStreamAccountPurge — empty account name fails
    [Fact]
    public void Account_purge_without_name_returns_not_found()
    {
        var router = new JetStreamApiRouter();
        var resp = router.Route("$JS.API.ACCOUNT.PURGE.", "{}"u8);
        resp.Error.ShouldNotBeNull();
    }

    // Go: TestJetStreamAccountStreamMove — empty stream name fails
    [Fact]
    public void Account_stream_move_without_name_returns_not_found()
    {
        var router = new JetStreamApiRouter();
        var resp = router.Route("$JS.API.ACCOUNT.STREAM.MOVE.", "{}"u8);
        resp.Error.ShouldNotBeNull();
    }

    // Go: TestJetStreamStreamLeaderStepdown — empty stream name fails
    [Fact]
    public void Stream_leader_stepdown_without_name_returns_not_found()
    {
        var router = new JetStreamApiRouter();
        var resp = router.Route("$JS.API.STREAM.LEADER.STEPDOWN.", "{}"u8);
        resp.Error.ShouldNotBeNull();
    }

    // Go: TestJetStreamStreamPeerRemove — empty stream name fails
    [Fact]
    public void Stream_peer_remove_without_name_returns_not_found()
    {
        var router = new JetStreamApiRouter();
        var resp = router.Route("$JS.API.STREAM.PEER.REMOVE.", "{}"u8);
        resp.Error.ShouldNotBeNull();
    }

    // Go: TestJetStreamConsumerLeaderStepdown — malformed subject
    [Fact]
    public void Consumer_leader_stepdown_with_single_token_returns_not_found()
    {
        var router = new JetStreamApiRouter();
        var resp = router.Route("$JS.API.CONSUMER.LEADER.STEPDOWN.ONLYONE", "{}"u8);
        resp.Error.ShouldNotBeNull();
    }

    // Go: TestJetStreamConsumerReset — non-existent consumer
    [Fact]
    public async Task Consumer_reset_non_existent_returns_not_found()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("RNE", "rne.>");

        var resp = await fx.RequestLocalAsync("$JS.API.CONSUMER.RESET.RNE.NOPE", "{}");
        resp.Success.ShouldBeFalse();
    }

    // Go: TestJetStreamConsumerUnpin — non-existent consumer
    [Fact]
    public async Task Consumer_unpin_non_existent_returns_not_found()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("UNE", "une.>");

        var resp = await fx.RequestLocalAsync("$JS.API.CONSUMER.UNPIN.UNE.NOPE", "{}");
        resp.Success.ShouldBeFalse();
    }

    // Go: TestJetStreamLimits server/jetstream_test.go
    [Fact]
    public async Task Jwt_limited_account_allows_within_limit()
    {
        await using var fx = await JetStreamApiFixture.StartJwtLimitedAccountAsync(maxStreams: 3);

        var s1 = await fx.RequestLocalAsync("$JS.API.STREAM.CREATE.S1", """{"subjects":["s1.>"]}""");
        s1.Error.ShouldBeNull();
        var s2 = await fx.RequestLocalAsync("$JS.API.STREAM.CREATE.S2", """{"subjects":["s2.>"]}""");
        s2.Error.ShouldBeNull();
        var s3 = await fx.RequestLocalAsync("$JS.API.STREAM.CREATE.S3", """{"subjects":["s3.>"]}""");
        s3.Error.ShouldBeNull();

        // Fourth should fail
        var s4 = await fx.RequestLocalAsync("$JS.API.STREAM.CREATE.S4", """{"subjects":["s4.>"]}""");
        s4.Error.ShouldNotBeNull();
    }

    // Go: TestJetStreamStreamMessageDeleteViaAPI
    [Fact]
    public async Task Message_delete_via_api_and_verify()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("MDAPI", "mdapi.>");
        _ = await fx.PublishAndGetAckAsync("mdapi.x", "msg1");
        var ack2 = await fx.PublishAndGetAckAsync("mdapi.x", "msg2");
        _ = await fx.PublishAndGetAckAsync("mdapi.x", "msg3");

        var del = await fx.RequestLocalAsync(
            "$JS.API.STREAM.MSG.DELETE.MDAPI",
            $$"""{ "seq": {{ack2.Seq}} }""");
        del.Success.ShouldBeTrue();

        // Verify the deleted message is gone
        var msg = await fx.RequestLocalAsync(
            "$JS.API.STREAM.MSG.GET.MDAPI",
            $$"""{ "seq": {{ack2.Seq}} }""");
        msg.Error.ShouldNotBeNull();

        // Other messages still exist
        var state = await fx.GetStreamStateAsync("MDAPI");
        state.Messages.ShouldBe(2UL);
    }

    // Go: TestJetStreamRequestAPI — direct get missing sequence
    [Fact]
    public async Task Direct_get_with_zero_sequence_returns_error()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("DGZ", "dgz.>");
        _ = await fx.PublishAndGetAckAsync("dgz.x", "data");

        var resp = await fx.RequestLocalAsync("$JS.API.DIRECT.GET.DGZ", """{"seq":0}""");
        resp.Error.ShouldNotBeNull();
    }

    // Go: TestJetStreamRequestAPI — direct get non-existent stream
    [Fact]
    public void Direct_get_non_existent_stream_returns_error()
    {
        var router = new JetStreamApiRouter();
        var resp = router.Route("$JS.API.DIRECT.GET.NOPE", """{"seq":1}"""u8);
        resp.Error.ShouldNotBeNull();
    }

    // Go: TestJetStreamConsumerNext — batch default
    [Fact]
    public async Task Consumer_next_with_no_batch_defaults_to_one()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("NBAT", "nbat.>");
        _ = await fx.CreateConsumerAsync("NBAT", "C1", "nbat.>");
        _ = await fx.PublishAndGetAckAsync("nbat.x", "data1");
        _ = await fx.PublishAndGetAckAsync("nbat.x", "data2");

        var resp = await fx.RequestLocalAsync(
            "$JS.API.CONSUMER.MSG.NEXT.NBAT.C1", "{}");
        resp.PullBatch!.Messages.Count.ShouldBe(1);
    }
}
