// Tests for account claim hot-reload with diff-based update detection.
// Go reference: accounts_test.go TestUpdateAccountClaims, updateAccountClaimsWithRefresh (~line 3374).

using NATS.Server.Auth;

namespace NATS.Server.Auth.Tests.Auth;

public class AccountClaimReloadTests
{
    // 1. First update: all provided fields are reported as changed.
    [Fact]
    public void UpdateAccountClaims_FirstUpdate_AllFieldsChanged()
    {
        var account = new Account("test");
        var claims = new AccountClaimData
        {
            MaxConnections = 10,
            MaxSubscriptions = 100,
            Nkey = "NKEY123",
            Issuer = "ISSUER_OP",
            ExpiresAt = new DateTime(2030, 1, 1, 0, 0, 0, DateTimeKind.Utc),
        };

        var result = account.UpdateAccountClaims(claims);

        result.Changed.ShouldBeTrue();
        result.ChangedFields.ShouldContain(nameof(AccountClaimData.MaxConnections));
        result.ChangedFields.ShouldContain(nameof(AccountClaimData.MaxSubscriptions));
        result.ChangedFields.ShouldContain(nameof(AccountClaimData.Nkey));
        result.ChangedFields.ShouldContain(nameof(AccountClaimData.Issuer));
        result.ChangedFields.ShouldContain(nameof(AccountClaimData.ExpiresAt));
        result.ChangedFields.Count.ShouldBe(5);
    }

    // 2. Applying the exact same claims a second time returns Changed=false.
    [Fact]
    public void UpdateAccountClaims_NoChange_ReturnsFalse()
    {
        var account = new Account("test");
        var claims = new AccountClaimData
        {
            MaxConnections = 5,
            MaxSubscriptions = 50,
            Nkey = "NKEY_A",
            Issuer = "OP",
        };

        account.UpdateAccountClaims(claims);
        var result = account.UpdateAccountClaims(claims);

        result.Changed.ShouldBeFalse();
        result.ChangedFields.Count.ShouldBe(0);
    }

    // 3. Changing MaxConnections is detected.
    [Fact]
    public void UpdateAccountClaims_MaxConnectionsChanged_Detected()
    {
        var account = new Account("test");
        var initial = new AccountClaimData { MaxConnections = 10 };
        account.UpdateAccountClaims(initial);

        var updated = new AccountClaimData { MaxConnections = 20 };
        var result = account.UpdateAccountClaims(updated);

        result.Changed.ShouldBeTrue();
        result.ChangedFields.ShouldContain(nameof(AccountClaimData.MaxConnections));
        account.MaxConnections.ShouldBe(20);
    }

    // 4. Changing MaxSubscriptions is detected.
    [Fact]
    public void UpdateAccountClaims_MaxSubscriptionsChanged_Detected()
    {
        var account = new Account("test");
        var initial = new AccountClaimData { MaxSubscriptions = 100 };
        account.UpdateAccountClaims(initial);

        var updated = new AccountClaimData { MaxSubscriptions = 200 };
        var result = account.UpdateAccountClaims(updated);

        result.Changed.ShouldBeTrue();
        result.ChangedFields.ShouldContain(nameof(AccountClaimData.MaxSubscriptions));
        account.MaxSubscriptions.ShouldBe(200);
    }

    // 5. Changing Nkey is detected.
    [Fact]
    public void UpdateAccountClaims_NkeyChanged_Detected()
    {
        var account = new Account("test");
        var initial = new AccountClaimData { Nkey = "OLD_NKEY" };
        account.UpdateAccountClaims(initial);

        var updated = new AccountClaimData { Nkey = "NEW_NKEY" };
        var result = account.UpdateAccountClaims(updated);

        result.Changed.ShouldBeTrue();
        result.ChangedFields.ShouldContain(nameof(AccountClaimData.Nkey));
        account.Nkey.ShouldBe("NEW_NKEY");
    }

    // 6. Changing Issuer is detected.
    [Fact]
    public void UpdateAccountClaims_IssuerChanged_Detected()
    {
        var account = new Account("test");
        var initial = new AccountClaimData { Issuer = "ISSUER_A" };
        account.UpdateAccountClaims(initial);

        var updated = new AccountClaimData { Issuer = "ISSUER_B" };
        var result = account.UpdateAccountClaims(updated);

        result.Changed.ShouldBeTrue();
        result.ChangedFields.ShouldContain(nameof(AccountClaimData.Issuer));
        account.Issuer.ShouldBe("ISSUER_B");
    }

    // 7. A successful claim update increments the generation counter.
    [Fact]
    public void UpdateAccountClaims_IncrementsGeneration()
    {
        var account = new Account("test");
        var before = account.GenerationId;

        var claims = new AccountClaimData { MaxConnections = 5 };
        account.UpdateAccountClaims(claims);

        account.GenerationId.ShouldBe(before + 1);
    }

    // 8. HasClaims is false on a fresh account.
    [Fact]
    public void HasClaims_BeforeUpdate_ReturnsFalse()
    {
        var account = new Account("test");
        account.HasClaims.ShouldBeFalse();
    }

    // 9. HasClaims is true after the first update.
    [Fact]
    public void HasClaims_AfterUpdate_ReturnsTrue()
    {
        var account = new Account("test");
        var claims = new AccountClaimData { MaxConnections = 1 };
        account.UpdateAccountClaims(claims);

        account.HasClaims.ShouldBeTrue();
        account.CurrentClaims.ShouldNotBeNull();
        account.CurrentClaims!.MaxConnections.ShouldBe(1);
    }

    // 10. ClaimUpdateCount increments only when claims actually change.
    [Fact]
    public void ClaimUpdateCount_IncrementsOnChange()
    {
        var account = new Account("test");
        account.ClaimUpdateCount.ShouldBe(0);

        var claimsA = new AccountClaimData { MaxConnections = 5 };
        account.UpdateAccountClaims(claimsA);
        account.ClaimUpdateCount.ShouldBe(1);

        // Reapplying same claims does NOT increment count.
        account.UpdateAccountClaims(claimsA);
        account.ClaimUpdateCount.ShouldBe(1);

        // Applying different claims does increment.
        var claimsB = new AccountClaimData { MaxConnections = 10 };
        account.UpdateAccountClaims(claimsB);
        account.ClaimUpdateCount.ShouldBe(2);
    }
}
