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

namespace IamService.UnitTests.Domain.AccessControl;

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

    #region Creation Tests

    [Fact]
    public void Create_ValidParameters_CreatesAccessReviewInDraftStatus()
    {
        // Arrange
        var dueDate = DateTime.UtcNow.AddDays(7);

        // Act
        var review = AccessReview.Create(
            "Q1 Access Review",
            "Quarterly access certification",
            _validOwnerId,
            "Organization:org-123",
            dueDate);

        // Assert
        review.Should().NotBeNull();
        review.Id.Should().NotBeEmpty();
        review.Name.Should().Be("Q1 Access Review");
        review.Description.Should().Be("Quarterly access certification");
        review.OwnerId.Should().Be(_validOwnerId);
        review.Scope.Should().Be("Organization:org-123");
        review.Status.Should().Be(AccessReviewStatus.Draft);
        review.DueDate.Should().Be(dueDate);
        review.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
        review.Items.Should().BeEmpty();
    }

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    public void Create_InvalidName_ThrowsArgumentException(string? name)
    {
        // Arrange & Act
        var act = () => AccessReview.Create(
            name!,
            null,
            _validOwnerId,
            "Role:admin",
            DateTime.UtcNow.AddDays(7));

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

    [Fact]
    public void Create_EmptyOwnerId_ThrowsArgumentException()
    {
        // Arrange & Act
        var act = () => AccessReview.Create(
            "Test Review",
            null,
            Guid.Empty,
            "Role:admin",
            DateTime.UtcNow.AddDays(7));

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

    [Fact]
    public void Create_PastDueDate_ThrowsArgumentException()
    {
        // Arrange & Act
        var act = () => AccessReview.Create(
            "Test Review",
            null,
            _validOwnerId,
            "Role:admin",
            DateTime.UtcNow.AddDays(-1));

        // Assert
        act.Should().Throw<ArgumentException>()
            .WithMessage("*Due date*future*");
    }

    [Fact]
    public void Create_RaisesAccessReviewCreatedEvent()
    {
        // Act
        var review = AccessReview.Create(
            "Test Review",
            null,
            _validOwnerId,
            "Role:admin",
            DateTime.UtcNow.AddDays(7));

        // Assert
        review.DomainEvents.Should().ContainSingle();
        review.DomainEvents.First().Should().BeOfType<AccessReviewCreatedEvent>();
    }

    #endregion

    #region AddItem Tests

    [Fact]
    public void AddItem_InDraftStatus_AddsReviewItem()
    {
        // Arrange
        var review = CreateDraftReview();
        var userId = Guid.NewGuid();
        var resourceId = Guid.NewGuid();

        // Act
        var item = review.AddItem(userId, "Project", resourceId, "write");

        // Assert
        review.Items.Should().HaveCount(1);
        item.UserId.Should().Be(userId);
        item.ResourceType.Should().Be("Project");
        item.Permission.Should().Be("write");
    }

    [Fact]
    public void AddItem_AfterStart_ThrowsInvalidOperationException()
    {
        // Arrange
        var review = CreateDraftReview();
        review.AddItem(Guid.NewGuid(), "Project", Guid.NewGuid(), "read");
        review.Start();

        // Act
        var act = () => review.AddItem(Guid.NewGuid(), "Project", Guid.NewGuid(), "write");

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

    #endregion

    #region Start Tests

    [Fact]
    public void Start_WithItems_ChangesStatusToActive()
    {
        // Arrange
        var review = CreateDraftReview();
        review.AddItem(Guid.NewGuid(), "Project", Guid.NewGuid(), "read");

        // Act
        review.Start();

        // Assert
        review.Status.Should().Be(AccessReviewStatus.Active);
        review.StartedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
    }

    [Fact]
    public void Start_WithoutItems_ThrowsInvalidOperationException()
    {
        // Arrange
        var review = CreateDraftReview();

        // Act
        var act = () => review.Start();

        // Assert
        act.Should().Throw<InvalidOperationException>()
            .WithMessage("*at least one item*");
    }

    [Fact]
    public void Start_NotInDraft_ThrowsInvalidOperationException()
    {
        // Arrange
        var review = CreateDraftReview();
        review.AddItem(Guid.NewGuid(), "Project", Guid.NewGuid(), "read");
        review.Start();

        // Act
        var act = () => review.Start();

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

    [Fact]
    public void Start_RaisesAccessReviewStartedEvent()
    {
        // Arrange
        var review = CreateDraftReview();
        review.AddItem(Guid.NewGuid(), "Project", Guid.NewGuid(), "read");
        review.ClearDomainEvents();

        // Act
        review.Start();

        // Assert
        review.DomainEvents.Should().ContainSingle();
        review.DomainEvents.First().Should().BeOfType<AccessReviewStartedEvent>();
    }

    #endregion

    #region ReviewItem Tests

    [Fact]
    public void ReviewItem_Certify_SetsDecisionToCertify()
    {
        // Arrange
        var review = CreateActiveReviewWithItem(out var item);
        var reviewerId = Guid.NewGuid();

        // Act
        review.ReviewItem(item.Id, reviewerId, certify: true, "Approved access");

        // Assert
        item.Decision.Should().Be(ReviewDecision.Certify);
        item.ReviewedByUserId.Should().Be(reviewerId);
    }

    [Fact]
    public void ReviewItem_Revoke_SetsDecisionToRevoke()
    {
        // Arrange
        var review = CreateActiveReviewWithItem(out var item);
        var reviewerId = Guid.NewGuid();

        // Act
        review.ReviewItem(item.Id, reviewerId, certify: false, "Access no longer needed");

        // Assert
        item.Decision.Should().Be(ReviewDecision.Revoke);
    }

    [Fact]
    public void ReviewItem_NotActive_ThrowsInvalidOperationException()
    {
        // Arrange
        var review = CreateDraftReview();
        review.AddItem(Guid.NewGuid(), "Project", Guid.NewGuid(), "read");

        // Act
        var act = () => review.ReviewItem(Guid.NewGuid(), Guid.NewGuid(), true);

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

    [Fact]
    public void ReviewItem_InvalidItemId_ThrowsInvalidOperationException()
    {
        // Arrange
        var review = CreateActiveReviewWithItem(out _);

        // Act
        var act = () => review.ReviewItem(Guid.NewGuid(), Guid.NewGuid(), true);

        // Assert
        act.Should().Throw<InvalidOperationException>()
            .WithMessage("*Item not found*");
    }

    #endregion

    #region Complete Tests

    [Fact]
    public void Complete_AllItemsReviewed_ChangesStatusToCompleted()
    {
        // Arrange
        var review = CreateActiveReviewWithItem(out var item);
        review.ReviewItem(item.Id, Guid.NewGuid(), true);

        // Act
        review.Complete();

        // Assert
        review.Status.Should().Be(AccessReviewStatus.Completed);
        review.CompletedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
    }

    [Fact]
    public void Complete_PendingItems_ThrowsInvalidOperationException()
    {
        // Arrange
        var review = CreateActiveReviewWithItem(out _);

        // Act
        var act = () => review.Complete();

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

    [Fact]
    public void Complete_NotActive_ThrowsInvalidOperationException()
    {
        // Arrange
        var review = CreateDraftReview();

        // Act
        var act = () => review.Complete();

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

    [Fact]
    public void Complete_RaisesAccessReviewCompletedEvent()
    {
        // Arrange
        var review = CreateActiveReviewWithItem(out var item);
        review.ReviewItem(item.Id, Guid.NewGuid(), true);
        review.ClearDomainEvents();

        // Act
        review.Complete();

        // Assert
        review.DomainEvents.Should().ContainSingle();
        review.DomainEvents.First().Should().BeOfType<AccessReviewCompletedEvent>();
    }

    #endregion

    #region Cancel Tests

    [Fact]
    public void Cancel_DraftReview_ChangesStatusToCancelled()
    {
        // Arrange
        var review = CreateDraftReview();

        // Act
        review.Cancel();

        // Assert
        review.Status.Should().Be(AccessReviewStatus.Cancelled);
    }

    [Fact]
    public void Cancel_ActiveReview_ChangesStatusToCancelled()
    {
        // Arrange
        var review = CreateActiveReviewWithItem(out _);

        // Act
        review.Cancel();

        // Assert
        review.Status.Should().Be(AccessReviewStatus.Cancelled);
    }

    [Fact]
    public void Cancel_CompletedReview_ThrowsInvalidOperationException()
    {
        // Arrange
        var review = CreateActiveReviewWithItem(out var item);
        review.ReviewItem(item.Id, Guid.NewGuid(), true);
        review.Complete();

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

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

    #endregion

    #region Helper Methods

    private AccessReview CreateDraftReview() =>
        AccessReview.Create("Test Review", null, _validOwnerId, "Role:admin", DateTime.UtcNow.AddDays(7));

    private AccessReview CreateActiveReviewWithItem(out AccessReviewItem item)
    {
        var review = CreateDraftReview();
        item = review.AddItem(Guid.NewGuid(), "Project", Guid.NewGuid(), "read");
        review.Start();
        return review;
    }

    #endregion
}
