using FluentAssertions;
using FnbEngine.API.Application.Commands;
using FnbEngine.Domain.AggregatesModel.TableAggregate;
using FnbEngine.Domain.Exceptions;
using FnbEngine.Domain.SeedWork;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;

namespace FnbEngine.UnitTests.Application.Commands;

/// <summary>
/// EN: Unit tests for ChangeTableStatusCommandHandler.
/// VI: Unit tests cho ChangeTableStatusCommandHandler.
/// </summary>
public class ChangeTableStatusCommandHandlerTests
{
    private readonly Mock<ITableRepository> _repoMock;
    private readonly Mock<ILogger<ChangeTableStatusCommandHandler>> _loggerMock;
    private readonly ChangeTableStatusCommandHandler _handler;

    public ChangeTableStatusCommandHandlerTests()
    {
        _repoMock = new Mock<ITableRepository>();
        _loggerMock = new Mock<ILogger<ChangeTableStatusCommandHandler>>();
        _repoMock.Setup(r => r.UnitOfWork.SaveEntitiesAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(true);
        _handler = new ChangeTableStatusCommandHandler(_repoMock.Object, _loggerMock.Object);
    }

    [Theory]
    [InlineData("available")]
    [InlineData("Available")]
    public async Task Handle_WithAvailableStatus_ShouldMarkTableAsAvailable(string status)
    {
        // Arrange
        var tableId = Guid.NewGuid();
        var table = new Table(Guid.NewGuid(), "A-01", 4);
        table.MarkAsOccupied();
        _repoMock.Setup(r => r.GetByIdAsync(tableId, It.IsAny<CancellationToken>()))
            .ReturnsAsync(table);

        var command = new ChangeTableStatusCommand(tableId, status);

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

        // Assert
        result.Should().BeTrue();
        table.Status.Should().Be(TableStatus.Available);
    }

    [Fact]
    public async Task Handle_WithOccupiedStatus_ShouldMarkTableAsOccupied()
    {
        // Arrange
        var tableId = Guid.NewGuid();
        var table = new Table(Guid.NewGuid(), "A-01", 4);
        _repoMock.Setup(r => r.GetByIdAsync(tableId, It.IsAny<CancellationToken>()))
            .ReturnsAsync(table);

        var command = new ChangeTableStatusCommand(tableId, "occupied");

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

        // Assert
        result.Should().BeTrue();
        table.Status.Should().Be(TableStatus.Occupied);
    }

    [Fact]
    public async Task Handle_WithCleaningStatus_ShouldMarkTableAsCleaning()
    {
        // Arrange
        var tableId = Guid.NewGuid();
        var table = new Table(Guid.NewGuid(), "A-01", 4);
        _repoMock.Setup(r => r.GetByIdAsync(tableId, It.IsAny<CancellationToken>()))
            .ReturnsAsync(table);

        var command = new ChangeTableStatusCommand(tableId, "cleaning");

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

        // Assert
        result.Should().BeTrue();
        table.Status.Should().Be(TableStatus.Cleaning);
    }

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

        var command = new ChangeTableStatusCommand(tableId, "available");

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

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

    [Fact]
    public async Task Handle_WithInvalidStatus_ShouldThrowArgumentException()
    {
        // Arrange
        var tableId = Guid.NewGuid();
        var table = new Table(Guid.NewGuid(), "A-01", 4);
        _repoMock.Setup(r => r.GetByIdAsync(tableId, It.IsAny<CancellationToken>()))
            .ReturnsAsync(table);

        var command = new ChangeTableStatusCommand(tableId, "invalid");

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

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