// EN: Unit tests for Wallet aggregate with multi-currency support
// VI: Unit tests cho Wallet aggregate với hỗ trợ đa tiền tệ
using WalletService.Domain.AggregatesModel.WalletAggregate;
using WalletService.Domain.Exceptions;
using Xunit;

namespace WalletService.UnitTests.Domain;

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

    [Fact]
    public void Constructor_ShouldCreateWallet_WithZeroBalance()
    {
        // Arrange & Act
        var wallet = new Wallet(TestUserId, CurrencyType.VND);

        // Assert
        Assert.NotNull(wallet);
        Assert.Equal(TestUserId, wallet.UserId);
        Assert.Equal(CurrencyType.VND.Id, wallet.DefaultCurrencyTypeId);
        Assert.Equal(0, wallet.GetBalance(CurrencyType.VND));
        Assert.Equal(WalletStatus.Active, wallet.Status);
    }

    [Fact]
    public void Constructor_ShouldDefaultToVND_WhenNoCurrencyProvided()
    {
        // Arrange & Act
        var wallet = new Wallet(TestUserId);

        // Assert
        Assert.Equal(CurrencyType.VND.Id, wallet.DefaultCurrencyTypeId);
    }

    [Fact]
    public void Deposit_ShouldIncreaseBalance()
    {
        // Arrange
        var wallet = new Wallet(TestUserId, CurrencyType.VND);

        // Act
        wallet.Deposit(100000m, CurrencyType.VND, "Test deposit", "REF001");

        // Assert
        Assert.Equal(100000m, wallet.GetBalance(CurrencyType.VND));
        Assert.Single(wallet.Transactions);
        Assert.Equal(TransactionType.Credit, wallet.Transactions.First().Type);
    }

    [Fact]
    public void CreateMoney_ShouldThrow_WhenAmountIsNegative()
    {
        // Arrange & Act & Assert
        // EN: Money constructor throws ArgumentException for negative amounts
        // VI: Money constructor ném ArgumentException cho số tiền âm
        Assert.Throws<ArgumentException>(() =>
            new Money(-100, "VND"));
    }

    [Fact]
    public void Withdraw_ShouldDecreaseBalance()
    {
        // Arrange
        var wallet = new Wallet(TestUserId, CurrencyType.VND);
        wallet.Deposit(100000m, CurrencyType.VND, "Initial deposit", "REF001");

        // Act
        wallet.Withdraw(50000m, CurrencyType.VND, "Test withdrawal", "REF002");

        // Assert
        Assert.Equal(50000m, wallet.GetBalance(CurrencyType.VND));
        Assert.Equal(2, wallet.Transactions.Count);
    }

    [Fact]
    public void Withdraw_ShouldThrow_WhenInsufficientBalance()
    {
        // Arrange
        var wallet = new Wallet(TestUserId, CurrencyType.VND);
        wallet.Deposit(50000m, CurrencyType.VND, "Initial deposit", "REF001");

        // Act & Assert
        Assert.Throws<InsufficientBalanceException>(() =>
            wallet.Withdraw(100000m, CurrencyType.VND, "Invalid withdrawal"));
    }

    [Fact]
    public void Freeze_ShouldSetStatusToFrozen()
    {
        // Arrange
        var wallet = new Wallet(TestUserId, CurrencyType.VND);

        // Act
        wallet.Freeze();

        // Assert
        Assert.Equal(WalletStatus.Frozen, wallet.Status);
    }

    [Fact]
    public void Unfreeze_ShouldSetStatusToActive()
    {
        // Arrange
        var wallet = new Wallet(TestUserId, CurrencyType.VND);
        wallet.Freeze();

        // Act
        wallet.Unfreeze();

        // Assert
        Assert.Equal(WalletStatus.Active, wallet.Status);
    }

    [Fact]
    public void Close_ShouldSetStatusToClosed()
    {
        // Arrange
        var wallet = new Wallet(TestUserId, CurrencyType.VND);

        // Act
        wallet.Close();

        // Assert
        Assert.Equal(WalletStatus.Closed, wallet.Status);
    }

    [Fact]
    public void Close_ShouldThrow_WhenBalanceIsPositive()
    {
        // Arrange
        var wallet = new Wallet(TestUserId, CurrencyType.VND);
        wallet.Deposit(100000m, CurrencyType.VND, "Deposit", "REF001");

        // Act & Assert
        Assert.Throws<WalletDomainException>(() => wallet.Close());
    }

    #region Multi-Currency Tests / Tests đa tiền tệ

    [Fact]
    public void Deposit_ShouldSupportMultipleCurrencies()
    {
        // Arrange
        var wallet = new Wallet(TestUserId, CurrencyType.VND);

        // Act
        wallet.Deposit(100000m, CurrencyType.VND, "VND deposit");
        wallet.Deposit(100m, CurrencyType.USD, "USD deposit");
        wallet.Deposit(1000, CurrencyType.PPoint, "PPoint deposit");

        // Assert
        Assert.Equal(100000m, wallet.GetBalance(CurrencyType.VND));
        Assert.Equal(100m, wallet.GetBalance(CurrencyType.USD));
        Assert.Equal(1000, wallet.GetBalance(CurrencyType.PPoint));
        Assert.Equal(3, wallet.Balances.Count);
    }

    [Fact]
    public void Exchange_ShouldConvertBetweenCurrencies()
    {
        // Arrange
        var wallet = new Wallet(TestUserId, CurrencyType.VND);
        wallet.Deposit(100000m, CurrencyType.VND, "Initial VND deposit");

        // Act - Exchange 100000 VND to PPoint (rate: 1 VND = 0.001 PPoint)
        var receivedPoints = wallet.Exchange(100000m, CurrencyType.VND, CurrencyType.PPoint);

        // Assert
        Assert.Equal(0, wallet.GetBalance(CurrencyType.VND));
        Assert.Equal(100, wallet.GetBalance(CurrencyType.PPoint)); // 100000 / 1000 = 100
    }

    [Fact]
    public void Exchange_ShouldThrow_WhenSameCurrency()
    {
        // Arrange
        var wallet = new Wallet(TestUserId, CurrencyType.VND);
        wallet.Deposit(100000m, CurrencyType.VND, "Deposit");

        // Act & Assert
        Assert.Throws<WalletDomainException>(() =>
            wallet.Exchange(50000m, CurrencyType.VND, CurrencyType.VND));
    }

    [Fact]
    public void Exchange_ShouldThrow_WhenInsufficientBalance()
    {
        // Arrange
        var wallet = new Wallet(TestUserId, CurrencyType.VND);
        wallet.Deposit(50000m, CurrencyType.VND, "Deposit");

        // Act & Assert
        Assert.Throws<InsufficientBalanceException>(() =>
            wallet.Exchange(100000m, CurrencyType.VND, CurrencyType.PPoint));
    }

    #endregion

    #region Escrow Tests / Tests Ký Quỹ

    [Fact]
    public void Hold_ShouldReduceAvailableBalanceAndCreateHoldItem()
    {
        // Arrange
        var wallet = new Wallet(TestUserId, CurrencyType.VND);
        wallet.Deposit(1000m, CurrencyType.VND, "Init", "REF001");

        // Act
        var hold = wallet.Hold(100m, CurrencyType.VND, "REF", Guid.NewGuid(), "Desc");

        // Assert
        Assert.Equal(900m, wallet.GetBalance(CurrencyType.VND));
        Assert.Single(wallet.Holds);
        Assert.Equal(100m, wallet.GetTotalHeldAmount(CurrencyType.VND));
        Assert.Equal(HoldStatus.Active, hold.Status);
    }

    [Fact]
    public void Hold_ShouldThrow_WhenInsufficientBalance()
    {
        // Arrange
        var wallet = new Wallet(TestUserId, CurrencyType.VND);
        wallet.Deposit(50m, CurrencyType.VND, "Init", "REF001");

        // Act & Assert
        Assert.Throws<InsufficientBalanceException>(() =>
            wallet.Hold(100m, CurrencyType.VND, "REF", Guid.NewGuid(), "Desc"));
    }

    #endregion
}
