// Tests for Account JWT activation claim expiration: RegisterActivation,
// CheckActivationExpiry, IsActivationExpired, GetExpiredActivations,
// RemoveExpiredActivations, and ActiveActivationCount.
// Go reference: server/accounts.go — checkActivation (~line 2943),
//               activationExpired (~line 2920).

using NATS.Server.Auth;

namespace NATS.Server.Auth.Tests.Auth;

public class ActivationExpirationTests
{
    // Helpers for well-past / well-future dates to avoid timing flakiness.
    private static DateTime WellFuture => DateTime.UtcNow.AddDays(30);
    private static DateTime WellPast => DateTime.UtcNow.AddDays(-30);

    private static ActivationClaim ValidClaim(string subject) => new()
    {
        Subject = subject,
        IssuedAt = DateTime.UtcNow.AddDays(-1),
        ExpiresAt = WellFuture,
        Issuer = "AABC123",
    };

    private static ActivationClaim ExpiredClaim(string subject) => new()
    {
        Subject = subject,
        IssuedAt = DateTime.UtcNow.AddDays(-60),
        ExpiresAt = WellPast,
        Issuer = "AABC123",
    };

    // ---------------------------------------------------------------------------
    // RegisterActivation
    // ---------------------------------------------------------------------------

    [Fact]
    public void RegisterActivation_StoresActivation()
    {
        // Go ref: accounts.go — checkActivation stores the decoded activation claim.
        var account = new Account("test");
        var claim = ValidClaim("svc.foo");

        account.RegisterActivation("svc.foo", claim);

        var result = account.CheckActivationExpiry("svc.foo");
        result.Found.ShouldBeTrue();
    }

    // ---------------------------------------------------------------------------
    // CheckActivationExpiry
    // ---------------------------------------------------------------------------

    [Fact]
    public void CheckActivationExpiry_Valid_NotExpired()
    {
        // Go ref: accounts.go — act.Expires > tn ⇒ checkActivation returns true (not expired).
        var account = new Account("test");
        account.RegisterActivation("svc.valid", ValidClaim("svc.valid"));

        var result = account.CheckActivationExpiry("svc.valid");

        result.Found.ShouldBeTrue();
        result.IsExpired.ShouldBeFalse();
        result.ExpiresAt.ShouldNotBeNull();
        result.TimeToExpiry.ShouldNotBeNull();
        result.TimeToExpiry!.Value.ShouldBeGreaterThan(TimeSpan.Zero);
    }

    [Fact]
    public void CheckActivationExpiry_Expired_ReturnsExpired()
    {
        // Go ref: accounts.go — act.Expires <= tn ⇒ checkActivation returns false (expired).
        var account = new Account("test");
        account.RegisterActivation("svc.expired", ExpiredClaim("svc.expired"));

        var result = account.CheckActivationExpiry("svc.expired");

        result.Found.ShouldBeTrue();
        result.IsExpired.ShouldBeTrue();
        result.ExpiresAt.ShouldNotBeNull();
        result.TimeToExpiry.ShouldBe(TimeSpan.Zero);
    }

    [Fact]
    public void CheckActivationExpiry_NotFound()
    {
        // Go ref: accounts.go — checkActivation returns false when claim is nil/empty token.
        var account = new Account("test");

        var result = account.CheckActivationExpiry("svc.unknown");

        result.Found.ShouldBeFalse();
        result.IsExpired.ShouldBeFalse();
        result.ExpiresAt.ShouldBeNull();
        result.TimeToExpiry.ShouldBeNull();
    }

    // ---------------------------------------------------------------------------
    // IsActivationExpired
    // ---------------------------------------------------------------------------

    [Fact]
    public void IsActivationExpired_Valid_ReturnsFalse()
    {
        // Go ref: accounts.go — act.Expires > tn ⇒ not expired.
        var account = new Account("test");
        account.RegisterActivation("svc.ok", ValidClaim("svc.ok"));

        account.IsActivationExpired("svc.ok").ShouldBeFalse();
    }

    [Fact]
    public void IsActivationExpired_Expired_ReturnsTrue()
    {
        // Go ref: accounts.go — act.Expires <= tn ⇒ expired, activationExpired fires.
        var account = new Account("test");
        account.RegisterActivation("svc.past", ExpiredClaim("svc.past"));

        account.IsActivationExpired("svc.past").ShouldBeTrue();
    }

    // ---------------------------------------------------------------------------
    // GetExpiredActivations
    // ---------------------------------------------------------------------------

    [Fact]
    public void GetExpiredActivations_ReturnsOnlyExpired()
    {
        // Go ref: accounts.go — activationExpired is called only for expired claims.
        var account = new Account("test");
        account.RegisterActivation("svc.a", ValidClaim("svc.a"));
        account.RegisterActivation("svc.b", ExpiredClaim("svc.b"));
        account.RegisterActivation("svc.c", ValidClaim("svc.c"));
        account.RegisterActivation("svc.d", ExpiredClaim("svc.d"));

        var expired = account.GetExpiredActivations();

        expired.Count.ShouldBe(2);
        expired.ShouldContain("svc.b");
        expired.ShouldContain("svc.d");
        expired.ShouldNotContain("svc.a");
        expired.ShouldNotContain("svc.c");
    }

    // ---------------------------------------------------------------------------
    // RemoveExpiredActivations
    // ---------------------------------------------------------------------------

    [Fact]
    public void RemoveExpiredActivations_RemovesAndReturnsCount()
    {
        // Go ref: accounts.go — activationExpired removes the import when activation expires.
        var account = new Account("test");
        account.RegisterActivation("svc.live", ValidClaim("svc.live"));
        account.RegisterActivation("svc.gone1", ExpiredClaim("svc.gone1"));
        account.RegisterActivation("svc.gone2", ExpiredClaim("svc.gone2"));

        var removed = account.RemoveExpiredActivations();

        removed.ShouldBe(2);

        // The expired ones should no longer be found.
        account.CheckActivationExpiry("svc.gone1").Found.ShouldBeFalse();
        account.CheckActivationExpiry("svc.gone2").Found.ShouldBeFalse();

        // The live one should still be registered.
        account.CheckActivationExpiry("svc.live").Found.ShouldBeTrue();
    }

    // ---------------------------------------------------------------------------
    // ActiveActivationCount
    // ---------------------------------------------------------------------------

    [Fact]
    public void ActiveActivationCount_ExcludesExpired()
    {
        // Go ref: accounts.go — only non-expired activations are considered active.
        var account = new Account("test");
        account.RegisterActivation("svc.1", ValidClaim("svc.1"));
        account.RegisterActivation("svc.2", ValidClaim("svc.2"));
        account.RegisterActivation("svc.3", ExpiredClaim("svc.3"));

        account.ActiveActivationCount.ShouldBe(2);
    }

    // ---------------------------------------------------------------------------
    // ActivationClaim.TimeToExpiry
    // ---------------------------------------------------------------------------

    [Fact]
    public void ActivationClaim_TimeToExpiry_Zero_WhenExpired()
    {
        // Go ref: accounts.go — expired activation has no remaining time.
        var claim = ExpiredClaim("svc.expired");

        claim.IsExpired.ShouldBeTrue();
        claim.TimeToExpiry.ShouldBe(TimeSpan.Zero);
    }
}
