using ColaFlow.Modules.Identity.Domain.Aggregates.Users;
using ColaFlow.Modules.Identity.Domain.Entities;
using FluentAssertions;
using Xunit;

namespace ColaFlow.Modules.Identity.Domain.Tests.Entities;

public sealed class PasswordResetTokenTests
{
    private readonly UserId _userId = UserId.CreateUnique();
    private const string TestTokenHash = "hashed_token_value";
    private const string TestIpAddress = "192.168.1.1";
    private const string TestUserAgent = "Mozilla/5.0";

    [Fact]
    public void Create_WithValidData_ShouldSucceed()
    {
        // Arrange
        var expiresAt = DateTime.UtcNow.AddHours(1);

        // Act
        var token = PasswordResetToken.Create(
            _userId,
            TestTokenHash,
            expiresAt,
            TestIpAddress,
            TestUserAgent);

        // Assert
        token.Should().NotBeNull();
        token.Id.Should().NotBe(Guid.Empty);
        token.UserId.Should().Be(_userId);
        token.TokenHash.Should().Be(TestTokenHash);
        token.ExpiresAt.Should().Be(expiresAt);
        token.IpAddress.Should().Be(TestIpAddress);
        token.UserAgent.Should().Be(TestUserAgent);
        token.UsedAt.Should().BeNull();
        token.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
        token.IsExpired.Should().BeFalse();
        token.IsUsed.Should().BeFalse();
        token.IsValid.Should().BeTrue();
    }

    [Fact]
    public void Create_WithoutOptionalParameters_ShouldSucceed()
    {
        // Arrange
        var expiresAt = DateTime.UtcNow.AddHours(1);

        // Act
        var token = PasswordResetToken.Create(_userId, TestTokenHash, expiresAt);

        // Assert
        token.Should().NotBeNull();
        token.IpAddress.Should().BeNull();
        token.UserAgent.Should().BeNull();
    }

    [Fact]
    public void IsExpired_WithFutureExpiration_ShouldReturnFalse()
    {
        // Arrange
        var expiresAt = DateTime.UtcNow.AddHours(1);
        var token = PasswordResetToken.Create(_userId, TestTokenHash, expiresAt);

        // Act & Assert
        token.IsExpired.Should().BeFalse();
    }

    [Fact]
    public void IsExpired_WithPastExpiration_ShouldReturnTrue()
    {
        // Arrange
        var expiresAt = DateTime.UtcNow.AddHours(-1);
        var token = PasswordResetToken.Create(_userId, TestTokenHash, expiresAt);

        // Act & Assert
        token.IsExpired.Should().BeTrue();
    }

    [Fact]
    public void IsUsed_WhenNotUsed_ShouldReturnFalse()
    {
        // Arrange
        var expiresAt = DateTime.UtcNow.AddHours(1);
        var token = PasswordResetToken.Create(_userId, TestTokenHash, expiresAt);

        // Act & Assert
        token.IsUsed.Should().BeFalse();
    }

    [Fact]
    public void IsUsed_WhenUsed_ShouldReturnTrue()
    {
        // Arrange
        var expiresAt = DateTime.UtcNow.AddHours(1);
        var token = PasswordResetToken.Create(_userId, TestTokenHash, expiresAt);
        token.MarkAsUsed();

        // Act & Assert
        token.IsUsed.Should().BeTrue();
    }

    [Fact]
    public void IsValid_WithValidToken_ShouldReturnTrue()
    {
        // Arrange
        var expiresAt = DateTime.UtcNow.AddHours(1);
        var token = PasswordResetToken.Create(_userId, TestTokenHash, expiresAt);

        // Act & Assert
        token.IsValid.Should().BeTrue();
    }

    [Fact]
    public void IsValid_WithExpiredToken_ShouldReturnFalse()
    {
        // Arrange
        var expiresAt = DateTime.UtcNow.AddHours(-1);
        var token = PasswordResetToken.Create(_userId, TestTokenHash, expiresAt);

        // Act & Assert
        token.IsValid.Should().BeFalse();
    }

    [Fact]
    public void IsValid_WithUsedToken_ShouldReturnFalse()
    {
        // Arrange
        var expiresAt = DateTime.UtcNow.AddHours(1);
        var token = PasswordResetToken.Create(_userId, TestTokenHash, expiresAt);
        token.MarkAsUsed();

        // Act & Assert
        token.IsValid.Should().BeFalse();
    }

    [Fact]
    public void MarkAsUsed_WithValidToken_ShouldSetUsedAt()
    {
        // Arrange
        var expiresAt = DateTime.UtcNow.AddHours(1);
        var token = PasswordResetToken.Create(_userId, TestTokenHash, expiresAt);

        // Act
        token.MarkAsUsed();

        // Assert
        token.UsedAt.Should().NotBeNull();
        token.UsedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
        token.IsUsed.Should().BeTrue();
        token.IsValid.Should().BeFalse();
    }

    [Fact]
    public void MarkAsUsed_WithExpiredToken_ShouldThrowException()
    {
        // Arrange
        var expiresAt = DateTime.UtcNow.AddHours(-1);
        var token = PasswordResetToken.Create(_userId, TestTokenHash, expiresAt);

        // Act
        var act = () => token.MarkAsUsed();

        // Assert
        act.Should().Throw<InvalidOperationException>()
            .WithMessage("*Token is not valid for password reset*");
    }

    [Fact]
    public void MarkAsUsed_WithAlreadyUsedToken_ShouldThrowException()
    {
        // Arrange
        var expiresAt = DateTime.UtcNow.AddHours(1);
        var token = PasswordResetToken.Create(_userId, TestTokenHash, expiresAt);
        token.MarkAsUsed();

        // Act
        var act = () => token.MarkAsUsed();

        // Assert
        act.Should().Throw<InvalidOperationException>()
            .WithMessage("*Token is not valid for password reset*");
    }

    [Fact]
    public void Create_WithShortExpiration_ShouldBeSecure()
    {
        // Arrange - Tokens should expire quickly for security (e.g., 1 hour)
        var expiresAt = DateTime.UtcNow.AddHours(1);

        // Act
        var token = PasswordResetToken.Create(_userId, TestTokenHash, expiresAt);

        // Assert
        token.ExpiresAt.Should().BeBefore(DateTime.UtcNow.AddHours(2));
    }

    [Fact]
    public void MarkAsUsed_PreventTokenReuse_ShouldEnforceSingleUse()
    {
        // Arrange
        var expiresAt = DateTime.UtcNow.AddHours(1);
        var token = PasswordResetToken.Create(_userId, TestTokenHash, expiresAt);

        // Act - Use token once
        token.MarkAsUsed();

        // Assert - Subsequent use should fail
        var act = () => token.MarkAsUsed();
        act.Should().Throw<InvalidOperationException>()
            .WithMessage("*Token is not valid for password reset*");
    }
}
