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 CloseSessionCommandHandler.
/// VI: Unit tests cho CloseSessionCommandHandler.
/// </summary>
public class CloseSessionCommandHandlerTests
{
    private readonly Mock<ISessionRepository> _sessionRepoMock;
    private readonly Mock<ITableRepository> _tableRepoMock;
    private readonly Mock<ILogger<CloseSessionCommandHandler>> _loggerMock;
    private readonly CloseSessionCommandHandler _handler;

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

        _sessionRepoMock.Setup(r => r.UnitOfWork.SaveEntitiesAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(true);

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

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

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

        var command = new CloseSessionCommand(session.Id);

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

        // Assert
        result.Should().BeTrue();
        session.Status.Should().Be("Closed");
        session.ClosedAt.Should().NotBeNull();
        table.Status.Should().Be(TableStatus.Available);
        _sessionRepoMock.Verify(r => r.Update(session), Times.Once);
        _tableRepoMock.Verify(r => r.Update(table), Times.Once);
    }

    [Fact]
    public async Task Handle_WithNonExistentSession_ShouldThrowInvalidOperationException()
    {
        // Arrange
        var sessionId = Guid.NewGuid();
        _sessionRepoMock.Setup(r => r.GetByIdAsync(sessionId, It.IsAny<CancellationToken>()))
            .ReturnsAsync((Session?)null);

        var command = new CloseSessionCommand(sessionId);

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

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

    [Fact]
    public async Task Handle_WhenTableNotFound_ShouldStillCloseSession()
    {
        // Arrange
        var shopId = Guid.NewGuid();
        var tableId = Guid.NewGuid();
        var session = new Session(tableId, shopId, 2);

        _sessionRepoMock.Setup(r => r.GetByIdAsync(session.Id, It.IsAny<CancellationToken>()))
            .ReturnsAsync(session);
        _tableRepoMock.Setup(r => r.GetByIdAsync(tableId, It.IsAny<CancellationToken>()))
            .ReturnsAsync((Table?)null);

        var command = new CloseSessionCommand(session.Id);

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

        // Assert
        result.Should().BeTrue();
        session.Status.Should().Be("Closed");
        _tableRepoMock.Verify(r => r.Update(It.IsAny<Table>()), Times.Never);
    }
}
