// Tests for per-account JetStream resource limits.
// Go reference: accounts_test.go TestAccountLimits, TestJetStreamLimits.

using NATS.Server.Auth;

namespace NATS.Server.Auth.Tests.Auth;

public class AccountLimitsTests
{
    [Fact]
    public void TryReserveConsumer_UnderLimit_ReturnsTrue()
    {
        var account = new Account("test")
        {
            JetStreamLimits = new AccountLimits { MaxConsumers = 3 },
        };

        account.TryReserveConsumer().ShouldBeTrue();
        account.TryReserveConsumer().ShouldBeTrue();
        account.TryReserveConsumer().ShouldBeTrue();
        account.ConsumerCount.ShouldBe(3);
    }

    [Fact]
    public void TryReserveConsumer_AtLimit_ReturnsFalse()
    {
        var account = new Account("test")
        {
            JetStreamLimits = new AccountLimits { MaxConsumers = 2 },
        };

        account.TryReserveConsumer().ShouldBeTrue();
        account.TryReserveConsumer().ShouldBeTrue();
        account.TryReserveConsumer().ShouldBeFalse();
        account.ConsumerCount.ShouldBe(2);
    }

    [Fact]
    public void ReleaseConsumer_DecrementsCount()
    {
        var account = new Account("test")
        {
            JetStreamLimits = new AccountLimits { MaxConsumers = 2 },
        };

        account.TryReserveConsumer().ShouldBeTrue();
        account.TryReserveConsumer().ShouldBeTrue();
        account.ConsumerCount.ShouldBe(2);

        account.ReleaseConsumer();
        account.ConsumerCount.ShouldBe(1);

        // Now we can reserve again
        account.TryReserveConsumer().ShouldBeTrue();
        account.ConsumerCount.ShouldBe(2);
    }

    [Fact]
    public void TrackStorageDelta_UnderLimit_ReturnsTrue()
    {
        var account = new Account("test")
        {
            JetStreamLimits = new AccountLimits { MaxStorage = 1000 },
        };

        account.TrackStorageDelta(500).ShouldBeTrue();
        account.StorageUsed.ShouldBe(500);

        account.TrackStorageDelta(400).ShouldBeTrue();
        account.StorageUsed.ShouldBe(900);
    }

    [Fact]
    public void TrackStorageDelta_ExceedsLimit_ReturnsFalse()
    {
        var account = new Account("test")
        {
            JetStreamLimits = new AccountLimits { MaxStorage = 1000 },
        };

        account.TrackStorageDelta(800).ShouldBeTrue();
        account.TrackStorageDelta(300).ShouldBeFalse(); // 800 + 300 = 1100 > 1000
        account.StorageUsed.ShouldBe(800); // unchanged
    }

    [Fact]
    public void TrackStorageDelta_NegativeDelta_ReducesUsage()
    {
        var account = new Account("test")
        {
            JetStreamLimits = new AccountLimits { MaxStorage = 1000 },
        };

        account.TrackStorageDelta(800).ShouldBeTrue();
        account.TrackStorageDelta(-300).ShouldBeTrue(); // negative always succeeds
        account.StorageUsed.ShouldBe(500);

        // Now we have room again
        account.TrackStorageDelta(400).ShouldBeTrue();
        account.StorageUsed.ShouldBe(900);
    }

    [Fact]
    public void MaxStorage_Zero_Unlimited()
    {
        var account = new Account("test")
        {
            JetStreamLimits = new AccountLimits { MaxStorage = 0 }, // unlimited
        };

        // Should accept any amount
        account.TrackStorageDelta(long.MaxValue / 2).ShouldBeTrue();
        account.StorageUsed.ShouldBe(long.MaxValue / 2);
    }

    [Fact]
    public void Limits_DefaultValues_AllUnlimited()
    {
        var limits = AccountLimits.Unlimited;

        limits.MaxStorage.ShouldBe(0);
        limits.MaxStreams.ShouldBe(0);
        limits.MaxConsumers.ShouldBe(0);
        limits.MaxAckPending.ShouldBe(0);
        limits.MaxMemoryStorage.ShouldBe(0);
        limits.MaxDiskStorage.ShouldBe(0);

        // Account defaults to unlimited
        var account = new Account("test");
        account.JetStreamLimits.ShouldBe(AccountLimits.Unlimited);
    }

    [Fact]
    public void TryReserveStream_WithLimits_RespectsNewLimits()
    {
        // JetStreamLimits.MaxStreams should take precedence over MaxJetStreamStreams
        var account = new Account("test")
        {
            MaxJetStreamStreams = 10, // legacy field
            JetStreamLimits = new AccountLimits { MaxStreams = 2 }, // new limit overrides
        };

        account.TryReserveStream().ShouldBeTrue();
        account.TryReserveStream().ShouldBeTrue();
        account.TryReserveStream().ShouldBeFalse(); // limited to 2 by JetStreamLimits
        account.JetStreamStreamCount.ShouldBe(2);
    }

    [Fact]
    public void EvictOldestClient_WhenMaxConnectionsExceeded()
    {
        var account = new Account("test")
        {
            MaxConnections = 2,
        };

        account.AddClient(1).ShouldBeTrue();
        account.AddClient(2).ShouldBeTrue();
        account.AddClient(3).ShouldBeFalse(); // at limit
        account.ClientCount.ShouldBe(2);

        // Remove oldest, then new one can connect
        account.RemoveClient(1);
        account.ClientCount.ShouldBe(1);

        account.AddClient(3).ShouldBeTrue();
        account.ClientCount.ShouldBe(2);
    }
}
