// Go reference: golang/nats-server/server/jetstream.go — $JS.API.* subject dispatch
// Covers create/info/update/delete for streams, create/info/list/delete for consumers,
// direct-get access, account info, and 404 routing for unknown subjects.

namespace NATS.Server.Tests;

public class ApiEndpointParityTests
{
    // Go ref: jsStreamCreateT handler — stream create persists config and info round-trips correctly.
    [Fact]
    public async Task Stream_create_info_update_delete_lifecycle()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("EVENTS", "events.*");

        var info = await fx.RequestLocalAsync("$JS.API.STREAM.INFO.EVENTS", "{}");
        info.Error.ShouldBeNull();
        info.StreamInfo.ShouldNotBeNull();
        info.StreamInfo!.Config.Name.ShouldBe("EVENTS");
        info.StreamInfo.Config.Subjects.ShouldContain("events.*");

        var update = await fx.RequestLocalAsync(
            "$JS.API.STREAM.UPDATE.EVENTS",
            "{\"name\":\"EVENTS\",\"subjects\":[\"events.*\"],\"max_msgs\":100}");
        update.Error.ShouldBeNull();
        update.StreamInfo.ShouldNotBeNull();
        update.StreamInfo!.Config.MaxMsgs.ShouldBe(100);

        var delete = await fx.RequestLocalAsync("$JS.API.STREAM.DELETE.EVENTS", "{}");
        delete.Error.ShouldBeNull();
        delete.Success.ShouldBeTrue();

        var infoAfterDelete = await fx.RequestLocalAsync("$JS.API.STREAM.INFO.EVENTS", "{}");
        infoAfterDelete.Error.ShouldNotBeNull();
        infoAfterDelete.Error!.Code.ShouldBe(404);
    }

    // Go ref: jsConsumerCreateT / jsConsumerInfoT handlers — consumer create then info returns config.
    [Fact]
    public async Task Consumer_create_info_list_delete_lifecycle()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("ORDERS", "orders.*");

        var create = await fx.CreateConsumerAsync("ORDERS", "MON", "orders.created");
        create.Error.ShouldBeNull();
        create.ConsumerInfo.ShouldNotBeNull();
        create.ConsumerInfo!.Config.DurableName.ShouldBe("MON");

        var info = await fx.RequestLocalAsync("$JS.API.CONSUMER.INFO.ORDERS.MON", "{}");
        info.Error.ShouldBeNull();
        info.ConsumerInfo.ShouldNotBeNull();
        info.ConsumerInfo!.Config.FilterSubject.ShouldBe("orders.created");

        var names = await fx.RequestLocalAsync("$JS.API.CONSUMER.NAMES.ORDERS", "{}");
        names.Error.ShouldBeNull();
        names.ConsumerNames.ShouldNotBeNull();
        names.ConsumerNames.ShouldContain("MON");

        var list = await fx.RequestLocalAsync("$JS.API.CONSUMER.LIST.ORDERS", "{}");
        list.Error.ShouldBeNull();
        list.ConsumerNames.ShouldNotBeNull();
        list.ConsumerNames.ShouldContain("MON");

        var del = await fx.RequestLocalAsync("$JS.API.CONSUMER.DELETE.ORDERS.MON", "{}");
        del.Error.ShouldBeNull();
        del.Success.ShouldBeTrue();

        var infoAfterDelete = await fx.RequestLocalAsync("$JS.API.CONSUMER.INFO.ORDERS.MON", "{}");
        infoAfterDelete.Error.ShouldNotBeNull();
        infoAfterDelete.Error!.Code.ShouldBe(404);
    }

    // Go ref: jsDirectMsgGetT handler — direct get returns message payload at correct sequence.
    [Fact]
    public async Task Direct_get_returns_message_at_sequence()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("LOGS", "logs.*");
        var ack = await fx.PublishAndGetAckAsync("logs.app", "hello-direct");

        var direct = await fx.RequestLocalAsync("$JS.API.DIRECT.GET.LOGS", $"{{\"seq\":{ack.Seq}}}");
        direct.Error.ShouldBeNull();
        direct.DirectMessage.ShouldNotBeNull();
        direct.DirectMessage!.Sequence.ShouldBe(ack.Seq);
        direct.DirectMessage.Payload.ShouldBe("hello-direct");
    }

    // Go ref: jsStreamNamesT / $JS.API.INFO handler — names list reflects created streams,
    // account info reflects total stream and consumer counts.
    [Fact]
    public async Task Stream_names_and_account_info_reflect_state()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("ALPHA", "alpha.*");
        _ = await fx.CreateStreamAsync("BETA", ["beta.*"]);
        _ = await fx.CreateConsumerAsync("ALPHA", "C1", "alpha.>");
        _ = await fx.CreateConsumerAsync("BETA", "C2", "beta.>");

        var names = await fx.RequestLocalAsync("$JS.API.STREAM.NAMES", "{}");
        names.Error.ShouldBeNull();
        names.StreamNames.ShouldNotBeNull();
        names.StreamNames.ShouldContain("ALPHA");
        names.StreamNames.ShouldContain("BETA");

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

    // Go ref: JetStreamApiRouter dispatch — subjects not matching any handler return 404 error shape.
    [Fact]
    public async Task Unknown_api_subject_returns_404_error_response()
    {
        await using var fx = await JetStreamApiFixture.StartWithStreamAsync("ORDERS", "orders.*");

        var response = await fx.RequestLocalAsync("$JS.API.STREAM.FROBNICATE.ORDERS", "{}");
        response.Error.ShouldNotBeNull();
        response.Error!.Code.ShouldBe(404);
        response.StreamInfo.ShouldBeNull();
        response.ConsumerInfo.ShouldBeNull();
        response.Success.ShouldBeFalse();
    }
}
