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

namespace IamService.UnitTests.Domain.AccessControl;

/// <summary>
/// EN: Unit tests for AccessRequest aggregate root.
/// VI: Unit tests cho AccessRequest aggregate root.
/// </summary>
public class AccessRequestTests
{
    private readonly Guid _validRequesterId = Guid.NewGuid();
    private readonly Guid _validResourceId = Guid.NewGuid();

    #region Creation Tests

    [Fact]
    public void Create_ValidParameters_CreatesAccessRequestInDraftStatus()
    {
        // Arrange & Act
        var request = AccessRequest.Create(
            _validRequesterId,
            "Project",
            _validResourceId,
            "read",
            "Need access to project files");

        // Assert
        request.Should().NotBeNull();
        request.Id.Should().NotBeEmpty();
        request.RequesterId.Should().Be(_validRequesterId);
        request.ResourceType.Should().Be("Project");
        request.ResourceId.Should().Be(_validResourceId);
        request.RequestedPermission.Should().Be("read");
        request.Justification.Should().Be("Need access to project files");
        request.Status.Should().Be(AccessRequestStatus.Draft);
        request.Priority.Should().Be(AccessRequestPriority.Medium);
        request.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
        request.Approvers.Should().BeEmpty();
    }

    [Fact]
    public void Create_WithCustomPriority_SetsPriority()
    {
        // Arrange & Act
        var request = AccessRequest.Create(
            _validRequesterId,
            "Project",
            _validResourceId,
            "admin",
            priority: AccessRequestPriority.High);

        // Assert
        request.Priority.Should().Be(AccessRequestPriority.High);
    }

    [Fact]
    public void Create_EmptyRequesterId_ThrowsArgumentException()
    {
        // Arrange & Act
        var act = () => AccessRequest.Create(Guid.Empty, "Project", _validResourceId, "read");

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

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    public void Create_InvalidResourceType_ThrowsArgumentException(string? resourceType)
    {
        // Arrange & Act
        var act = () => AccessRequest.Create(_validRequesterId, resourceType!, _validResourceId, "read");

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

    [Fact]
    public void Create_EmptyResourceId_ThrowsArgumentException()
    {
        // Arrange & Act
        var act = () => AccessRequest.Create(_validRequesterId, "Project", Guid.Empty, "read");

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

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    public void Create_InvalidPermission_ThrowsArgumentException(string? permission)
    {
        // Arrange & Act
        var act = () => AccessRequest.Create(_validRequesterId, "Project", _validResourceId, permission!);

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

    [Fact]
    public void Create_RaisesAccessRequestCreatedEvent()
    {
        // Arrange & Act
        var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");

        // Assert
        request.DomainEvents.Should().ContainSingle();
        request.DomainEvents.First().Should().BeOfType<AccessRequestCreatedEvent>();
    }

    #endregion

    #region AddApprover Tests

    [Fact]
    public void AddApprover_InDraftStatus_AddsApprover()
    {
        // Arrange
        var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
        var approverId = Guid.NewGuid();

        // Act
        var approver = request.AddApprover(approverId);

        // Assert
        request.Approvers.Should().HaveCount(1);
        approver.UserId.Should().Be(approverId);
        approver.Order.Should().Be(1);
    }

    [Fact]
    public void AddApprover_MultipleApprovers_SetsCorrectOrder()
    {
        // Arrange
        var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
        var approver1 = Guid.NewGuid();
        var approver2 = Guid.NewGuid();

        // Act
        request.AddApprover(approver1);
        request.AddApprover(approver2);

        // Assert
        request.Approvers.Should().HaveCount(2);
        request.Approvers.ElementAt(0).Order.Should().Be(1);
        request.Approvers.ElementAt(1).Order.Should().Be(2);
    }

    [Fact]
    public void AddApprover_AfterSubmit_ThrowsInvalidOperationException()
    {
        // Arrange
        var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
        request.AddApprover(Guid.NewGuid());
        request.Submit();

        // Act
        var act = () => request.AddApprover(Guid.NewGuid());

        // Assert
        act.Should().Throw<InvalidOperationException>()
            .WithMessage("*after request is submitted*");
    }

    #endregion

    #region Submit Tests

    [Fact]
    public void Submit_WithApprovers_ChangeStatusToPending()
    {
        // Arrange
        var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
        request.AddApprover(Guid.NewGuid());

        // Act
        request.Submit();

        // Assert
        request.Status.Should().Be(AccessRequestStatus.Pending);
        request.SubmittedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
        request.ExpiresAt.Should().NotBeNull();
    }

    [Fact]
    public void Submit_WithoutApprovers_ThrowsInvalidOperationException()
    {
        // Arrange
        var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");

        // Act
        var act = () => request.Submit();

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

    [Fact]
    public void Submit_NotInDraft_ThrowsInvalidOperationException()
    {
        // Arrange
        var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
        request.AddApprover(Guid.NewGuid());
        request.Submit();

        // Act - Try to submit again
        var act = () => request.Submit();

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

    [Fact]
    public void Submit_RaisesAccessRequestSubmittedEvent()
    {
        // Arrange
        var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
        request.AddApprover(Guid.NewGuid());
        request.ClearDomainEvents();

        // Act
        request.Submit();

        // Assert
        request.DomainEvents.Should().ContainSingle();
        request.DomainEvents.First().Should().BeOfType<AccessRequestSubmittedEvent>();
    }

    #endregion

    #region Approve Tests

    [Fact]
    public void Approve_SingleApprover_ChangesStatusToApproved()
    {
        // Arrange
        var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
        var approverId = Guid.NewGuid();
        request.AddApprover(approverId);
        request.Submit();

        // Act
        request.Approve(approverId, "Looks good");

        // Assert
        request.Status.Should().Be(AccessRequestStatus.Approved);
        request.ResolvedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
    }

    [Fact]
    public void Approve_AllApprovers_ChangesStatusToApproved()
    {
        // Arrange
        var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
        var approver1 = Guid.NewGuid();
        var approver2 = Guid.NewGuid();
        request.AddApprover(approver1);
        request.AddApprover(approver2);
        request.Submit();

        // Act
        request.Approve(approver1);
        request.Approve(approver2);

        // Assert
        request.Status.Should().Be(AccessRequestStatus.Approved);
    }

    [Fact]
    public void Approve_PartialApproval_StatusRemainsPending()
    {
        // Arrange
        var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
        var approver1 = Guid.NewGuid();
        var approver2 = Guid.NewGuid();
        request.AddApprover(approver1);
        request.AddApprover(approver2);
        request.Submit();

        // Act
        request.Approve(approver1);

        // Assert
        request.Status.Should().Be(AccessRequestStatus.Pending);
    }

    [Fact]
    public void Approve_NotPending_ThrowsInvalidOperationException()
    {
        // Arrange
        var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
        var approverId = Guid.NewGuid();
        request.AddApprover(approverId);
        // Not submitted yet

        // Act
        var act = () => request.Approve(approverId);

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

    [Fact]
    public void Approve_NotAnApprover_ThrowsInvalidOperationException()
    {
        // Arrange
        var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
        request.AddApprover(Guid.NewGuid());
        request.Submit();

        // Act - Random user trying to approve
        var act = () => request.Approve(Guid.NewGuid());

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

    #endregion

    #region Reject Tests

    [Fact]
    public void Reject_PendingRequest_ChangesStatusToRejected()
    {
        // Arrange
        var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
        var approverId = Guid.NewGuid();
        request.AddApprover(approverId);
        request.Submit();

        // Act
        request.Reject(approverId, "Not justified");

        // Assert
        request.Status.Should().Be(AccessRequestStatus.Rejected);
        request.ResolvedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
    }

    [Fact]
    public void Reject_RaisesAccessRequestRejectedEvent()
    {
        // Arrange
        var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
        var approverId = Guid.NewGuid();
        request.AddApprover(approverId);
        request.Submit();
        request.ClearDomainEvents();

        // Act
        request.Reject(approverId, "Not justified");

        // Assert
        request.DomainEvents.Should().ContainSingle();
        request.DomainEvents.First().Should().BeOfType<AccessRequestRejectedEvent>();
    }

    #endregion

    #region Cancel Tests

    [Fact]
    public void Cancel_DraftRequest_ChangesStatusToCancelled()
    {
        // Arrange
        var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");

        // Act
        request.Cancel();

        // Assert
        request.Status.Should().Be(AccessRequestStatus.Cancelled);
    }

    [Fact]
    public void Cancel_PendingRequest_ChangesStatusToCancelled()
    {
        // Arrange
        var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
        request.AddApprover(Guid.NewGuid());
        request.Submit();

        // Act
        request.Cancel();

        // Assert
        request.Status.Should().Be(AccessRequestStatus.Cancelled);
    }

    [Fact]
    public void Cancel_ApprovedRequest_ThrowsInvalidOperationException()
    {
        // Arrange
        var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
        var approverId = Guid.NewGuid();
        request.AddApprover(approverId);
        request.Submit();
        request.Approve(approverId);

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

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

    #endregion

    #region Expire Tests

    [Fact]
    public void Expire_PendingRequest_ChangesStatusToExpired()
    {
        // Arrange
        var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
        request.AddApprover(Guid.NewGuid());
        request.Submit();

        // Act
        request.Expire();

        // Assert
        request.Status.Should().Be(AccessRequestStatus.Expired);
        request.ResolvedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
    }

    [Fact]
    public void Expire_NotPending_ThrowsInvalidOperationException()
    {
        // Arrange
        var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");

        // Act
        var act = () => request.Expire();

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

    #endregion

    #region UpdateJustification Tests

    [Fact]
    public void UpdateJustification_InDraft_UpdatesJustification()
    {
        // Arrange
        var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");

        // Act
        request.UpdateJustification("Updated justification");

        // Assert
        request.Justification.Should().Be("Updated justification");
    }

    [Fact]
    public void UpdateJustification_AfterSubmit_ThrowsInvalidOperationException()
    {
        // Arrange
        var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
        request.AddApprover(Guid.NewGuid());
        request.Submit();

        // Act
        var act = () => request.UpdateJustification("Updated");

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

    #endregion
}
