using Xunit;
using Moq;
using FluentAssertions;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using IamService.API.Application.Commands.Roles;
using IamService.Domain.AggregatesModel.UserAggregate;
using IamService.Domain.AggregatesModel.RoleAggregate;
using IamService.Domain.Exceptions;

namespace IamService.UnitTests.Application.Commands.Roles;

/// <summary>
/// EN: Unit tests for AssignRoleToUserCommandHandler.
/// VI: Unit tests cho AssignRoleToUserCommandHandler.
/// </summary>
public class AssignRoleToUserCommandHandlerTests
{
    private readonly Mock<UserManager<ApplicationUser>> _userManagerMock;
    private readonly Mock<RoleManager<ApplicationRole>> _roleManagerMock;
    private readonly Mock<ILogger<AssignRoleToUserCommandHandler>> _loggerMock;
    private readonly AssignRoleToUserCommandHandler _handler;

    public AssignRoleToUserCommandHandlerTests()
    {
        // EN: Setup UserManager mock
        // VI: Thiết lập mock cho UserManager
        var userStoreMock = new Mock<IUserStore<ApplicationUser>>();
        _userManagerMock = new Mock<UserManager<ApplicationUser>>(
            userStoreMock.Object, null!, null!, null!, null!, null!, null!, null!, null!);

        // EN: Setup RoleManager mock
        // VI: Thiết lập mock cho RoleManager
        var roleStoreMock = new Mock<IRoleStore<ApplicationRole>>();
        _roleManagerMock = new Mock<RoleManager<ApplicationRole>>(
            roleStoreMock.Object, null!, null!, null!, null!);

        _loggerMock = new Mock<ILogger<AssignRoleToUserCommandHandler>>();

        _handler = new AssignRoleToUserCommandHandler(
            _userManagerMock.Object,
            _roleManagerMock.Object,
            _loggerMock.Object);
    }

    [Fact]
    public async Task Handle_ValidCommand_AssignsRoleToUser()
    {
        // Arrange
        var userId = Guid.NewGuid();
        var roleName = "Administrator";
        var command = new AssignRoleToUserCommand(userId, roleName);
        var user = new ApplicationUser("test@example.com", "Test", "User");

        _userManagerMock
            .Setup(u => u.FindByIdAsync(userId.ToString()))
            .ReturnsAsync(user);

        _roleManagerMock
            .Setup(r => r.RoleExistsAsync(roleName))
            .ReturnsAsync(true);

        _userManagerMock
            .Setup(u => u.IsInRoleAsync(user, roleName))
            .ReturnsAsync(false);

        _userManagerMock
            .Setup(u => u.AddToRoleAsync(user, roleName))
            .ReturnsAsync(IdentityResult.Success);

        // Act
        var result = await _handler.Handle(command, CancellationToken.None);

        // Assert
        result.Should().BeTrue();
        _userManagerMock.Verify(u => u.AddToRoleAsync(user, roleName), Times.Once);
    }

    [Fact]
    public async Task Handle_UserNotFound_ThrowsDomainException()
    {
        // Arrange
        var command = new AssignRoleToUserCommand(Guid.NewGuid(), "Admin");

        _userManagerMock
            .Setup(u => u.FindByIdAsync(It.IsAny<string>()))
            .ReturnsAsync((ApplicationUser?)null);

        // Act
        var act = async () => await _handler.Handle(command, CancellationToken.None);

        // Assert
        await act.Should().ThrowAsync<DomainException>()
            .WithMessage("*User*not found*");

        _userManagerMock.Verify(u => u.AddToRoleAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>()), Times.Never);
    }

    [Fact]
    public async Task Handle_RoleNotFound_ThrowsDomainException()
    {
        // Arrange
        var userId = Guid.NewGuid();
        var command = new AssignRoleToUserCommand(userId, "NonExistentRole");
        var user = new ApplicationUser("test@example.com", "Test", "User");

        _userManagerMock
            .Setup(u => u.FindByIdAsync(userId.ToString()))
            .ReturnsAsync(user);

        _roleManagerMock
            .Setup(r => r.RoleExistsAsync("NonExistentRole"))
            .ReturnsAsync(false);

        // Act
        var act = async () => await _handler.Handle(command, CancellationToken.None);

        // Assert
        await act.Should().ThrowAsync<DomainException>()
            .WithMessage("*Role*not found*");

        _userManagerMock.Verify(u => u.AddToRoleAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>()), Times.Never);
    }

    [Fact]
    public async Task Handle_UserAlreadyHasRole_ThrowsDomainException()
    {
        // Arrange
        var userId = Guid.NewGuid();
        var roleName = "Admin";
        var command = new AssignRoleToUserCommand(userId, roleName);
        var user = new ApplicationUser("test@example.com", "Test", "User");

        _userManagerMock
            .Setup(u => u.FindByIdAsync(userId.ToString()))
            .ReturnsAsync(user);

        _roleManagerMock
            .Setup(r => r.RoleExistsAsync(roleName))
            .ReturnsAsync(true);

        _userManagerMock
            .Setup(u => u.IsInRoleAsync(user, roleName))
            .ReturnsAsync(true); // User already has the role

        // Act
        var act = async () => await _handler.Handle(command, CancellationToken.None);

        // Assert
        await act.Should().ThrowAsync<DomainException>()
            .WithMessage("*already has role*");

        _userManagerMock.Verify(u => u.AddToRoleAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>()), Times.Never);
    }

    [Fact]
    public async Task Handle_AddToRoleFails_ThrowsDomainException()
    {
        // Arrange
        var userId = Guid.NewGuid();
        var roleName = "Admin";
        var command = new AssignRoleToUserCommand(userId, roleName);
        var user = new ApplicationUser("test@example.com", "Test", "User");

        _userManagerMock
            .Setup(u => u.FindByIdAsync(userId.ToString()))
            .ReturnsAsync(user);

        _roleManagerMock
            .Setup(r => r.RoleExistsAsync(roleName))
            .ReturnsAsync(true);

        _userManagerMock
            .Setup(u => u.IsInRoleAsync(user, roleName))
            .ReturnsAsync(false);

        _userManagerMock
            .Setup(u => u.AddToRoleAsync(user, roleName))
            .ReturnsAsync(IdentityResult.Failed(new IdentityError { Description = "Assignment failed" }));

        // Act
        var act = async () => await _handler.Handle(command, CancellationToken.None);

        // Assert
        await act.Should().ThrowAsync<DomainException>()
            .WithMessage("*Failed to assign role*");
    }
}
