using Xunit;
using FluentAssertions;
using IamService.Domain.AggregatesModel.VerificationAggregate;
using IamService.Domain.Events;

namespace IamService.UnitTests.Domain.Verification;

/// <summary>
/// EN: Unit tests for IdentityVerification aggregate root.
/// VI: Unit tests cho IdentityVerification aggregate root.
/// </summary>
public class IdentityVerificationTests
{
    private readonly Guid _validUserId = Guid.NewGuid();

    #region Phone Verification Creation Tests

    [Fact]
    public void CreatePhoneVerification_ValidParameters_CreatesVerificationAndOtp()
    {
        // Arrange & Act
        var (verification, otp) = IdentityVerification.CreatePhoneVerification(
            _validUserId, "+84901234567");

        // Assert
        verification.Should().NotBeNull();
        verification.Id.Should().NotBeEmpty();
        verification.UserId.Should().Be(_validUserId);
        verification.Type.Should().Be(VerificationType.Phone);
        verification.Status.Should().Be(VerificationStatus.Pending);
        verification.VerificationData.Should().Be("+84901234567");
        verification.VerificationCodeHash.Should().NotBeNullOrEmpty();
        verification.RequestedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
        verification.ExpiresAt.Should().BeAfter(DateTime.UtcNow);
        verification.AttemptCount.Should().Be(0);
        otp.Should().HaveLength(6);
        otp.Should().MatchRegex("^[0-9]{6}$");
    }

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    public void CreatePhoneVerification_InvalidPhoneNumber_ThrowsArgumentException(string? phoneNumber)
    {
        // Arrange & Act
        var act = () => IdentityVerification.CreatePhoneVerification(_validUserId, phoneNumber!);

        // Assert
        act.Should().Throw<ArgumentException>()
            .WithMessage("*Phone number*empty*");
    }

    [Fact]
    public void CreatePhoneVerification_EmptyUserId_ThrowsArgumentException()
    {
        // Arrange & Act
        var act = () => IdentityVerification.CreatePhoneVerification(Guid.Empty, "+84901234567");

        // Assert
        act.Should().Throw<ArgumentException>()
            .WithMessage("*User ID*empty*");
    }

    [Fact]
    public void CreatePhoneVerification_RaisesVerificationRequestedEvent()
    {
        // Arrange & Act
        var (verification, _) = IdentityVerification.CreatePhoneVerification(_validUserId, "+84901234567");

        // Assert
        verification.DomainEvents.Should().ContainSingle();
        verification.DomainEvents.First().Should().BeOfType<VerificationRequestedEvent>();
    }

    #endregion

    #region Document Verification Creation Tests

    [Fact]
    public void CreateDocumentVerification_ValidParameters_CreatesVerification()
    {
        // Arrange & Act
        var verification = IdentityVerification.CreateDocumentVerification(
            _validUserId,
            "https://storage.example.com/documents/id-card.jpg",
            "ID_CARD");

        // Assert
        verification.Should().NotBeNull();
        verification.Id.Should().NotBeEmpty();
        verification.UserId.Should().Be(_validUserId);
        verification.Type.Should().Be(VerificationType.Document);
        verification.Status.Should().Be(VerificationStatus.InProgress);
        verification.VerificationData.Should().Be("https://storage.example.com/documents/id-card.jpg");
        verification.Metadata.Should().Contain("ID_CARD");
    }

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    public void CreateDocumentVerification_InvalidDocumentUrl_ThrowsArgumentException(string? documentUrl)
    {
        // Arrange & Act
        var act = () => IdentityVerification.CreateDocumentVerification(_validUserId, documentUrl!);

        // Assert
        act.Should().Throw<ArgumentException>()
            .WithMessage("*Document URL*empty*");
    }

    #endregion

    #region VerifyCode Tests

    [Fact]
    public void VerifyCode_CorrectCode_ReturnsTrue()
    {
        // Arrange
        var (verification, otp) = IdentityVerification.CreatePhoneVerification(_validUserId, "+84901234567");

        // Act
        var result = verification.VerifyCode(otp);

        // Assert
        result.Should().BeTrue();
        verification.Status.Should().Be(VerificationStatus.Verified);
        verification.VerifiedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
    }

    [Fact]
    public void VerifyCode_IncorrectCode_ReturnsFalse()
    {
        // Arrange
        var (verification, _) = IdentityVerification.CreatePhoneVerification(_validUserId, "+84901234567");

        // Act
        var result = verification.VerifyCode("000000");

        // Assert
        result.Should().BeFalse();
        verification.Status.Should().Be(VerificationStatus.Pending);
        verification.AttemptCount.Should().Be(1);
    }

    [Fact]
    public void VerifyCode_ExceedsMaxAttempts_RejectsVerification()
    {
        // Arrange
        var (verification, _) = IdentityVerification.CreatePhoneVerification(_validUserId, "+84901234567");

        // Act - Try 6 times (max is 5)
        for (int i = 0; i < 6; i++)
        {
            verification.VerifyCode("000000");
        }

        // Assert
        verification.Status.Should().Be(VerificationStatus.Rejected);
        verification.RejectionReason.Should().Contain("Maximum attempts");
    }

    [Fact]
    public void VerifyCode_AlreadyVerified_ThrowsInvalidOperationException()
    {
        // Arrange
        var (verification, otp) = IdentityVerification.CreatePhoneVerification(_validUserId, "+84901234567");
        verification.VerifyCode(otp); // Verify first

        // Act
        var act = () => verification.VerifyCode("000000");

        // Assert
        act.Should().Throw<InvalidOperationException>()
            .WithMessage("*final status*");
    }

    #endregion

    #region MarkAsVerified Tests

    [Fact]
    public void MarkAsVerified_PendingStatus_ChangesToVerified()
    {
        // Arrange
        var verification = IdentityVerification.CreateDocumentVerification(
            _validUserId, "https://example.com/doc.jpg");
        verification.ClearDomainEvents();

        // Act
        verification.MarkAsVerified();

        // Assert
        verification.Status.Should().Be(VerificationStatus.Verified);
        verification.VerifiedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
    }

    [Fact]
    public void MarkAsVerified_RaisesVerificationCompletedEvent()
    {
        // Arrange
        var verification = IdentityVerification.CreateDocumentVerification(
            _validUserId, "https://example.com/doc.jpg");
        verification.ClearDomainEvents();

        // Act
        verification.MarkAsVerified();

        // Assert
        verification.DomainEvents.Should().ContainSingle();
        verification.DomainEvents.First().Should().BeOfType<VerificationCompletedEvent>();
    }

    #endregion

    #region MarkAsRejected Tests

    [Fact]
    public void MarkAsRejected_PendingStatus_ChangesToRejected()
    {
        // Arrange
        var verification = IdentityVerification.CreateDocumentVerification(
            _validUserId, "https://example.com/doc.jpg");

        // Act
        verification.MarkAsRejected("Document is blurry");

        // Assert
        verification.Status.Should().Be(VerificationStatus.Rejected);
        verification.RejectionReason.Should().Be("Document is blurry");
    }

    [Fact]
    public void MarkAsRejected_AlreadyFinal_ThrowsInvalidOperationException()
    {
        // Arrange
        var (verification, otp) = IdentityVerification.CreatePhoneVerification(_validUserId, "+84901234567");
        verification.VerifyCode(otp);

        // Act
        var act = () => verification.MarkAsRejected("Some reason");

        // Assert
        act.Should().Throw<InvalidOperationException>()
            .WithMessage("*final status*");
    }

    #endregion

    #region Cancel Tests

    [Fact]
    public void Cancel_PendingStatus_ChangesToCancelled()
    {
        // Arrange
        var (verification, _) = IdentityVerification.CreatePhoneVerification(_validUserId, "+84901234567");

        // Act
        verification.Cancel();

        // Assert
        verification.Status.Should().Be(VerificationStatus.Cancelled);
    }

    [Fact]
    public void Cancel_FinalStatus_ThrowsInvalidOperationException()
    {
        // Arrange
        var (verification, otp) = IdentityVerification.CreatePhoneVerification(_validUserId, "+84901234567");
        verification.VerifyCode(otp);

        // Act
        var act = () => verification.Cancel();

        // Assert
        act.Should().Throw<InvalidOperationException>()
            .WithMessage("*final status*");
    }

    #endregion

    #region Helper Properties Tests

    [Fact]
    public void IsExpired_BeforeExpiration_ReturnsFalse()
    {
        // Arrange
        var (verification, _) = IdentityVerification.CreatePhoneVerification(_validUserId, "+84901234567");

        // Assert
        verification.IsExpired.Should().BeFalse();
    }

    [Fact]
    public void CanRetry_UnderMaxAttempts_ReturnsTrue()
    {
        // Arrange
        var (verification, _) = IdentityVerification.CreatePhoneVerification(_validUserId, "+84901234567");
        verification.VerifyCode("000000"); // 1 failed attempt

        // Assert
        verification.CanRetry.Should().BeTrue();
    }

    [Fact]
    public void CanRetry_AfterVerified_ReturnsFalse()
    {
        // Arrange
        var (verification, otp) = IdentityVerification.CreatePhoneVerification(_validUserId, "+84901234567");
        verification.VerifyCode(otp);

        // Assert
        verification.CanRetry.Should().BeFalse();
    }

    #endregion
}
