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

namespace IamService.UnitTests.Domain.Groups;

/// <summary>
/// EN: Comprehensive unit tests for Group aggregate root.
/// VI: Kiểm thử đơn vị toàn diện cho Group aggregate root.
/// </summary>
public class GroupTests
{
    private readonly Guid _validOrganizationId = Guid.NewGuid();

    #region Creation Tests

    [Fact]
    public void Create_ValidParameters_CreatesGroupWithGeneratedId()
    {
        // Arrange
        var name = "Development Team";
        var description = "Main development team";

        // Act
        var group = Group.Create(_validOrganizationId, name, description);

        // Assert
        group.Should().NotBeNull();
        group.Id.Should().NotBeEmpty();
        group.Name.Should().Be(name);
        group.Description.Should().Be(description);
        group.OrganizationId.Should().Be(_validOrganizationId);
        group.IsDeleted.Should().BeFalse();
        group.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
        group.Members.Should().BeEmpty();
        group.Permissions.Should().BeEmpty();
    }

    [Fact]
    public void Create_WithoutDescription_CreatesGroupWithNullDescription()
    {
        // Arrange & Act
        var group = Group.Create(_validOrganizationId, "Team Name");

        // Assert
        group.Description.Should().BeNull();
    }

    [Fact]
    public void Create_EmptyOrganizationId_ThrowsArgumentException()
    {
        // Arrange & Act
        var act = () => Group.Create(Guid.Empty, "Team Name");

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

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    public void Create_InvalidName_ThrowsArgumentException(string? name)
    {
        // Arrange & Act
        var act = () => Group.Create(_validOrganizationId, name!);

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

    [Fact]
    public void Create_TrimsNameAndDescription()
    {
        // Arrange & Act
        var group = Group.Create(_validOrganizationId, "  Team Name  ", "  Description  ");

        // Assert
        group.Name.Should().Be("Team Name");
        group.Description.Should().Be("Description");
    }

    [Fact]
    public void Create_RaisesGroupCreatedEvent()
    {
        // Arrange & Act
        var group = Group.Create(_validOrganizationId, "Team Name");

        // Assert
        group.DomainEvents.Should().ContainSingle();
        group.DomainEvents.First().Should().BeOfType<GroupCreatedEvent>();
        var domainEvent = (GroupCreatedEvent)group.DomainEvents.First();
        domainEvent.GroupId.Should().Be(group.Id);
        domainEvent.OrganizationId.Should().Be(_validOrganizationId);
        domainEvent.Name.Should().Be("Team Name");
    }

    #endregion

    #region Update Tests

    [Fact]
    public void Update_ValidData_UpdatesNameAndDescription()
    {
        // Arrange
        var group = Group.Create(_validOrganizationId, "Old Name", "Old Description");
        var newName = "New Name";
        var newDescription = "New Description";

        // Act
        group.Update(newName, newDescription);

        // Assert
        group.Name.Should().Be(newName);
        group.Description.Should().Be(newDescription);
        group.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
    }

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    public void Update_InvalidName_ThrowsArgumentException(string? name)
    {
        // Arrange
        var group = Group.Create(_validOrganizationId, "Old Name");

        // Act
        var act = () => group.Update(name!, "Description");

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

    #endregion

    #region AddMember Tests

    [Fact]
    public void AddMember_ValidUser_AddsMemberToGroup()
    {
        // Arrange
        var group = Group.Create(_validOrganizationId, "Team Name");
        var userId = Guid.NewGuid();

        // Act
        var member = group.AddMember(userId);

        // Assert
        group.Members.Should().HaveCount(1);
        member.UserId.Should().Be(userId);
        member.GroupId.Should().Be(group.Id);
        member.Role.Should().Be(GroupRole.Member);
    }

    [Fact]
    public void AddMember_WithRole_AddsMemberWithSpecifiedRole()
    {
        // Arrange
        var group = Group.Create(_validOrganizationId, "Team Name");
        var userId = Guid.NewGuid();

        // Act
        var member = group.AddMember(userId, GroupRole.Admin);

        // Assert
        member.Role.Should().Be(GroupRole.Admin);
    }

    [Fact]
    public void AddMember_WithAddedByUser_TracksWhoAdded()
    {
        // Arrange
        var group = Group.Create(_validOrganizationId, "Team Name");
        var userId = Guid.NewGuid();
        var addedByUserId = Guid.NewGuid();

        // Act
        var member = group.AddMember(userId, addedByUserId: addedByUserId);

        // Assert
        member.AddedByUserId.Should().Be(addedByUserId);
    }

    [Fact]
    public void AddMember_DuplicateUser_ThrowsInvalidOperationException()
    {
        // Arrange
        var group = Group.Create(_validOrganizationId, "Team Name");
        var userId = Guid.NewGuid();
        group.AddMember(userId);

        // Act
        var act = () => group.AddMember(userId);

        // Assert
        act.Should().Throw<InvalidOperationException>()
            .WithMessage("*already a member*");
    }

    [Fact]
    public void AddMember_EmptyUserId_ThrowsArgumentException()
    {
        // Arrange
        var group = Group.Create(_validOrganizationId, "Team Name");

        // Act
        var act = () => group.AddMember(Guid.Empty);

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

    [Fact]
    public void AddMember_RaisesMemberAddedToGroupEvent()
    {
        // Arrange
        var group = Group.Create(_validOrganizationId, "Team Name");
        group.ClearDomainEvents(); // Clear creation event
        var userId = Guid.NewGuid();

        // Act
        group.AddMember(userId);

        // Assert
        group.DomainEvents.Should().ContainSingle();
        group.DomainEvents.First().Should().BeOfType<MemberAddedToGroupEvent>();
    }

    #endregion

    #region RemoveMember Tests

    [Fact]
    public void RemoveMember_ExistingMember_RemovesMember()
    {
        // Arrange
        var group = Group.Create(_validOrganizationId, "Team Name");
        var userId = Guid.NewGuid();
        group.AddMember(userId);

        // Act
        group.RemoveMember(userId);

        // Assert
        group.Members.Should().BeEmpty();
    }

    [Fact]
    public void RemoveMember_NonExistingMember_ThrowsInvalidOperationException()
    {
        // Arrange
        var group = Group.Create(_validOrganizationId, "Team Name");

        // Act
        var act = () => group.RemoveMember(Guid.NewGuid());

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

    [Fact]
    public void RemoveMember_LastOwner_ThrowsInvalidOperationException()
    {
        // Arrange
        var group = Group.Create(_validOrganizationId, "Team Name");
        var ownerId = Guid.NewGuid();
        group.AddMember(ownerId, GroupRole.Owner);

        // Act
        var act = () => group.RemoveMember(ownerId);

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

    [Fact]
    public void RemoveMember_OwnerWithOtherOwners_RemovesMember()
    {
        // Arrange
        var group = Group.Create(_validOrganizationId, "Team Name");
        var owner1 = Guid.NewGuid();
        var owner2 = Guid.NewGuid();
        group.AddMember(owner1, GroupRole.Owner);
        group.AddMember(owner2, GroupRole.Owner);

        // Act
        group.RemoveMember(owner1);

        // Assert
        group.Members.Should().HaveCount(1);
        group.Members.First().UserId.Should().Be(owner2);
    }

    [Fact]
    public void RemoveMember_RaisesMemberRemovedFromGroupEvent()
    {
        // Arrange
        var group = Group.Create(_validOrganizationId, "Team Name");
        var userId = Guid.NewGuid();
        group.AddMember(userId);
        group.ClearDomainEvents();

        // Act
        group.RemoveMember(userId);

        // Assert
        group.DomainEvents.Should().ContainSingle();
        group.DomainEvents.First().Should().BeOfType<MemberRemovedFromGroupEvent>();
    }

    #endregion

    #region ChangeMemberRole Tests

    [Fact]
    public void ChangeMemberRole_ValidRole_ChangesRole()
    {
        // Arrange
        var group = Group.Create(_validOrganizationId, "Team Name");
        var userId = Guid.NewGuid();
        group.AddMember(userId, GroupRole.Member);

        // Act
        group.ChangeMemberRole(userId, GroupRole.Admin);

        // Assert
        group.Members.First().Role.Should().Be(GroupRole.Admin);
    }

    [Fact]
    public void ChangeMemberRole_NonExistingMember_ThrowsInvalidOperationException()
    {
        // Arrange
        var group = Group.Create(_validOrganizationId, "Team Name");

        // Act
        var act = () => group.ChangeMemberRole(Guid.NewGuid(), GroupRole.Admin);

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

    [Fact]
    public void ChangeMemberRole_DemoteLastOwner_ThrowsInvalidOperationException()
    {
        // Arrange
        var group = Group.Create(_validOrganizationId, "Team Name");
        var ownerId = Guid.NewGuid();
        group.AddMember(ownerId, GroupRole.Owner);

        // Act
        var act = () => group.ChangeMemberRole(ownerId, GroupRole.Member);

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

    #endregion

    #region Permission Tests

    [Fact]
    public void AddPermission_ValidPermission_AddsPermissionToGroup()
    {
        // Arrange
        var group = Group.Create(_validOrganizationId, "Team Name");

        // Act
        var permission = group.AddPermission("read", "projects/*");

        // Assert
        group.Permissions.Should().HaveCount(1);
        permission.Permission.Should().Be("read");
        permission.Resource.Should().Be("projects/*");
    }

    [Fact]
    public void AddPermission_DuplicatePermission_ThrowsInvalidOperationException()
    {
        // Arrange
        var group = Group.Create(_validOrganizationId, "Team Name");
        group.AddPermission("read", "projects/*");

        // Act
        var act = () => group.AddPermission("read", "projects/*");

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

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    public void AddPermission_InvalidPermission_ThrowsArgumentException(string? permission)
    {
        // Arrange
        var group = Group.Create(_validOrganizationId, "Team Name");

        // Act
        var act = () => group.AddPermission(permission!);

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

    [Fact]
    public void RemovePermission_ExistingPermission_RemovesPermission()
    {
        // Arrange
        var group = Group.Create(_validOrganizationId, "Team Name");
        group.AddPermission("read", "projects/*");

        // Act
        group.RemovePermission("read", "projects/*");

        // Assert
        group.Permissions.Should().BeEmpty();
    }

    [Fact]
    public void RemovePermission_NonExistingPermission_ThrowsInvalidOperationException()
    {
        // Arrange
        var group = Group.Create(_validOrganizationId, "Team Name");

        // Act
        var act = () => group.RemovePermission("read", "projects/*");

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

    [Fact]
    public void HasPermission_ExistingPermission_ReturnsTrue()
    {
        // Arrange
        var group = Group.Create(_validOrganizationId, "Team Name");
        group.AddPermission("read", "projects/*");

        // Act & Assert
        group.HasPermission("read", "projects/123").Should().BeTrue();
    }

    [Fact]
    public void HasPermission_NonExistingPermission_ReturnsFalse()
    {
        // Arrange
        var group = Group.Create(_validOrganizationId, "Team Name");

        // Act & Assert
        group.HasPermission("write", "projects/*").Should().BeFalse();
    }

    #endregion

    #region Delete/Restore Tests

    [Fact]
    public void Delete_SetsIsDeletedTrue()
    {
        // Arrange
        var group = Group.Create(_validOrganizationId, "Team Name");

        // Act
        group.Delete();

        // Assert
        group.IsDeleted.Should().BeTrue();
        group.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
    }

    [Fact]
    public void Restore_SetsIsDeletedFalse()
    {
        // Arrange
        var group = Group.Create(_validOrganizationId, "Team Name");
        group.Delete();

        // Act
        group.Restore();

        // Assert
        group.IsDeleted.Should().BeFalse();
    }

    #endregion
}
