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

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

public sealed class EmailRateLimitTests
{
    private readonly Guid _tenantId = Guid.NewGuid();
    private const string TestEmail = "user@example.com";
    private const string OperationType = "verification";

    [Fact]
    public void Create_WithValidData_ShouldSucceed()
    {
        // Arrange & Act
        var rateLimit = EmailRateLimit.Create(TestEmail, _tenantId, OperationType);

        // Assert
        rateLimit.Should().NotBeNull();
        rateLimit.Id.Should().NotBe(Guid.Empty);
        rateLimit.Email.Should().Be(TestEmail.ToLower());
        rateLimit.TenantId.Should().Be(_tenantId);
        rateLimit.OperationType.Should().Be(OperationType);
        rateLimit.AttemptsCount.Should().Be(1);
        rateLimit.LastSentAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
    }

    [Fact]
    public void Create_ShouldNormalizeEmail()
    {
        // Arrange
        const string mixedCaseEmail = "User@EXAMPLE.COM";

        // Act
        var rateLimit = EmailRateLimit.Create(mixedCaseEmail, _tenantId, OperationType);

        // Assert
        rateLimit.Email.Should().Be("user@example.com");
    }

    [Fact]
    public void Create_WithEmptyEmail_ShouldThrowException()
    {
        // Arrange & Act
        var act = () => EmailRateLimit.Create(string.Empty, _tenantId, OperationType);

        // Assert
        act.Should().Throw<ArgumentException>()
            .WithMessage("*Email cannot be empty*");
    }

    [Fact]
    public void Create_WithEmptyOperationType_ShouldThrowException()
    {
        // Arrange & Act
        var act = () => EmailRateLimit.Create(TestEmail, _tenantId, string.Empty);

        // Assert
        act.Should().Throw<ArgumentException>()
            .WithMessage("*Operation type cannot be empty*");
    }

    [Fact]
    public void RecordAttempt_ShouldIncrementCount()
    {
        // Arrange
        var rateLimit = EmailRateLimit.Create(TestEmail, _tenantId, OperationType);
        var initialCount = rateLimit.AttemptsCount;
        var initialLastSentAt = rateLimit.LastSentAt;

        // Wait a bit to ensure time difference
        System.Threading.Thread.Sleep(10);

        // Act
        rateLimit.RecordAttempt();

        // Assert
        rateLimit.AttemptsCount.Should().Be(initialCount + 1);
        rateLimit.LastSentAt.Should().BeAfter(initialLastSentAt);
    }

    [Fact]
    public void RecordAttempt_MultipleCallsMultiple_ShouldIncrementCorrectly()
    {
        // Arrange
        var rateLimit = EmailRateLimit.Create(TestEmail, _tenantId, OperationType);

        // Act
        rateLimit.RecordAttempt();
        rateLimit.RecordAttempt();
        rateLimit.RecordAttempt();

        // Assert
        rateLimit.AttemptsCount.Should().Be(4); // 1 initial + 3 increments
    }

    [Fact]
    public void ResetAttempts_ShouldResetCountToOne()
    {
        // Arrange
        var rateLimit = EmailRateLimit.Create(TestEmail, _tenantId, OperationType);
        rateLimit.RecordAttempt();
        rateLimit.RecordAttempt();
        rateLimit.RecordAttempt();

        // Act
        rateLimit.ResetAttempts();

        // Assert
        rateLimit.AttemptsCount.Should().Be(1);
    }

    [Fact]
    public void ResetAttempts_ShouldUpdateLastSentAt()
    {
        // Arrange
        var rateLimit = EmailRateLimit.Create(TestEmail, _tenantId, OperationType);
        var initialLastSentAt = rateLimit.LastSentAt;

        // Wait a bit to ensure time difference
        System.Threading.Thread.Sleep(10);

        // Act
        rateLimit.ResetAttempts();

        // Assert
        rateLimit.LastSentAt.Should().BeAfter(initialLastSentAt);
        rateLimit.LastSentAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
    }

    [Fact]
    public void IsWindowExpired_WithinWindow_ShouldReturnFalse()
    {
        // Arrange
        var rateLimit = EmailRateLimit.Create(TestEmail, _tenantId, OperationType);
        var window = TimeSpan.FromMinutes(5);

        // Act
        var isExpired = rateLimit.IsWindowExpired(window);

        // Assert
        isExpired.Should().BeFalse();
    }

    [Fact]
    public void IsWindowExpired_OutsideWindow_ShouldReturnTrue()
    {
        // Arrange
        var rateLimit = EmailRateLimit.Create(TestEmail, _tenantId, OperationType);
        var window = TimeSpan.FromMilliseconds(1);

        // Wait for window to expire
        System.Threading.Thread.Sleep(10);

        // Act
        var isExpired = rateLimit.IsWindowExpired(window);

        // Assert
        isExpired.Should().BeTrue();
    }

    [Fact]
    public void IsWindowExpired_WithZeroWindow_ShouldReturnTrue()
    {
        // Arrange
        var rateLimit = EmailRateLimit.Create(TestEmail, _tenantId, OperationType);
        var window = TimeSpan.Zero;

        // Act
        var isExpired = rateLimit.IsWindowExpired(window);

        // Assert
        isExpired.Should().BeTrue();
    }
}
