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 UserAuthorizationStateTests
{
    private static readonly DateTime Now = new(2026, 4, 19, 12, 0, 0, DateTimeKind.Utc);

    private static UserAuthorizationState Fresh(DateTime resolved) => new()
    {
        SessionId = "s",
        ClusterId = "c1",
        LdapGroups = ["cn=ops"],
        MembershipResolvedUtc = resolved,
        AuthGenerationId = 1,
        MembershipVersion = 1,
    };

    /// <summary>Verifies that freshly resolved authorization state is neither stale nor needs refresh.</summary>
    [Fact]
    public void FreshlyResolved_Is_NotStale_NorNeedsRefresh()
    {
        var session = Fresh(Now);

        session.IsStale(Now.AddMinutes(1)).ShouldBeFalse();
        session.NeedsRefresh(Now.AddMinutes(1)).ShouldBeFalse();
    }

    /// <summary>Verifies that refresh flag fires after the freshness interval expires.</summary>
    [Fact]
    public void NeedsRefresh_FiresAfter_FreshnessInterval()
    {
        var session = Fresh(Now);

        session.NeedsRefresh(Now.AddMinutes(16)).ShouldBeFalse("past freshness but also past the 15-min staleness ceiling — should be Stale, not NeedsRefresh");
    }

    /// <summary>Verifies that the refresh flag fires within the production default freshness and staleness windows.</summary>
    [Fact]
    public void NeedsRefresh_FiresWithin_ProductionDefault_Windows()
    {
        // Core-001 regression: with the production defaults the refresh window must be
        // non-empty. Freshness defaults to 5 min and staleness to 15 min, so between
        // those two boundaries NeedsRefresh must return true while IsStale stays false.
        var session = Fresh(Now);

        session.MembershipFreshnessInterval.ShouldBeLessThan(session.AuthCacheMaxStaleness);

        session.NeedsRefresh(Now.AddMinutes(10)).ShouldBeTrue("10 min is past the 5-min freshness window but within the 15-min staleness ceiling");
        session.IsStale(Now.AddMinutes(10)).ShouldBeFalse("10 min is still within the 15-min staleness ceiling");
    }

    /// <summary>Verifies that the refresh flag is true between the freshness and staleness windows.</summary>
    [Fact]
    public void NeedsRefresh_TrueBetween_Freshness_And_Staleness_Windows()
    {
        // Custom: freshness=2 min, staleness=10 min → between 2 and 10 min NeedsRefresh fires.
        var session = Fresh(Now) with
        {
            MembershipFreshnessInterval = TimeSpan.FromMinutes(2),
            AuthCacheMaxStaleness = TimeSpan.FromMinutes(10),
        };

        session.NeedsRefresh(Now.AddMinutes(5)).ShouldBeTrue();
        session.IsStale(Now.AddMinutes(5)).ShouldBeFalse();
    }

    /// <summary>Verifies that the stale flag is true after the staleness window expires.</summary>
    [Fact]
    public void IsStale_TrueAfter_StalenessWindow()
    {
        var session = Fresh(Now);

        session.IsStale(Now.AddMinutes(16)).ShouldBeTrue("default AuthCacheMaxStaleness is 15 min");
    }
}
