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

namespace FnbEngine.UnitTests.Domain;

/// <summary>
/// EN: Unit tests for Reservation aggregate behavior.
/// VI: Unit tests cho hanh vi aggregate Reservation.
/// </summary>
public class ReservationTests
{
    private static readonly Guid ValidShopId = Guid.NewGuid();
    private static readonly DateTime FutureTime = DateTime.UtcNow.AddHours(2);

    [Fact]
    public void Constructor_WithValidData_ShouldCreatePendingReservation()
    {
        // Act
        var reservation = new Reservation(ValidShopId, "Nguyen Van A", 4, FutureTime, "0901234567");

        // Assert
        reservation.Id.Should().NotBeEmpty();
        reservation.ShopId.Should().Be(ValidShopId);
        reservation.GuestName.Should().Be("Nguyen Van A");
        reservation.PartySize.Should().Be(4);
        reservation.ReservationTime.Should().Be(FutureTime);
        reservation.Phone.Should().Be("0901234567");
        reservation.Status.Should().Be("pending");
        reservation.TableId.Should().BeNull();
        reservation.Note.Should().BeNull();
        reservation.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
    }

    [Fact]
    public void Constructor_WithAllOptionalParams_ShouldSetAllFields()
    {
        // Arrange
        var tableId = Guid.NewGuid();

        // Act
        var reservation = new Reservation(ValidShopId, "Tran Thi B", 6, FutureTime,
            "0909876543", tableId, "Window seat preferred");

        // Assert
        reservation.TableId.Should().Be(tableId);
        reservation.Note.Should().Be("Window seat preferred");
        reservation.Phone.Should().Be("0909876543");
    }

    [Fact]
    public void Constructor_WithEmptyShopId_ShouldThrowDomainException()
    {
        var action = () => new Reservation(Guid.Empty, "Guest", 2, FutureTime);
        action.Should().Throw<DomainException>().WithMessage("*Shop ID*");
    }

    [Fact]
    public void Constructor_WithEmptyGuestName_ShouldThrowDomainException()
    {
        var action = () => new Reservation(ValidShopId, "", 2, FutureTime);
        action.Should().Throw<DomainException>().WithMessage("*Guest name*");
    }

    [Fact]
    public void Constructor_WithWhitespaceGuestName_ShouldThrowDomainException()
    {
        var action = () => new Reservation(ValidShopId, "   ", 2, FutureTime);
        action.Should().Throw<DomainException>();
    }

    [Fact]
    public void Constructor_WithZeroPartySize_ShouldThrowDomainException()
    {
        var action = () => new Reservation(ValidShopId, "Guest", 0, FutureTime);
        action.Should().Throw<DomainException>().WithMessage("*Party size*");
    }

    [Fact]
    public void Constructor_ShouldTrimGuestNameAndPhone()
    {
        var reservation = new Reservation(ValidShopId, "  Nguyen Van A  ", 2, FutureTime, "  0901234567  ");
        reservation.GuestName.Should().Be("Nguyen Van A");
        reservation.Phone.Should().Be("0901234567");
    }

    [Fact]
    public void Confirm_ShouldSetStatusToConfirmed()
    {
        var reservation = new Reservation(ValidShopId, "Guest", 2, FutureTime);
        reservation.Confirm();
        reservation.Status.Should().Be("confirmed");
    }

    [Fact]
    public void Seat_ShouldSetStatusToSeated()
    {
        var reservation = new Reservation(ValidShopId, "Guest", 2, FutureTime);
        reservation.Seat();
        reservation.Status.Should().Be("seated");
    }

    [Fact]
    public void Cancel_ShouldSetStatusToCancelled()
    {
        var reservation = new Reservation(ValidShopId, "Guest", 2, FutureTime);
        reservation.Cancel();
        reservation.Status.Should().Be("cancelled");
    }

    [Fact]
    public void NoShow_ShouldSetStatusToNoShow()
    {
        var reservation = new Reservation(ValidShopId, "Guest", 2, FutureTime);
        reservation.NoShow();
        reservation.Status.Should().Be("no_show");
    }

    [Fact]
    public void AssignTable_ShouldSetTableId()
    {
        var reservation = new Reservation(ValidShopId, "Guest", 2, FutureTime);
        var tableId = Guid.NewGuid();

        reservation.AssignTable(tableId);

        reservation.TableId.Should().Be(tableId);
    }

    [Fact]
    public void UpdateStatus_WithValidStatus_ShouldSucceed()
    {
        var reservation = new Reservation(ValidShopId, "Guest", 2, FutureTime);

        reservation.UpdateStatus("confirmed");
        reservation.Status.Should().Be("confirmed");

        reservation.UpdateStatus("seated");
        reservation.Status.Should().Be("seated");
    }

    [Fact]
    public void UpdateStatus_WithInvalidStatus_ShouldThrowDomainException()
    {
        var reservation = new Reservation(ValidShopId, "Guest", 2, FutureTime);

        var action = () => reservation.UpdateStatus("invalid_status");

        action.Should().Throw<DomainException>()
            .WithMessage("*Invalid reservation status*");
    }
}
