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

namespace FnbEngine.UnitTests.Application.Commands;

/// <summary>
/// EN: Unit tests for OpenSessionCommandHandler.
/// VI: Unit tests cho OpenSessionCommandHandler.
/// </summary>
public class OpenSessionCommandHandlerTests
{
    private readonly Mock<ISessionRepository> _sessionRepoMock;
    private readonly Mock<ITableRepository> _tableRepoMock;
    private readonly Mock<ILogger<OpenSessionCommandHandler>> _loggerMock;
    private readonly OpenSessionCommandHandler _handler;

    public OpenSessionCommandHandlerTests()
    {
        _sessionRepoMock = new Mock<ISessionRepository>();
        _tableRepoMock = new Mock<ITableRepository>();
        _loggerMock = new Mock<ILogger<OpenSessionCommandHandler>>();

        _sessionRepoMock.Setup(r => r.UnitOfWork.SaveEntitiesAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(true);
        _sessionRepoMock.Setup(r => r.AddAsync(It.IsAny<Session>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync((Session s, CancellationToken _) => s);

        _handler = new OpenSessionCommandHandler(
            _sessionRepoMock.Object,
            _tableRepoMock.Object,
            _loggerMock.Object);
    }

    [Fact]
    public async Task Handle_WithValidCommand_ShouldCreateSessionAndOccupyTable()
    {
        // Arrange
        var tableId = Guid.NewGuid();
        var shopId = Guid.NewGuid();
        var table = new Table(shopId, "A-01", 4);

        _tableRepoMock.Setup(r => r.GetByIdAsync(tableId, It.IsAny<CancellationToken>()))
            .ReturnsAsync(table);
        _sessionRepoMock.Setup(r => r.GetActiveByTableAsync(tableId, It.IsAny<CancellationToken>()))
            .ReturnsAsync((Session?)null);

        var command = new OpenSessionCommand(tableId, shopId, 3);

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

        // Assert
        result.Should().NotBeNull();
        result.SessionId.Should().NotBeEmpty();
        table.Status.Should().Be(TableStatus.Occupied);
        _sessionRepoMock.Verify(r => r.AddAsync(It.IsAny<Session>(), It.IsAny<CancellationToken>()), Times.Once);
        _tableRepoMock.Verify(r => r.Update(table), Times.Once);
        _sessionRepoMock.Verify(r => r.UnitOfWork.SaveEntitiesAsync(It.IsAny<CancellationToken>()), Times.Once);
    }

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

        var command = new OpenSessionCommand(tableId, Guid.NewGuid(), 2);

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

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

    [Fact]
    public async Task Handle_WithExistingActiveSession_ShouldThrowInvalidOperationException()
    {
        // Arrange
        var tableId = Guid.NewGuid();
        var shopId = Guid.NewGuid();
        var table = new Table(shopId, "A-01", 4);
        var existingSession = new Session(tableId, shopId, 2);

        _tableRepoMock.Setup(r => r.GetByIdAsync(tableId, It.IsAny<CancellationToken>()))
            .ReturnsAsync(table);
        _sessionRepoMock.Setup(r => r.GetActiveByTableAsync(tableId, It.IsAny<CancellationToken>()))
            .ReturnsAsync(existingSession);

        var command = new OpenSessionCommand(tableId, shopId, 3);

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

        // Assert
        await action.Should().ThrowAsync<InvalidOperationException>()
            .WithMessage("*already has an active session*");
    }
}
