// Port of Go server/reload.go — auth change propagation tests.
// Reference: golang/nats-server/server/reload.go — authOption.Apply, usersOption.Apply.

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

namespace NATS.Server.Core.Tests.Configuration;

public sealed class AuthChangePropagationTests
{
    // ─── helpers ────────────────────────────────────────────────────

    private static User MakeUser(string username, string password = "pw") =>
        new() { Username = username, Password = password };

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

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

    [Fact]
    public void No_changes_returns_no_changes()
    {
        // Same empty options → nothing changed.
        var oldOpts = BaseOpts();
        var newOpts = BaseOpts();

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

        result.HasChanges.ShouldBeFalse();
        result.UsersChanged.ShouldBeFalse();
        result.AccountsChanged.ShouldBeFalse();
        result.TokenChanged.ShouldBeFalse();
    }

    [Fact]
    public void User_added_detected()
    {
        // Adding a user must set UsersChanged.
        var oldOpts = BaseOpts();
        var newOpts = BaseOpts();
        newOpts.Users = [MakeUser("alice")];

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

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

    [Fact]
    public void User_removed_detected()
    {
        // Removing a user must set UsersChanged.
        var oldOpts = BaseOpts();
        oldOpts.Users = [MakeUser("alice")];
        var newOpts = BaseOpts();

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

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

    [Fact]
    public void Account_added_detected()
    {
        // Adding an account must set AccountsChanged.
        var oldOpts = BaseOpts();
        var newOpts = BaseOpts();
        newOpts.Accounts = new Dictionary<string, AccountConfig>
        {
            ["engineering"] = new AccountConfig()
        };

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

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

    [Fact]
    public void Token_changed_detected()
    {
        // Changing the Authorization token must set TokenChanged.
        var oldOpts = BaseOpts();
        oldOpts.Authorization = "old-secret-token";
        var newOpts = BaseOpts();
        newOpts.Authorization = "new-secret-token";

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

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

    [Fact]
    public void Multiple_changes_all_flagged()
    {
        // Changing both users and accounts must set both flags.
        var oldOpts = BaseOpts();
        oldOpts.Users = [MakeUser("alice")];
        oldOpts.Accounts = new Dictionary<string, AccountConfig>
        {
            ["acct-a"] = new AccountConfig()
        };

        var newOpts = BaseOpts();
        newOpts.Users = [MakeUser("alice"), MakeUser("bob")];
        newOpts.Accounts = new Dictionary<string, AccountConfig>
        {
            ["acct-a"] = new AccountConfig(),
            ["acct-b"] = new AccountConfig()
        };

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

        result.UsersChanged.ShouldBeTrue();
        result.AccountsChanged.ShouldBeTrue();
        result.HasChanges.ShouldBeTrue();
    }

    [Fact]
    public void Same_users_different_order_no_change()
    {
        // Users in a different order with the same names must NOT trigger UsersChanged
        // because the comparison is set-based.
        var oldOpts = BaseOpts();
        oldOpts.Users = [MakeUser("alice"), MakeUser("bob")];

        var newOpts = BaseOpts();
        newOpts.Users = [MakeUser("bob"), MakeUser("alice")];

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

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

    [Fact]
    public void HasChanges_true_when_any_change()
    {
        // A single changed field (token only) is enough to set HasChanges.
        var oldOpts = BaseOpts();
        var newOpts = BaseOpts();
        newOpts.Authorization = "token-xyz";

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

        result.HasChanges.ShouldBeTrue();
    }

    [Fact]
    public void Empty_to_non_empty_users_detected()
    {
        // Going from zero users to one user must be detected.
        var oldOpts = BaseOpts();
        // No Users assigned — null list.
        var newOpts = BaseOpts();
        newOpts.Users = [MakeUser("charlie")];

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

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

    [Fact]
    public void No_auth_to_auth_detected()
    {
        // Going from null Authorization to a token string must be detected.
        var oldOpts = BaseOpts();
        // Authorization is null by default.
        var newOpts = BaseOpts();
        newOpts.Authorization = "brand-new-token";

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

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

    [Fact]
    public void Same_token_no_change()
    {
        // The same token value on both sides must NOT flag TokenChanged.
        var oldOpts = BaseOpts();
        oldOpts.Authorization = "stable-token";
        var newOpts = BaseOpts();
        newOpts.Authorization = "stable-token";

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

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