using SIGCM2.Domain.Entities;

namespace SIGCM2.Application.Tests.Domain;

public class RefreshTokenTests
{
    // --- IssueForNewFamily ---

    [Fact]
    public void IssueForNewFamily_SetsNewFamilyIdAndExpiresAt()
    {
        var now = DateTime.UtcNow;
        var ttl = TimeSpan.FromDays(7);

        var token = RefreshToken.IssueForNewFamily(
            usuarioId: 1,
            tokenHash: "hash_abc",
            now: now,
            ttl: ttl,
            createdByIp: "127.0.0.1",
            userAgent: "Mozilla/5.0");

        Assert.Equal(1, token.UsuarioId);
        Assert.Equal("hash_abc", token.TokenHash);
        Assert.NotEqual(Guid.Empty, token.FamilyId);
        Assert.Equal(now, token.IssuedAt);
        Assert.Equal(now + ttl, token.ExpiresAt);
        Assert.Equal("127.0.0.1", token.CreatedByIp);
        Assert.Equal("Mozilla/5.0", token.UserAgent);
        Assert.Null(token.RevokedAt);
        Assert.Null(token.ReplacedById);
    }

    [Fact]
    public void IssueForNewFamily_TwoCallsProduceDifferentFamilyIds()
    {
        var now = DateTime.UtcNow;
        var ttl = TimeSpan.FromDays(7);

        var t1 = RefreshToken.IssueForNewFamily(1, "hash1", now, ttl, "127.0.0.1", null);
        var t2 = RefreshToken.IssueForNewFamily(1, "hash2", now, ttl, "127.0.0.1", null);

        Assert.NotEqual(t1.FamilyId, t2.FamilyId);
    }

    // --- IssueRotation ---

    [Fact]
    public void IssueRotation_InheritsFamilyIdAndExpiresAt()
    {
        var now = DateTime.UtcNow;
        var original = RefreshToken.IssueForNewFamily(
            1, "hash_original", now.AddHours(-2), TimeSpan.FromDays(7), "10.0.0.1", "UA1");

        var rotationTime = now;
        var rotated = RefreshToken.IssueRotation(
            previous: original,
            newTokenHash: "hash_new",
            now: rotationTime,
            createdByIp: "10.0.0.2",
            userAgent: "UA2");

        Assert.Equal(original.FamilyId, rotated.FamilyId);     // same family
        Assert.Equal(original.ExpiresAt, rotated.ExpiresAt);   // ABSOLUTE — inherited
        Assert.Equal(original.UsuarioId, rotated.UsuarioId);
        Assert.Equal("hash_new", rotated.TokenHash);
        Assert.Equal(rotationTime, rotated.IssuedAt);
        Assert.Equal("10.0.0.2", rotated.CreatedByIp);
        Assert.Equal("UA2", rotated.UserAgent);
        Assert.Null(rotated.RevokedAt);
        Assert.Null(rotated.ReplacedById);
    }

    // --- IsActive ---

    [Fact]
    public void IsActive_False_WhenRevoked()
    {
        var now = DateTime.UtcNow;
        var token = RefreshToken.IssueForNewFamily(1, "h", now, TimeSpan.FromDays(7), "1.1.1.1", null);
        token.MarkAsPersistedRevocation(now.AddSeconds(-1), replacedById: null);

        Assert.False(token.IsActive(now));
        Assert.True(token.IsRevoked);
    }

    [Fact]
    public void IsActive_False_WhenExpired()
    {
        var now = DateTime.UtcNow;
        // issued 8 days ago, ttl 7 days → expired yesterday
        var token = RefreshToken.IssueForNewFamily(1, "h", now.AddDays(-8), TimeSpan.FromDays(7), "1.1.1.1", null);

        Assert.False(token.IsActive(now));
        Assert.True(token.IsExpired(now));
        Assert.False(token.IsRevoked);
    }

    [Fact]
    public void IsActive_True_WhenFreshAndNotRevoked()
    {
        var now = DateTime.UtcNow;
        var token = RefreshToken.IssueForNewFamily(1, "h", now, TimeSpan.FromDays(7), "1.1.1.1", null);

        Assert.True(token.IsActive(now.AddMinutes(1)));
        Assert.False(token.IsRevoked);
        Assert.False(token.IsExpired(now.AddMinutes(1)));
    }

    [Fact]
    public void IsExpired_True_AtExpiresAt()
    {
        var now = DateTime.UtcNow;
        var token = RefreshToken.IssueForNewFamily(1, "h", now, TimeSpan.FromDays(7), "1.1.1.1", null);

        // exactly at ExpiresAt it is expired (>= boundary)
        Assert.True(token.IsExpired(token.ExpiresAt));
        // one second before: not expired
        Assert.False(token.IsExpired(token.ExpiresAt.AddSeconds(-1)));
    }

    // --- MarkAsPersistedRevocation ---

    [Fact]
    public void MarkAsPersistedRevocation_SetsRevokedAtAndReplacedById()
    {
        var now = DateTime.UtcNow;
        var token = RefreshToken.IssueForNewFamily(1, "h", now, TimeSpan.FromDays(7), "1.1.1.1", null);

        token.MarkAsPersistedRevocation(now.AddSeconds(30), replacedById: 42);

        Assert.Equal(now.AddSeconds(30), token.RevokedAt);
        Assert.Equal(42, token.ReplacedById);
        Assert.True(token.IsRevoked);
    }
}
