using FluentAssertions;
using FnbEngine.Domain.AggregatesModel.TableAggregate;
using FnbEngine.Domain.Exceptions;
using Xunit;

namespace FnbEngine.UnitTests.Domain;

/// <summary>
/// EN: Unit tests for Table aggregate behavior.
/// VI: Unit tests cho hanh vi aggregate Table.
/// </summary>
public class TableAggregateTests
{
    private static readonly Guid ValidShopId = Guid.NewGuid();

    [Fact]
    public void Constructor_WithValidData_ShouldCreateAvailableTable()
    {
        // Act
        var table = new Table(ValidShopId, "A-01", 4, "Main Hall");

        // Assert
        table.Id.Should().NotBeEmpty();
        table.ShopId.Should().Be(ValidShopId);
        table.TableNumber.Should().Be("A-01");
        table.Capacity.Should().Be(4);
        table.Zone.Should().Be("Main Hall");
        table.Status.Should().Be(TableStatus.Available);
        table.StatusId.Should().Be(TableStatus.Available.Id);
        table.HourlyRate.Should().Be(0);
        table.QrToken.Should().BeNull();
        table.PositionX.Should().BeNull();
        table.PositionY.Should().BeNull();
        table.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
    }

    [Fact]
    public void Constructor_WithEmptyShopId_ShouldThrowDomainException()
    {
        // Act
        var action = () => new Table(Guid.Empty, "A-01", 4);

        // Assert
        action.Should().Throw<DomainException>()
            .WithMessage("*Shop ID*");
    }

    [Fact]
    public void Constructor_WithEmptyTableNumber_ShouldThrowDomainException()
    {
        // Act
        var action = () => new Table(ValidShopId, "", 4);

        // Assert
        action.Should().Throw<DomainException>()
            .WithMessage("*Table number*");
    }

    [Fact]
    public void Constructor_WithWhitespaceTableNumber_ShouldThrowDomainException()
    {
        // Act
        var action = () => new Table(ValidShopId, "   ", 4);

        // Assert
        action.Should().Throw<DomainException>();
    }

    [Fact]
    public void Constructor_WithZeroCapacity_ShouldThrowDomainException()
    {
        // Act
        var action = () => new Table(ValidShopId, "A-01", 0);

        // Assert
        action.Should().Throw<DomainException>()
            .WithMessage("*Capacity*");
    }

    [Fact]
    public void Constructor_WithNegativeCapacity_ShouldThrowDomainException()
    {
        // Act
        var action = () => new Table(ValidShopId, "A-01", -1);

        // Assert
        action.Should().Throw<DomainException>();
    }

    [Fact]
    public void Constructor_ShouldTrimTableNumber()
    {
        // Act
        var table = new Table(ValidShopId, "  A-01  ", 4);

        // Assert
        table.TableNumber.Should().Be("A-01");
    }

    [Fact]
    public void Constructor_ShouldTrimZone()
    {
        // Act
        var table = new Table(ValidShopId, "A-01", 4, "  VIP  ");

        // Assert
        table.Zone.Should().Be("VIP");
    }

    [Fact]
    public void MarkAsOccupied_WhenAvailable_ShouldSucceed()
    {
        // Arrange
        var table = new Table(ValidShopId, "B-02", 6);

        // Act
        table.MarkAsOccupied();

        // Assert
        table.Status.Should().Be(TableStatus.Occupied);
        table.StatusId.Should().Be(TableStatus.Occupied.Id);
        table.UpdatedAt.Should().NotBeNull();
    }

    [Fact]
    public void MarkAsOccupied_WhenCleaning_ShouldThrowDomainException()
    {
        // Arrange
        var table = new Table(ValidShopId, "C-03", 2);
        table.MarkAsCleaning();

        // Act
        var action = () => table.MarkAsOccupied();

        // Assert
        action.Should().Throw<DomainException>();
    }

    [Fact]
    public void MarkAsOccupied_WhenAlreadyOccupied_ShouldThrowDomainException()
    {
        // Arrange
        var table = new Table(ValidShopId, "D-04", 2);
        table.MarkAsOccupied();

        // Act
        var action = () => table.MarkAsOccupied();

        // Assert
        action.Should().Throw<DomainException>();
    }

    [Fact]
    public void MarkAsAvailable_FromAnyStatus_ShouldSucceed()
    {
        // Arrange
        var table = new Table(ValidShopId, "E-05", 4);
        table.MarkAsOccupied();

        // Act
        table.MarkAsAvailable();

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

    [Fact]
    public void MarkAsCleaning_ShouldSetCleaningStatus()
    {
        // Arrange
        var table = new Table(ValidShopId, "F-06", 4);

        // Act
        table.MarkAsCleaning();

        // Assert
        table.Status.Should().Be(TableStatus.Cleaning);
        table.StatusId.Should().Be(TableStatus.Cleaning.Id);
    }

    [Fact]
    public void SetHourlyRate_ShouldUpdateRate()
    {
        // Arrange
        var table = new Table(ValidShopId, "G-07", 4);

        // Act
        table.SetHourlyRate(50_000m);

        // Assert
        table.HourlyRate.Should().Be(50_000m);
        table.UpdatedAt.Should().NotBeNull();
    }

    [Fact]
    public void SetPosition_ShouldUpdateCoordinates()
    {
        // Arrange
        var table = new Table(ValidShopId, "H-08", 4);

        // Act
        table.SetPosition(100, 200);

        // Assert
        table.PositionX.Should().Be(100);
        table.PositionY.Should().Be(200);
        table.UpdatedAt.Should().NotBeNull();
    }

    [Fact]
    public void GenerateQrToken_ShouldReturnNonEmptyToken()
    {
        // Arrange
        var table = new Table(ValidShopId, "I-09", 4);

        // Act
        var token = table.GenerateQrToken();

        // Assert
        token.Should().NotBeNullOrEmpty();
        token.Should().HaveLength(16);
        table.QrToken.Should().Be(token);
        table.UpdatedAt.Should().NotBeNull();
    }

    [Fact]
    public void ClearQrToken_ShouldSetTokenToNull()
    {
        // Arrange
        var table = new Table(ValidShopId, "J-10", 4);
        table.GenerateQrToken();

        // Act
        table.ClearQrToken();

        // Assert
        table.QrToken.Should().BeNull();
    }
}
