using NATS.Server.Auth;
using Shouldly;

namespace NATS.Server.Auth.Tests.Auth;

/// <summary>
/// Tests for SUB permission caching and generation-based invalidation.
/// Reference: Go server/client.go — subPermCache, pubPermCache, perm cache invalidation on account update.
/// </summary>
public sealed class SubPermissionCacheTests
{
    // ── SUB API ───────────────────────────────────────────────────────────────

    [Fact]
    public void SetSub_and_TryGetSub_round_trips()
    {
        var cache = new PermissionLruCache();

        cache.SetSub("foo.bar", true);

        cache.TryGetSub("foo.bar", out var result).ShouldBeTrue();
        result.ShouldBeTrue();
    }

    [Fact]
    public void TryGetSub_returns_false_for_unknown()
    {
        var cache = new PermissionLruCache();

        cache.TryGetSub("unknown.subject", out var result).ShouldBeFalse();
        result.ShouldBeFalse();
    }

    [Fact]
    public void PUB_and_SUB_stored_independently()
    {
        var cache = new PermissionLruCache();

        // Same logical subject, different PUB/SUB outcomes
        cache.Set("orders.>", false);   // PUB denied
        cache.SetSub("orders.>", true); // SUB allowed

        cache.TryGet("orders.>", out var pubAllowed).ShouldBeTrue();
        pubAllowed.ShouldBeFalse();

        cache.TryGetSub("orders.>", out var subAllowed).ShouldBeTrue();
        subAllowed.ShouldBeTrue();
    }

    // ── Invalidation ─────────────────────────────────────────────────────────

    [Fact]
    public void Invalidate_clears_on_next_access()
    {
        var cache = new PermissionLruCache();

        cache.Set("pub.subject", true);
        cache.SetSub("sub.subject", true);

        cache.Invalidate();

        // Both PUB and SUB lookups should miss after invalidation
        cache.TryGet("pub.subject", out _).ShouldBeFalse();
        cache.TryGetSub("sub.subject", out _).ShouldBeFalse();
    }

    [Fact]
    public void Generation_increments_on_invalidate()
    {
        var cache = new PermissionLruCache();

        var before = cache.Generation;
        cache.Invalidate();
        var afterOne = cache.Generation;
        cache.Invalidate();
        var afterTwo = cache.Generation;

        afterOne.ShouldBe(before + 1);
        afterTwo.ShouldBe(before + 2);
    }

    // ── LRU eviction ─────────────────────────────────────────────────────────

    [Fact]
    public void LRU_eviction_applies_to_SUB_entries()
    {
        // capacity = 4: fill with 4 SUB entries then add a 5th; the oldest should be evicted
        var cache = new PermissionLruCache(capacity: 4);

        cache.SetSub("a", true);
        cache.SetSub("b", true);
        cache.SetSub("c", true);
        cache.SetSub("d", true);

        // Touch "a" so it becomes MRU; "b" becomes LRU
        cache.TryGetSub("a", out _);

        // Adding "e" should evict "b" (LRU)
        cache.SetSub("e", true);

        cache.Count.ShouldBe(4);
        cache.TryGetSub("b", out _).ShouldBeFalse("b should have been evicted");
        cache.TryGetSub("e", out _).ShouldBeTrue("e was just added");
    }

    // ── Backward compatibility ────────────────────────────────────────────────

    [Fact]
    public void Existing_PUB_API_still_works()
    {
        var cache = new PermissionLruCache();

        cache.Set("pub.only", true);

        cache.TryGet("pub.only", out var value).ShouldBeTrue();
        value.ShouldBeTrue();

        // Overwrite with false
        cache.Set("pub.only", false);
        cache.TryGet("pub.only", out value).ShouldBeTrue();
        value.ShouldBeFalse();
    }

    // ── Account.GenerationId ──────────────────────────────────────────────────

    [Fact]
    public void Account_GenerationId_starts_at_zero()
    {
        var account = new Account("test");

        account.GenerationId.ShouldBe(0L);
    }

    [Fact]
    public void Account_IncrementGeneration_increments()
    {
        var account = new Account("test");

        account.IncrementGeneration();
        account.GenerationId.ShouldBe(1L);

        account.IncrementGeneration();
        account.GenerationId.ShouldBe(2L);
    }

    // ── Mixed PUB + SUB count ─────────────────────────────────────────────────

    [Fact]
    public void Mixed_PUB_SUB_count_includes_both()
    {
        var cache = new PermissionLruCache();

        cache.Set("pub.a", true);
        cache.Set("pub.b", false);
        cache.SetSub("sub.a", true);
        cache.SetSub("sub.b", false);

        // All four entries (stored under different internal keys) contribute to Count
        cache.Count.ShouldBe(4);
    }
}
