// Tests for ConfigReloader.ApplyClusterConfigChanges.
// Go reference: golang/nats-server/server/reload.go — routesOption.Apply, gatewayOption.Apply.

using NATS.Server;
using NATS.Server.Configuration;
using Shouldly;

namespace NATS.Server.Core.Tests.Configuration;

public class ClusterConfigReloadTests
{
    // ─── helpers ────────────────────────────────────────────────────

    private static NatsOptions WithRoutes(params string[] routes)
    {
        var opts = new NatsOptions();
        opts.Cluster = new ClusterOptions { Routes = [..routes] };
        return opts;
    }

    private static NatsOptions WithGatewayRemotes(params string[] urls)
    {
        var opts = new NatsOptions();
        opts.Gateway = new GatewayOptions
        {
            RemoteGateways = [new RemoteGatewayOptions { Urls = [..urls] }]
        };
        return opts;
    }

    private static NatsOptions WithLeafRemotes(params string[] urls)
    {
        var opts = new NatsOptions();
        opts.LeafNode = new LeafNodeOptions { Remotes = [..urls] };
        return opts;
    }

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

    [Fact]
    public void No_changes_returns_no_changes()
    {
        // Go reference: reload.go routesOption.Apply — no-op when sets are equal
        var old = WithRoutes("nats://server1:6222", "nats://server2:6222");
        var updated = WithRoutes("nats://server1:6222", "nats://server2:6222");

        var result = ConfigReloader.ApplyClusterConfigChanges(old, updated);

        result.HasChanges.ShouldBeFalse();
        result.RouteUrlsChanged.ShouldBeFalse();
        result.GatewayUrlsChanged.ShouldBeFalse();
        result.LeafUrlsChanged.ShouldBeFalse();
    }

    [Fact]
    public void Route_url_added_detected()
    {
        // Go reference: reload.go routesOption.Apply — new route triggers reconnect
        var old = WithRoutes("nats://server1:6222");
        var updated = WithRoutes("nats://server1:6222", "nats://server2:6222");

        var result = ConfigReloader.ApplyClusterConfigChanges(old, updated);

        result.HasChanges.ShouldBeTrue();
        result.RouteUrlsChanged.ShouldBeTrue();
    }

    [Fact]
    public void Route_url_removed_detected()
    {
        // Go reference: reload.go routesOption.Apply — removed route triggers disconnect
        var old = WithRoutes("nats://server1:6222", "nats://server2:6222");
        var updated = WithRoutes("nats://server1:6222");

        var result = ConfigReloader.ApplyClusterConfigChanges(old, updated);

        result.HasChanges.ShouldBeTrue();
        result.RouteUrlsChanged.ShouldBeTrue();
    }

    [Fact]
    public void Gateway_url_changed_detected()
    {
        // Go reference: reload.go gatewayOption.Apply — gateway remotes reconciled on reload
        var old = WithGatewayRemotes("nats://gw1:7222");
        var updated = WithGatewayRemotes("nats://gw2:7222");

        var result = ConfigReloader.ApplyClusterConfigChanges(old, updated);

        result.HasChanges.ShouldBeTrue();
        result.GatewayUrlsChanged.ShouldBeTrue();
    }

    [Fact]
    public void Leaf_url_changed_detected()
    {
        // Go reference: reload.go leafNodeOption.Apply — leaf remotes reconciled on reload
        var old = WithLeafRemotes("nats://hub:5222");
        var updated = WithLeafRemotes("nats://hub2:5222");

        var result = ConfigReloader.ApplyClusterConfigChanges(old, updated);

        result.HasChanges.ShouldBeTrue();
        result.LeafUrlsChanged.ShouldBeTrue();
    }

    [Fact]
    public void Multiple_changes_detected()
    {
        // Go reference: reload.go — multiple topology changes in a single reload
        var old = new NatsOptions
        {
            Cluster = new ClusterOptions { Routes = ["nats://r1:6222"] },
            Gateway = new GatewayOptions { RemoteGateways = [new RemoteGatewayOptions { Urls = ["nats://gw1:7222"] }] }
        };
        var updated = new NatsOptions
        {
            Cluster = new ClusterOptions { Routes = ["nats://r1:6222", "nats://r2:6222"] },
            Gateway = new GatewayOptions { RemoteGateways = [new RemoteGatewayOptions { Urls = ["nats://gw2:7222"] }] }
        };

        var result = ConfigReloader.ApplyClusterConfigChanges(old, updated);

        result.HasChanges.ShouldBeTrue();
        result.RouteUrlsChanged.ShouldBeTrue();
        result.GatewayUrlsChanged.ShouldBeTrue();
        result.LeafUrlsChanged.ShouldBeFalse();
    }

    [Fact]
    public void Same_urls_different_order_no_change()
    {
        // Go reference: reload.go — order-independent URL comparison
        var old = WithRoutes("nats://server1:6222", "nats://server2:6222");
        var updated = WithRoutes("nats://server2:6222", "nats://server1:6222");

        var result = ConfigReloader.ApplyClusterConfigChanges(old, updated);

        result.HasChanges.ShouldBeFalse();
        result.RouteUrlsChanged.ShouldBeFalse();
    }

    [Fact]
    public void AddedRouteUrls_lists_new_routes()
    {
        // Go reference: reload.go routesOption.Apply — identifies routes to dial
        var old = WithRoutes("nats://server1:6222");
        var updated = WithRoutes("nats://server1:6222", "nats://server2:6222", "nats://server3:6222");

        var result = ConfigReloader.ApplyClusterConfigChanges(old, updated);

        result.AddedRouteUrls.Count.ShouldBe(2);
        result.AddedRouteUrls.ShouldContain("nats://server2:6222");
        result.AddedRouteUrls.ShouldContain("nats://server3:6222");
        result.RemovedRouteUrls.ShouldBeEmpty();
    }

    [Fact]
    public void RemovedRouteUrls_lists_removed_routes()
    {
        // Go reference: reload.go routesOption.Apply — identifies routes to close
        var old = WithRoutes("nats://server1:6222", "nats://server2:6222", "nats://server3:6222");
        var updated = WithRoutes("nats://server1:6222");

        var result = ConfigReloader.ApplyClusterConfigChanges(old, updated);

        result.RemovedRouteUrls.Count.ShouldBe(2);
        result.RemovedRouteUrls.ShouldContain("nats://server2:6222");
        result.RemovedRouteUrls.ShouldContain("nats://server3:6222");
        result.AddedRouteUrls.ShouldBeEmpty();
    }

    [Fact]
    public void Empty_to_non_empty_detected()
    {
        // Go reference: reload.go routesOption.Apply — nil→populated triggers dial
        var old = new NatsOptions();   // no Cluster configured
        var updated = WithRoutes("nats://server1:6222");

        var result = ConfigReloader.ApplyClusterConfigChanges(old, updated);

        result.HasChanges.ShouldBeTrue();
        result.RouteUrlsChanged.ShouldBeTrue();
        result.AddedRouteUrls.ShouldContain("nats://server1:6222");
    }
}
