// Ported from golang/nats-server/server/jetstream.go:414-523 (enableJetStream)
// Tests for JetStreamService lifecycle orchestration: store directory creation,
// API subject registration, configuration property exposure, and dispose semantics.

using NATS.Server.Configuration;
using NATS.Server.JetStream;

namespace NATS.Server.JetStream.Tests.JetStream;

public sealed class JetStreamServiceOrchestrationTests : IDisposable
{
    private readonly List<string> _tempDirs = [];

    private string MakeTempDir()
    {
        var path = Path.Combine(Path.GetTempPath(), "nats-js-test-" + Guid.NewGuid().ToString("N"));
        _tempDirs.Add(path);
        return path;
    }

    public void Dispose()
    {
        foreach (var dir in _tempDirs)
        {
            if (Directory.Exists(dir))
                Directory.Delete(dir, recursive: true);
        }
    }

    // Go: enableJetStream — jetstream.go:414 — happy path creates store dir and marks running
    [Fact]
    public async Task StartAsync_creates_store_directory_and_marks_running()
    {
        var storeDir = MakeTempDir();
        var options = new JetStreamOptions { StoreDir = storeDir };
        await using var svc = new JetStreamService(options);

        Directory.Exists(storeDir).ShouldBeFalse("directory must not exist before start");

        await svc.StartAsync(CancellationToken.None);

        svc.IsRunning.ShouldBeTrue();
        Directory.Exists(storeDir).ShouldBeTrue("StartAsync must create the store directory");
    }

    // Go: enableJetStream — jetstream.go:430 — existing dir is accepted without error
    [Fact]
    public async Task StartAsync_accepts_preexisting_store_directory()
    {
        var storeDir = MakeTempDir();
        Directory.CreateDirectory(storeDir);
        var options = new JetStreamOptions { StoreDir = storeDir };
        await using var svc = new JetStreamService(options);

        await svc.StartAsync(CancellationToken.None);

        svc.IsRunning.ShouldBeTrue();
        Directory.Exists(storeDir).ShouldBeTrue();
    }

    // Go: enableJetStream — memory-only mode when StoreDir is empty
    [Fact]
    public async Task StartAsync_with_empty_StoreDir_starts_in_memory_only_mode()
    {
        var options = new JetStreamOptions { StoreDir = string.Empty };
        await using var svc = new JetStreamService(options);

        await svc.StartAsync(CancellationToken.None);

        svc.IsRunning.ShouldBeTrue();
    }

    // Go: setJetStreamExportSubs — jetstream.go:489 — all $JS.API subjects registered
    [Fact]
    public async Task RegisteredApiSubjects_contains_expected_subjects_after_start()
    {
        var options = new JetStreamOptions();
        await using var svc = new JetStreamService(options);

        await svc.StartAsync(CancellationToken.None);

        var subjects = svc.RegisteredApiSubjects;
        subjects.ShouldNotBeEmpty();
        subjects.ShouldContain("$JS.API.>");
        subjects.ShouldContain("$JS.API.INFO");
        subjects.ShouldContain("$JS.API.META.LEADER.STEPDOWN");
        subjects.ShouldContain("$JS.API.STREAM.NAMES");
        subjects.ShouldContain("$JS.API.STREAM.LIST");
    }

    // Go: setJetStreamExportSubs — all consumer-related wildcards registered
    [Fact]
    public async Task RegisteredApiSubjects_includes_consumer_and_stream_wildcard_subjects()
    {
        var options = new JetStreamOptions();
        await using var svc = new JetStreamService(options);

        await svc.StartAsync(CancellationToken.None);

        var subjects = svc.RegisteredApiSubjects;

        // Stream management
        subjects.ShouldContain(s => s.StartsWith("$JS.API.STREAM.CREATE."), "stream create wildcard");
        subjects.ShouldContain(s => s.StartsWith("$JS.API.STREAM.DELETE."), "stream delete wildcard");
        subjects.ShouldContain(s => s.StartsWith("$JS.API.STREAM.INFO."), "stream info wildcard");
        subjects.ShouldContain(s => s.StartsWith("$JS.API.STREAM.UPDATE."), "stream update wildcard");
        subjects.ShouldContain(s => s.StartsWith("$JS.API.STREAM.PURGE."), "stream purge wildcard");
        subjects.ShouldContain(s => s.StartsWith("$JS.API.STREAM.MSG.GET."), "stream msg get wildcard");
        subjects.ShouldContain(s => s.StartsWith("$JS.API.STREAM.MSG.DELETE."), "stream msg delete wildcard");
        subjects.ShouldContain(s => s.StartsWith("$JS.API.STREAM.SNAPSHOT."), "stream snapshot wildcard");
        subjects.ShouldContain(s => s.StartsWith("$JS.API.STREAM.RESTORE."), "stream restore wildcard");
        subjects.ShouldContain(s => s.StartsWith("$JS.API.STREAM.LEADER.STEPDOWN."), "stream leader stepdown wildcard");

        // Consumer management
        subjects.ShouldContain(s => s.StartsWith("$JS.API.CONSUMER.CREATE."), "consumer create wildcard");
        subjects.ShouldContain(s => s.StartsWith("$JS.API.CONSUMER.DELETE."), "consumer delete wildcard");
        subjects.ShouldContain(s => s.StartsWith("$JS.API.CONSUMER.INFO."), "consumer info wildcard");
        subjects.ShouldContain(s => s.StartsWith("$JS.API.CONSUMER.NAMES."), "consumer names wildcard");
        subjects.ShouldContain(s => s.StartsWith("$JS.API.CONSUMER.LIST."), "consumer list wildcard");
        subjects.ShouldContain(s => s.StartsWith("$JS.API.CONSUMER.PAUSE."), "consumer pause wildcard");
        subjects.ShouldContain(s => s.StartsWith("$JS.API.CONSUMER.MSG.NEXT."), "consumer msg next wildcard");

        // Direct get
        subjects.ShouldContain(s => s.StartsWith("$JS.API.DIRECT.GET."), "direct get wildcard");
    }

    // RegisteredApiSubjects should be empty before start
    [Fact]
    public void RegisteredApiSubjects_is_empty_before_start()
    {
        var options = new JetStreamOptions();
        var svc = new JetStreamService(options);

        svc.RegisteredApiSubjects.ShouldBeEmpty();
    }

    // Go: shutdown path — DisposeAsync clears subjects and marks not running
    [Fact]
    public async Task DisposeAsync_clears_subjects_and_marks_not_running()
    {
        var options = new JetStreamOptions();
        var svc = new JetStreamService(options);

        await svc.StartAsync(CancellationToken.None);
        svc.IsRunning.ShouldBeTrue();
        svc.RegisteredApiSubjects.ShouldNotBeEmpty();

        await svc.DisposeAsync();

        svc.IsRunning.ShouldBeFalse();
        svc.RegisteredApiSubjects.ShouldBeEmpty();
    }

    // MaxStreams and MaxConsumers reflect config values
    [Fact]
    public async Task MaxStreams_and_MaxConsumers_reflect_config_values()
    {
        var options = new JetStreamOptions
        {
            MaxStreams = 100,
            MaxConsumers = 500,
        };
        await using var svc = new JetStreamService(options);

        await svc.StartAsync(CancellationToken.None);

        svc.MaxStreams.ShouldBe(100);
        svc.MaxConsumers.ShouldBe(500);
    }

    // MaxMemory and MaxStore reflect config values
    [Fact]
    public async Task MaxMemory_and_MaxStore_reflect_config_values()
    {
        var options = new JetStreamOptions
        {
            MaxMemoryStore = 1_073_741_824L, // 1 GiB
            MaxFileStore = 10_737_418_240L,  // 10 GiB
        };
        await using var svc = new JetStreamService(options);

        await svc.StartAsync(CancellationToken.None);

        svc.MaxMemory.ShouldBe(1_073_741_824L);
        svc.MaxStore.ShouldBe(10_737_418_240L);
    }

    // Default config values are zero (unlimited)
    [Fact]
    public void Default_config_values_are_unlimited_zero()
    {
        var options = new JetStreamOptions();
        var svc = new JetStreamService(options);

        svc.MaxStreams.ShouldBe(0);
        svc.MaxConsumers.ShouldBe(0);
        svc.MaxMemory.ShouldBe(0L);
        svc.MaxStore.ShouldBe(0L);
    }

    // Go: enableJetStream idempotency — double-start is safe (not an error)
    [Fact]
    public async Task Double_start_is_idempotent()
    {
        var options = new JetStreamOptions();
        await using var svc = new JetStreamService(options);

        await svc.StartAsync(CancellationToken.None);
        var subjectCountAfterFirst = svc.RegisteredApiSubjects.Count;

        // Second start must not throw and must not duplicate subjects
        await svc.StartAsync(CancellationToken.None);

        svc.IsRunning.ShouldBeTrue();
        svc.RegisteredApiSubjects.Count.ShouldBe(subjectCountAfterFirst);
    }

    // Store directory is created with a nested path (MkdirAll semantics)
    [Fact]
    public async Task StartAsync_creates_nested_store_directory()
    {
        var baseDir = MakeTempDir();
        var nestedDir = Path.Combine(baseDir, "level1", "level2", "jetstream");
        var options = new JetStreamOptions { StoreDir = nestedDir };
        await using var svc = new JetStreamService(options);

        await svc.StartAsync(CancellationToken.None);

        svc.IsRunning.ShouldBeTrue();
        Directory.Exists(nestedDir).ShouldBeTrue("nested store directory must be created");
    }

    // Service is not running before start
    [Fact]
    public void IsRunning_is_false_before_start()
    {
        var options = new JetStreamOptions();
        var svc = new JetStreamService(options);

        svc.IsRunning.ShouldBeFalse();
    }
}
