// EN: Unit tests for PointAccount aggregate
// VI: Unit tests cho PointAccount aggregate
using WalletService.Domain.AggregatesModel.PointAccountAggregate;
using WalletService.Domain.Exceptions;
using Xunit;

namespace WalletService.UnitTests.Domain;

public class PointAccountTests
{
    private static readonly Guid TestUserId = Guid.NewGuid();

    [Fact]
    public void Constructor_ShouldCreatePointAccount_WithZeroBalance()
    {
        // Arrange & Act
        var account = new PointAccount(TestUserId);

        // Assert
        Assert.NotNull(account);
        Assert.Equal(TestUserId, account.UserId);
        Assert.Equal(0, account.TotalPoints);
        Assert.Equal(0, account.AvailablePoints);
    }

    [Fact]
    public void EarnPoints_ShouldIncreasePoints()
    {
        // Arrange
        var account = new PointAccount(TestUserId);
        var earnPoints = 100L;

        // Act
        account.EarnPoints(earnPoints, "ORDER001", "Test earn", null);

        // Assert
        Assert.Equal(earnPoints, account.TotalPoints);
        Assert.Equal(earnPoints, account.AvailablePoints);
        Assert.Single(account.Transactions);
        Assert.Equal(PointTransactionType.Earn, account.Transactions.First().Type);
    }

    [Fact]
    public void EarnPoints_ShouldThrow_WhenPointsAreNegative()
    {
        // Arrange
        var account = new PointAccount(TestUserId);

        // Act & Assert
        Assert.Throws<PointsDomainException>(() =>
            account.EarnPoints(-100, "ORDER001", "Invalid", null));
    }

    [Fact]
    public void SpendPoints_ShouldDecreaseAvailablePoints()
    {
        // Arrange
        var account = new PointAccount(TestUserId);
        account.EarnPoints(100, "ORDER001", "Initial earn", null);

        // Act
        account.SpendPoints(50, "REWARD001", "Test spend");

        // Assert
        Assert.Equal(100, account.TotalPoints); // Total remains same
        Assert.Equal(50, account.AvailablePoints); // Available decreases
        Assert.Equal(2, account.Transactions.Count);
    }

    [Fact]
    public void SpendPoints_ShouldThrow_WhenInsufficientPoints()
    {
        // Arrange
        var account = new PointAccount(TestUserId);
        account.EarnPoints(50, "ORDER001", "Initial earn", null);

        // Act & Assert
        Assert.Throws<InsufficientPointsException>(() =>
            account.SpendPoints(100, "REWARD001", "Invalid spend"));
    }

    [Fact]
    public void ExpirePoints_ShouldDecreaseAvailablePoints()
    {
        // Arrange
        var account = new PointAccount(TestUserId);
        account.EarnPoints(100, "ORDER001", "Initial earn", null);

        // Act
        account.ExpirePoints(30, "Points expired");

        // Assert
        Assert.Equal(100, account.TotalPoints); // Total remains same
        Assert.Equal(70, account.AvailablePoints); // Available decreases
    }

    [Fact]
    public void AddBonusPoints_ShouldIncreasePoints()
    {
        // Arrange
        var account = new PointAccount(TestUserId);

        // Act
        account.AddBonusPoints(50, "PROMO001", "Bonus points", null);

        // Assert
        Assert.Equal(50, account.TotalPoints);
        Assert.Equal(50, account.AvailablePoints);
        Assert.Equal(PointTransactionType.Bonus, account.Transactions.First().Type);
    }

    [Fact]
    public void AdjustPoints_ShouldAdjustPositive()
    {
        // Arrange
        var account = new PointAccount(TestUserId);

        // Act
        account.AdjustPoints(100, "Admin adjustment");

        // Assert
        Assert.Equal(100, account.TotalPoints);
        Assert.Equal(100, account.AvailablePoints);
        Assert.Equal(PointTransactionType.Adjust, account.Transactions.First().Type);
    }

    [Fact]
    public void AdjustPoints_ShouldAdjustNegative()
    {
        // Arrange
        var account = new PointAccount(TestUserId);
        account.EarnPoints(100, "ORDER001", "Initial earn", null);

        // Act
        account.AdjustPoints(-30, "Admin deduction");

        // Assert
        Assert.Equal(100, account.TotalPoints); // Total doesn't decrease
        Assert.Equal(70, account.AvailablePoints);
    }

    [Fact]
    public void AdjustPoints_ShouldThrow_WhenInsufficientForNegativeAdjustment()
    {
        // Arrange
        var account = new PointAccount(TestUserId);
        account.EarnPoints(50, "ORDER001", "Initial earn", null);

        // Act & Assert
        Assert.Throws<InsufficientPointsException>(() =>
            account.AdjustPoints(-100, "Invalid deduction"));
    }
}
