using FluentAssertions;
using FnbEngine.API.Application.Commands;
using FnbEngine.Domain.AggregatesModel.KitchenAggregate;
using FnbEngine.Domain.SeedWork;
using Moq;
using Xunit;

namespace FnbEngine.UnitTests.Application.Commands;

/// <summary>
/// EN: Unit tests for UpdateTicketStatusCommandHandler.
/// VI: Unit tests cho UpdateTicketStatusCommandHandler.
/// </summary>
public class UpdateTicketStatusCommandHandlerTests
{
    private readonly Mock<IKitchenTicketRepository> _repoMock;
    private readonly UpdateTicketStatusCommandHandler _handler;

    public UpdateTicketStatusCommandHandlerTests()
    {
        _repoMock = new Mock<IKitchenTicketRepository>();
        _repoMock.Setup(r => r.UnitOfWork.SaveEntitiesAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(true);
        _handler = new UpdateTicketStatusCommandHandler(_repoMock.Object);
    }

    [Theory]
    [InlineData("inprogress", "InProgress")]
    [InlineData("InProgress", "InProgress")]
    [InlineData("ready", "Ready")]
    [InlineData("Ready", "Ready")]
    [InlineData("served", "Served")]
    [InlineData("Served", "Served")]
    public async Task Handle_WithValidStatus_ShouldUpdateTicketStatus(string inputStatus, string expectedStatus)
    {
        // Arrange
        var ticketId = Guid.NewGuid();
        var ticket = new KitchenTicket(Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), "Pho Bo");
        _repoMock.Setup(r => r.GetByIdAsync(ticketId, It.IsAny<CancellationToken>()))
            .ReturnsAsync(ticket);

        var command = new UpdateTicketStatusCommand(ticketId, inputStatus);

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

        // Assert
        result.Should().BeTrue();
        ticket.Status.Should().Be(expectedStatus);
        _repoMock.Verify(r => r.Update(ticket), Times.Once);
        _repoMock.Verify(r => r.UnitOfWork.SaveEntitiesAsync(It.IsAny<CancellationToken>()), Times.Once);
    }

    [Fact]
    public async Task Handle_WithNonExistentTicket_ShouldThrowInvalidOperationException()
    {
        // Arrange
        var ticketId = Guid.NewGuid();
        _repoMock.Setup(r => r.GetByIdAsync(ticketId, It.IsAny<CancellationToken>()))
            .ReturnsAsync((KitchenTicket?)null);

        var command = new UpdateTicketStatusCommand(ticketId, "Ready");

        // Act
        var action = () => _handler.Handle(command, CancellationToken.None);

        // Assert
        await action.Should().ThrowAsync<InvalidOperationException>()
            .WithMessage($"*{ticketId}*not found*");
    }

    [Fact]
    public async Task Handle_WithInvalidStatus_ShouldThrowArgumentException()
    {
        // Arrange
        var ticketId = Guid.NewGuid();
        var ticket = new KitchenTicket(Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), "Pho Bo");
        _repoMock.Setup(r => r.GetByIdAsync(ticketId, It.IsAny<CancellationToken>()))
            .ReturnsAsync(ticket);

        var command = new UpdateTicketStatusCommand(ticketId, "InvalidStatus");

        // Act
        var action = () => _handler.Handle(command, CancellationToken.None);

        // Assert
        await action.Should().ThrowAsync<ArgumentException>()
            .WithMessage("*Invalid status*");
    }

    [Fact]
    public async Task Handle_WithServedStatus_ShouldRaiseDomainEvent()
    {
        // Arrange
        var ticketId = Guid.NewGuid();
        var ticket = new KitchenTicket(Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), "Pho Bo");
        _repoMock.Setup(r => r.GetByIdAsync(ticketId, It.IsAny<CancellationToken>()))
            .ReturnsAsync(ticket);

        var command = new UpdateTicketStatusCommand(ticketId, "Served");

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

        // Assert
        ticket.DomainEvents.Should().ContainSingle();
    }
}
