// Port of Go server/reload.go — JetStream config change detection tests.
// Reference: golang/nats-server/server/reload.go — jetStreamOption.Apply.

using NATS.Server.Configuration;
using Shouldly;

namespace NATS.Server.Core.Tests.Configuration;

public sealed class JetStreamConfigReloadTests
{
    // ─── helpers ────────────────────────────────────────────────────

    private static NatsOptions BaseOpts() => new();

    private static NatsOptions OptsWithJs(long maxMemory = 0, long maxStore = 0, string? domain = null) =>
        new()
        {
            JetStream = new JetStreamOptions
            {
                MaxMemoryStore = maxMemory,
                MaxFileStore = maxStore,
                Domain = domain
            }
        };

    // ─── tests ──────────────────────────────────────────────────────

    [Fact]
    public void No_changes_returns_no_changes()
    {
        // Identical JetStream config on both sides → no changes detected.
        var oldOpts = OptsWithJs(maxMemory: 512, maxStore: 1024, domain: "hub");
        var newOpts = OptsWithJs(maxMemory: 512, maxStore: 1024, domain: "hub");

        var result = ConfigReloader.ApplyJetStreamConfigChanges(oldOpts, newOpts);

        result.HasChanges.ShouldBeFalse();
        result.MaxMemoryChanged.ShouldBeFalse();
        result.MaxStoreChanged.ShouldBeFalse();
        result.DomainChanged.ShouldBeFalse();
    }

    [Fact]
    public void MaxMemory_changed_detected()
    {
        // Changing MaxMemoryStore must set MaxMemoryChanged and HasChanges.
        var oldOpts = OptsWithJs(maxMemory: 1024 * 1024);
        var newOpts = OptsWithJs(maxMemory: 2 * 1024 * 1024);

        var result = ConfigReloader.ApplyJetStreamConfigChanges(oldOpts, newOpts);

        result.MaxMemoryChanged.ShouldBeTrue();
        result.HasChanges.ShouldBeTrue();
        result.MaxStoreChanged.ShouldBeFalse();
        result.DomainChanged.ShouldBeFalse();
    }

    [Fact]
    public void MaxStore_changed_detected()
    {
        // Changing MaxFileStore must set MaxStoreChanged and HasChanges.
        var oldOpts = OptsWithJs(maxStore: 10 * 1024 * 1024);
        var newOpts = OptsWithJs(maxStore: 20 * 1024 * 1024);

        var result = ConfigReloader.ApplyJetStreamConfigChanges(oldOpts, newOpts);

        result.MaxStoreChanged.ShouldBeTrue();
        result.HasChanges.ShouldBeTrue();
        result.MaxMemoryChanged.ShouldBeFalse();
        result.DomainChanged.ShouldBeFalse();
    }

    [Fact]
    public void Domain_changed_detected()
    {
        // Changing the domain name must set DomainChanged and HasChanges.
        var oldOpts = OptsWithJs(domain: "hub");
        var newOpts = OptsWithJs(domain: "edge");

        var result = ConfigReloader.ApplyJetStreamConfigChanges(oldOpts, newOpts);

        result.DomainChanged.ShouldBeTrue();
        result.HasChanges.ShouldBeTrue();
        result.MaxMemoryChanged.ShouldBeFalse();
        result.MaxStoreChanged.ShouldBeFalse();
    }

    [Fact]
    public void OldMaxMemory_and_NewMaxMemory_set()
    {
        // When MaxMemoryStore changes the old and new values must be captured.
        var oldOpts = OptsWithJs(maxMemory: 100_000);
        var newOpts = OptsWithJs(maxMemory: 200_000);

        var result = ConfigReloader.ApplyJetStreamConfigChanges(oldOpts, newOpts);

        result.OldMaxMemory.ShouldBe(100_000L);
        result.NewMaxMemory.ShouldBe(200_000L);
    }

    [Fact]
    public void OldMaxStore_and_NewMaxStore_set()
    {
        // When MaxFileStore changes the old and new values must be captured.
        var oldOpts = OptsWithJs(maxStore: 500_000);
        var newOpts = OptsWithJs(maxStore: 1_000_000);

        var result = ConfigReloader.ApplyJetStreamConfigChanges(oldOpts, newOpts);

        result.OldMaxStore.ShouldBe(500_000L);
        result.NewMaxStore.ShouldBe(1_000_000L);
    }

    [Fact]
    public void OldDomain_and_NewDomain_set()
    {
        // When Domain changes both old and new values must be captured.
        var oldOpts = OptsWithJs(domain: "alpha");
        var newOpts = OptsWithJs(domain: "beta");

        var result = ConfigReloader.ApplyJetStreamConfigChanges(oldOpts, newOpts);

        result.OldDomain.ShouldBe("alpha");
        result.NewDomain.ShouldBe("beta");
    }

    [Fact]
    public void Multiple_changes_detected()
    {
        // Changing MaxMemory, MaxStore, and Domain together must flag all three.
        var oldOpts = OptsWithJs(maxMemory: 1024, maxStore: 2048, domain: "primary");
        var newOpts = OptsWithJs(maxMemory: 4096, maxStore: 8192, domain: "secondary");

        var result = ConfigReloader.ApplyJetStreamConfigChanges(oldOpts, newOpts);

        result.MaxMemoryChanged.ShouldBeTrue();
        result.MaxStoreChanged.ShouldBeTrue();
        result.DomainChanged.ShouldBeTrue();
        result.HasChanges.ShouldBeTrue();
    }

    [Fact]
    public void Zero_to_nonzero_memory_detected()
    {
        // Going from the default (0 = unlimited) to a concrete limit must be detected.
        var oldOpts = BaseOpts(); // JetStream is null → effective MaxMemoryStore = 0
        var newOpts = OptsWithJs(maxMemory: 512 * 1024 * 1024);

        var result = ConfigReloader.ApplyJetStreamConfigChanges(oldOpts, newOpts);

        result.MaxMemoryChanged.ShouldBeTrue();
        result.HasChanges.ShouldBeTrue();
        result.OldMaxMemory.ShouldBe(0L);
        result.NewMaxMemory.ShouldBe(512 * 1024 * 1024L);
    }

    [Fact]
    public void Domain_null_to_value_detected()
    {
        // Going from no JetStream config (domain null/empty) to a named domain must be detected.
        var oldOpts = BaseOpts(); // JetStream is null → effective domain = ""
        var newOpts = OptsWithJs(domain: "cloud");

        var result = ConfigReloader.ApplyJetStreamConfigChanges(oldOpts, newOpts);

        result.DomainChanged.ShouldBeTrue();
        result.HasChanges.ShouldBeTrue();
        result.OldDomain.ShouldBe(string.Empty);
        result.NewDomain.ShouldBe("cloud");
    }
}
