using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Authorization;

namespace ZB.MOM.WW.OtOpcUa.Core.Tests.Authorization;

[Trait("Category", "Unit")]
public sealed class PermissionTrieCacheTests
{
    private static PermissionTrie Trie(string cluster, long generation) => new()
    {
        ClusterId = cluster,
        GenerationId = generation,
    };

    [Fact]
    public void GetTrie_Empty_ReturnsNull()
    {
        new PermissionTrieCache().GetTrie("c1").ShouldBeNull();
    }

    [Fact]
    public void Install_ThenGet_RoundTrips()
    {
        var cache = new PermissionTrieCache();
        cache.Install(Trie("c1", 5));

        cache.GetTrie("c1")!.GenerationId.ShouldBe(5);
        cache.CurrentGenerationId("c1").ShouldBe(5);
    }

    [Fact]
    public void NewGeneration_BecomesCurrent()
    {
        var cache = new PermissionTrieCache();
        cache.Install(Trie("c1", 1));
        cache.Install(Trie("c1", 2));

        cache.CurrentGenerationId("c1").ShouldBe(2);
        cache.GetTrie("c1", 1).ShouldNotBeNull("prior generation retained for in-flight requests");
        cache.GetTrie("c1", 2).ShouldNotBeNull();
    }

    [Fact]
    public void OutOfOrder_Install_DoesNotDowngrade_Current()
    {
        var cache = new PermissionTrieCache();
        cache.Install(Trie("c1", 3));
        cache.Install(Trie("c1", 1)); // late-arriving older generation

        cache.CurrentGenerationId("c1").ShouldBe(3, "older generation must not become current");
        cache.GetTrie("c1", 1).ShouldNotBeNull("but older is still retrievable by explicit lookup");
    }

    [Fact]
    public void Invalidate_DropsCluster()
    {
        var cache = new PermissionTrieCache();
        cache.Install(Trie("c1", 1));
        cache.Install(Trie("c2", 1));

        cache.Invalidate("c1");

        cache.GetTrie("c1").ShouldBeNull();
        cache.GetTrie("c2").ShouldNotBeNull("sibling cluster unaffected");
    }

    [Fact]
    public void Prune_RetainsMostRecent()
    {
        var cache = new PermissionTrieCache();
        for (var g = 1L; g <= 5; g++) cache.Install(Trie("c1", g));

        cache.Prune("c1", keepLatest: 2);

        cache.GetTrie("c1", 5).ShouldNotBeNull();
        cache.GetTrie("c1", 4).ShouldNotBeNull();
        cache.GetTrie("c1", 3).ShouldBeNull();
        cache.GetTrie("c1", 1).ShouldBeNull();
    }

    [Fact]
    public void Prune_LessThanKeep_IsNoOp()
    {
        var cache = new PermissionTrieCache();
        cache.Install(Trie("c1", 1));
        cache.Install(Trie("c1", 2));

        cache.Prune("c1", keepLatest: 10);

        cache.CachedTrieCount.ShouldBe(2);
    }

    [Fact]
    public void ClusterIsolation()
    {
        var cache = new PermissionTrieCache();
        cache.Install(Trie("c1", 1));
        cache.Install(Trie("c2", 9));

        cache.CurrentGenerationId("c1").ShouldBe(1);
        cache.CurrentGenerationId("c2").ShouldBe(9);
    }
}
