using Xunit;
using FluentAssertions;
using IamService.Domain.AggregatesModel.RoleAggregate;

namespace IamService.UnitTests.Domain.Roles;

/// <summary>
/// EN: Unit tests for ApplicationRole entity.
/// VI: Unit tests cho entity ApplicationRole.
/// </summary>
public class ApplicationRoleTests
{
    #region Creation Tests

    [Fact]
    public void Create_ValidParameters_CreatesRoleWithGeneratedId()
    {
        // Arrange
        var name = "Administrator";
        var description = "System administrator role";

        // Act
        var role = new ApplicationRole(name, description);

        // Assert
        role.Should().NotBeNull();
        role.Id.Should().NotBeEmpty();
        role.Name.Should().Be(name);
        role.NormalizedName.Should().Be(name.ToUpperInvariant());
        role.Description.Should().Be(description);
        role.IsSystemRole.Should().BeFalse();
        role.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
    }

    [Fact]
    public void Create_WithoutDescription_CreatesRoleWithNullDescription()
    {
        // Arrange & Act
        var role = new ApplicationRole("Editor");

        // Assert
        role.Name.Should().Be("Editor");
        role.Description.Should().BeNull();
    }

    [Fact]
    public void Create_AsSystemRole_SetsIsSystemRoleTrue()
    {
        // Arrange & Act
        var role = new ApplicationRole("SuperAdmin", "Super Administrator", isSystemRole: true);

        // Assert
        role.IsSystemRole.Should().BeTrue();
    }

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    public void Create_InvalidName_ThrowsArgumentException(string? name)
    {
        // Arrange & Act
        var act = () => new ApplicationRole(name!);

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

    [Fact]
    public void Create_NormalizesNameToUppercase()
    {
        // Arrange & Act
        var role = new ApplicationRole("user-manager");

        // Assert
        role.NormalizedName.Should().Be("USER-MANAGER");
    }

    #endregion

    #region Update Tests

    [Fact]
    public void Update_ValidData_UpdatesNameAndDescription()
    {
        // Arrange
        var role = new ApplicationRole("OldName", "Old description");
        var newName = "NewName";
        var newDescription = "New description";

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

        // Assert
        role.Name.Should().Be(newName);
        role.NormalizedName.Should().Be(newName.ToUpperInvariant());
        role.Description.Should().Be(newDescription);
    }

    [Fact]
    public void Update_WithNullDescription_SetsDescriptionNull()
    {
        // Arrange
        var role = new ApplicationRole("TestRole", "Initial description");

        // Act
        role.Update("TestRole", null);

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

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    public void Update_InvalidName_ThrowsArgumentException(string? name)
    {
        // Arrange
        var role = new ApplicationRole("OldName");

        // Act
        var act = () => role.Update(name!, "description");

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

    #endregion

    #region Domain Events Tests

    [Fact]
    public void AddDomainEvent_AddsEventToCollection()
    {
        // Arrange
        var role = new ApplicationRole("TestRole");
        var domainEvent = new TestDomainEvent();

        // Act
        role.AddDomainEvent(domainEvent);

        // Assert
        role.DomainEvents.Should().ContainSingle();
        role.DomainEvents.First().Should().Be(domainEvent);
    }

    [Fact]
    public void ClearDomainEvents_RemovesAllEvents()
    {
        // Arrange
        var role = new ApplicationRole("TestRole");
        role.AddDomainEvent(new TestDomainEvent());
        role.AddDomainEvent(new TestDomainEvent());

        // Act
        role.ClearDomainEvents();

        // Assert
        role.DomainEvents.Should().BeEmpty();
    }

    #endregion

    #region Equality Tests

    [Fact]
    public void TwoRoles_SameId_ShouldBeEqual()
    {
        // Arrange
        var role1 = new ApplicationRole("Admin");
        var role2Id = role1.Id;

        // Assert - role IDs are Guids, so comparing by Id
        role1.Id.Should().Be(role2Id);
    }

    [Fact]
    public void TwoRoles_DifferentIds_ShouldNotBeEqual()
    {
        // Arrange
        var role1 = new ApplicationRole("Admin");
        var role2 = new ApplicationRole("Admin");

        // Assert
        role1.Id.Should().NotBe(role2.Id);
    }

    #endregion

    /// <summary>
    /// EN: Test domain event for testing purposes.
    /// VI: Domain event test cho mục đích testing.
    /// </summary>
    private class TestDomainEvent : IamService.Domain.SeedWork.IDomainEvent
    {
        public DateTime OccurredOn { get; } = DateTime.UtcNow;
    }
}
