using Xunit;
using Moq;
using FluentAssertions;
using Microsoft.Extensions.Logging;
using IamService.API.Application.Commands.Groups;
using IamService.Domain.AggregatesModel.GroupAggregate;
using IamService.Domain.AggregatesModel.OrganizationAggregate;
using IamService.Domain.Exceptions;
using IamService.Domain.SeedWork;

namespace IamService.UnitTests.Application.Commands.Groups;

/// <summary>
/// EN: Unit tests for CreateGroupCommandHandler.
/// VI: Unit tests cho CreateGroupCommandHandler.
/// </summary>
public class CreateGroupCommandHandlerTests
{
    private readonly Mock<IGroupRepository> _groupRepositoryMock;
    private readonly Mock<IOrganizationRepository> _organizationRepositoryMock;
    private readonly Mock<ILogger<CreateGroupCommandHandler>> _loggerMock;
    private readonly Mock<IUnitOfWork> _unitOfWorkMock;
    private readonly CreateGroupCommandHandler _handler;

    public CreateGroupCommandHandlerTests()
    {
        _groupRepositoryMock = new Mock<IGroupRepository>();
        _organizationRepositoryMock = new Mock<IOrganizationRepository>();
        _loggerMock = new Mock<ILogger<CreateGroupCommandHandler>>();
        _unitOfWorkMock = new Mock<IUnitOfWork>();

        // EN: Setup UnitOfWork mock
        // VI: Thiết lập mock cho UnitOfWork
        _unitOfWorkMock
            .Setup(u => u.SaveEntitiesAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(true);
        _groupRepositoryMock
            .Setup(r => r.UnitOfWork)
            .Returns(_unitOfWorkMock.Object);

        _handler = new CreateGroupCommandHandler(
            _groupRepositoryMock.Object,
            _organizationRepositoryMock.Object,
            _loggerMock.Object);
    }

    [Fact]
    public async Task Handle_ValidCommand_CreatesGroupAndReturnsResult()
    {
        // Arrange
        var organizationId = Guid.NewGuid();
        var command = new CreateGroupCommand(
            Name: "Development Team",
            OrganizationId: organizationId,
            Description: "Main development team");

        var organization = Organization.Create("Test Org", "test-org");
        _organizationRepositoryMock
            .Setup(r => r.GetByIdAsync(organizationId, It.IsAny<CancellationToken>()))
            .ReturnsAsync(organization);

        Group? capturedGroup = null;
        _groupRepositoryMock
            .Setup(r => r.Add(It.IsAny<Group>()))
            .Callback<Group>(g => capturedGroup = g)
            .Returns((Group g) => g);

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

        // Assert
        result.Should().NotBeNull();
        result.Id.Should().NotBeEmpty();
        result.Name.Should().Be("Development Team");
        result.Description.Should().Be("Main development team");
        result.OrganizationId.Should().Be(organizationId);
        result.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));

        capturedGroup.Should().NotBeNull();
        capturedGroup!.OrganizationId.Should().Be(organizationId);
    }

    [Fact]
    public async Task Handle_ValidCommand_PersistsGroupToRepository()
    {
        // Arrange
        var organizationId = Guid.NewGuid();
        var command = new CreateGroupCommand(
            Name: "Dev Team",
            OrganizationId: organizationId);

        var organization = Organization.Create("Test Org", "test-org");
        _organizationRepositoryMock
            .Setup(r => r.GetByIdAsync(organizationId, It.IsAny<CancellationToken>()))
            .ReturnsAsync(organization);

        _groupRepositoryMock
            .Setup(r => r.Add(It.IsAny<Group>()))
            .Returns((Group g) => g);

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

        // Assert
        _groupRepositoryMock.Verify(r => r.Add(It.IsAny<Group>()), Times.Once);
        _unitOfWorkMock.Verify(u => u.SaveEntitiesAsync(It.IsAny<CancellationToken>()), Times.Once);
    }

    [Fact]
    public async Task Handle_OrganizationNotFound_ThrowsDomainException()
    {
        // Arrange
        var command = new CreateGroupCommand(
            Name: "Dev Team",
            OrganizationId: Guid.NewGuid());

        _organizationRepositoryMock
            .Setup(r => r.GetByIdAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync((Organization?)null);

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

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

        _groupRepositoryMock.Verify(r => r.Add(It.IsAny<Group>()), Times.Never);
    }

    [Fact]
    public async Task Handle_WithoutDescription_CreatesGroupWithNullDescription()
    {
        // Arrange
        var organizationId = Guid.NewGuid();
        var command = new CreateGroupCommand(
            Name: "Dev Team",
            OrganizationId: organizationId,
            Description: null);

        var organization = Organization.Create("Test Org", "test-org");
        _organizationRepositoryMock
            .Setup(r => r.GetByIdAsync(organizationId, It.IsAny<CancellationToken>()))
            .ReturnsAsync(organization);

        _groupRepositoryMock
            .Setup(r => r.Add(It.IsAny<Group>()))
            .Returns((Group g) => g);

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

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

    [Fact]
    public async Task Handle_CancellationRequested_PropagatesCancellation()
    {
        // Arrange
        var organizationId = Guid.NewGuid();
        var command = new CreateGroupCommand("Dev Team", organizationId);
        var cts = new CancellationTokenSource();
        cts.Cancel();

        _organizationRepositoryMock
            .Setup(r => r.GetByIdAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
            .ThrowsAsync(new OperationCanceledException());

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

        // Assert
        await act.Should().ThrowAsync<OperationCanceledException>();
    }
}
