namespace WalletService.Domain.AggregatesModel.WalletAggregate;

using WalletService.Domain.Events;
using WalletService.Domain.Exceptions;
using WalletService.Domain.SeedWork;

/// <summary>
/// EN: Wallet aggregate root representing a user's digital wallet with multi-currency support.
/// VI: Aggregate root Ví đại diện cho ví điện tử của người dùng với hỗ trợ đa tiền tệ.
/// </summary>
/// <remarks>
/// EN: Wallet manages multiple balances (one per currency type) and supports currency exchange.
/// VI: Wallet quản lý nhiều số dư (một cho mỗi loại tiền tệ) và hỗ trợ quy đổi tiền tệ.
/// </remarks>
public class Wallet : Entity, IAggregateRoot
{
    /// <summary>
    /// EN: User ID from IAM Service.
    /// VI: ID người dùng từ IAM Service.
    /// </summary>
    public Guid UserId { get; private set; }

    /// <summary>
    /// EN: Default currency type ID.
    /// VI: ID loại tiền tệ mặc định.
    /// </summary>
    public int DefaultCurrencyTypeId { get; private set; }

    /// <summary>
    /// EN: Default currency type.
    /// VI: Loại tiền tệ mặc định.
    /// </summary>
    public CurrencyType DefaultCurrencyType { get; private set; } = null!;

    /// <summary>
    /// EN: Legacy balance property - for backward compatibility. Returns default currency balance.
    /// VI: Thuộc tính số dư kế thừa - để tương thích ngược. Trả về số dư tiền tệ mặc định.
    /// </summary>
    [Obsolete("Use GetBalance(CurrencyType) instead for multi-currency support")]
    public Money Balance => GetLegacyBalance();

    /// <summary>
    /// EN: Wallet status.
    /// VI: Trạng thái ví.
    /// </summary>
    public WalletStatus Status { get; private set; } = null!;

    /// <summary>
    /// EN: Status ID for EF Core.
    /// VI: ID trạng thái cho EF Core.
    /// </summary>
    public int StatusId { get; private set; }

    /// <summary>
    /// EN: Wallet creation timestamp.
    /// VI: Thời điểm tạo ví.
    /// </summary>
    public DateTime CreatedAt { get; private set; }

    /// <summary>
    /// EN: Last update timestamp.
    /// VI: Thời điểm cập nhật cuối.
    /// </summary>
    public DateTime UpdatedAt { get; private set; }

    private readonly List<WalletItem> _balances = new();

    /// <summary>
    /// EN: Multi-currency balances.
    /// VI: Số dư đa tiền tệ.
    /// </summary>
    public IReadOnlyCollection<WalletItem> Balances => _balances.AsReadOnly();

    private readonly List<WalletTransaction> _transactions = new();

    /// <summary>
    /// EN: Wallet transactions.
    /// VI: Các giao dịch của ví.
    /// </summary>
    public IReadOnlyCollection<WalletTransaction> Transactions => _transactions.AsReadOnly();

    private readonly List<HoldItem> _holds = new();

    /// <summary>
    /// EN: Active escrow holds.
    /// VI: Các khoản ký quỹ đang hoạt động.
    /// </summary>
    public IReadOnlyCollection<HoldItem> Holds => _holds.AsReadOnly();

    protected Wallet() { }

    /// <summary>
    /// EN: Create a new wallet for a user.
    /// VI: Tạo ví mới cho người dùng.
    /// </summary>
    /// <param name="userId">User ID / ID người dùng</param>
    /// <param name="defaultCurrencyType">Default currency type (default: VND) / Loại tiền tệ mặc định (mặc định: VND)</param>
    public Wallet(Guid userId, CurrencyType? defaultCurrencyType = null)
    {
        var currency = defaultCurrencyType ?? CurrencyType.VND;

        Id = Guid.NewGuid();
        UserId = userId;
        DefaultCurrencyType = currency;
        DefaultCurrencyTypeId = currency.Id;
        Status = WalletStatus.Active;
        StatusId = Status.Id;
        CreatedAt = DateTime.UtcNow;
        UpdatedAt = DateTime.UtcNow;

        // Initialize default currency with zero balance
        _balances.Add(new WalletItem(Id, currency, 0));

        AddDomainEvent(new WalletCreatedDomainEvent(Id, userId));
    }

    #region Multi-Currency Methods

    /// <summary>
    /// EN: Get balance for a specific currency type.
    /// VI: Lấy số dư cho một loại tiền tệ cụ thể.
    /// </summary>
    public decimal GetBalance(CurrencyType currencyType)
    {
        var item = _balances.FirstOrDefault(b => b.CurrencyTypeId == currencyType.Id);
        return item?.Balance ?? 0;
    }

    /// <summary>
    /// EN: Get or create a wallet item for a currency type.
    /// VI: Lấy hoặc tạo wallet item cho một loại tiền tệ.
    /// </summary>
    private WalletItem GetOrCreateBalance(CurrencyType currencyType)
    {
        var item = _balances.FirstOrDefault(b => b.CurrencyTypeId == currencyType.Id);
        if (item == null)
        {
            item = new WalletItem(Id, currencyType, 0);
            _balances.Add(item);
        }
        return item;
    }

    /// <summary>
    /// EN: Deposit amount into wallet for a specific currency.
    /// VI: Nạp tiền vào ví cho một loại tiền tệ cụ thể.
    /// </summary>
    public void Deposit(decimal amount, CurrencyType currencyType, string description, string? referenceId = null)
    {
        EnsureWalletIsActive();

        if (amount <= 0)
            throw new WalletDomainException("Deposit amount must be greater than zero");

        var balance = GetOrCreateBalance(currencyType);
        balance.Add(amount);
        UpdatedAt = DateTime.UtcNow;

        var transaction = new WalletTransaction(
            Id,
            new Money(amount, currencyType.Name),
            TransactionType.Credit,
            balance.Balance,
            description,
            referenceId);

        _transactions.Add(transaction);

        AddDomainEvent(new WalletBalanceChangedDomainEvent(
            Id,
            UserId,
            TransactionType.Credit.Name,
            amount,
            balance.Balance));
    }

    /// <summary>
    /// EN: Withdraw amount from wallet for a specific currency.
    /// VI: Rút tiền từ ví cho một loại tiền tệ cụ thể.
    /// </summary>
    public void Withdraw(decimal amount, CurrencyType currencyType, string description, string? referenceId = null)
    {
        EnsureWalletIsActive();

        if (amount <= 0)
            throw new WalletDomainException("Withdrawal amount must be greater than zero");

        var balance = _balances.FirstOrDefault(b => b.CurrencyTypeId == currencyType.Id);
        if (balance == null || !balance.HasSufficientBalance(amount))
            throw new InsufficientBalanceException(balance?.Balance ?? 0, amount);

        balance.Subtract(amount);
        UpdatedAt = DateTime.UtcNow;

        var transaction = new WalletTransaction(
            Id,
            new Money(amount, currencyType.Name),
            TransactionType.Debit,
            balance.Balance,
            description,
            referenceId);

        _transactions.Add(transaction);

        AddDomainEvent(new WalletBalanceChangedDomainEvent(
            Id,
            UserId,
            TransactionType.Debit.Name,
            amount,
            balance.Balance));
    }

    /// <summary>
    /// EN: Exchange currency within the wallet (atomic operation).
    /// VI: Quy đổi tiền tệ trong ví (thao tác nguyên tử).
    /// </summary>
    /// <param name="fromAmount">Amount to exchange / Số tiền quy đổi</param>
    /// <param name="fromCurrency">Source currency / Tiền tệ nguồn</param>
    /// <param name="toCurrency">Target currency / Tiền tệ đích</param>
    /// <param name="customRate">Optional custom exchange rate / Tỷ giá tùy chỉnh (tuỳ chọn)</param>
    /// <returns>Amount received in target currency / Số tiền nhận được bằng tiền tệ đích</returns>
    public decimal Exchange(decimal fromAmount, CurrencyType fromCurrency, CurrencyType toCurrency, decimal? customRate = null)
    {
        EnsureWalletIsActive();

        if (fromAmount <= 0)
            throw new WalletDomainException("Exchange amount must be greater than zero");

        if (fromCurrency.Id == toCurrency.Id)
            throw new WalletDomainException("Cannot exchange to the same currency");

        // Calculate exchange rate
        var rate = customRate ?? fromCurrency.GetExchangeRateTo(toCurrency);
        if (rate <= 0)
            throw new WalletDomainException("Exchange rate must be greater than zero");

        // Calculate target amount
        var toAmount = Math.Round(fromAmount * rate, 2);

        // Withdraw from source (validates sufficient balance)
        Withdraw(fromAmount, fromCurrency, $"Exchange to {toCurrency.Name}", null);

        // Deposit to target
        Deposit(toAmount, toCurrency, $"Exchange from {fromCurrency.Name}", null);

        // Raise exchange domain event
        AddDomainEvent(new WalletExchangedDomainEvent(
            Id,
            UserId,
            fromCurrency.Id,
            toCurrency.Id,
            fromAmount,
            toAmount,
            rate));

        return toAmount;
    }

    #endregion

    #region Legacy Methods (Backward Compatibility)

    private Money GetLegacyBalance()
    {
        var defaultBalance = _balances.FirstOrDefault(b => b.CurrencyTypeId == DefaultCurrencyTypeId);
        return new Money(defaultBalance?.Balance ?? 0, DefaultCurrencyType?.Name ?? "VND");
    }

    /// <summary>
    /// EN: Deposit money into the wallet (legacy method - uses default currency).
    /// VI: Nạp tiền vào ví (phương thức kế thừa - sử dụng tiền tệ mặc định).
    /// </summary>
    [Obsolete("Use Deposit(decimal, CurrencyType, string, string?) instead")]
    public void Deposit(Money amount, string description, string? referenceId = null)
    {
        var currencyType = CurrencyType.FromDisplayName<CurrencyType>(amount.Currency) 
            ?? throw new WalletDomainException($"Unknown currency: {amount.Currency}");
        
        Deposit(amount.Amount, currencyType, description, referenceId);
    }

    /// <summary>
    /// EN: Withdraw money from the wallet (legacy method - uses default currency).
    /// VI: Rút tiền khỏi ví (phương thức kế thừa - sử dụng tiền tệ mặc định).
    /// </summary>
    [Obsolete("Use Withdraw(decimal, CurrencyType, string, string?) instead")]
    public void Withdraw(Money amount, string description, string? referenceId = null)
    {
        var currencyType = CurrencyType.FromDisplayName<CurrencyType>(amount.Currency)
            ?? throw new WalletDomainException($"Unknown currency: {amount.Currency}");
        
        Withdraw(amount.Amount, currencyType, description, referenceId);
    }

    /// <summary>
    /// EN: Transfer money to another wallet.
    /// VI: Chuyển tiền đến ví khác.
    /// </summary>
    public void TransferOut(Money amount, Guid toWalletId, string description)
    {
        EnsureWalletIsActive();

        var currencyType = CurrencyType.FromDisplayName<CurrencyType>(amount.Currency)
            ?? throw new WalletDomainException($"Unknown currency: {amount.Currency}");

        if (amount.Amount <= 0)
            throw new WalletDomainException("Transfer amount must be greater than zero");

        var balance = _balances.FirstOrDefault(b => b.CurrencyTypeId == currencyType.Id);
        if (balance == null || !balance.HasSufficientBalance(amount.Amount))
            throw new InsufficientBalanceException(balance?.Balance ?? 0, amount.Amount);

        balance.Subtract(amount.Amount);
        UpdatedAt = DateTime.UtcNow;

        var transaction = new WalletTransaction(
            Id,
            amount,
            TransactionType.TransferOut,
            balance.Balance,
            description,
            toWalletId.ToString());

        _transactions.Add(transaction);

        AddDomainEvent(new WalletBalanceChangedDomainEvent(
            Id,
            UserId,
            TransactionType.TransferOut.Name,
            amount.Amount,
            balance.Balance));
    }

    /// <summary>
    /// EN: Receive transfer from another wallet.
    /// VI: Nhận chuyển khoản từ ví khác.
    /// </summary>
    public void TransferIn(Money amount, Guid fromWalletId, string description)
    {
        EnsureWalletIsActive();

        var currencyType = CurrencyType.FromDisplayName<CurrencyType>(amount.Currency)
            ?? throw new WalletDomainException($"Unknown currency: {amount.Currency}");

        var balance = GetOrCreateBalance(currencyType);
        balance.Add(amount.Amount);
        UpdatedAt = DateTime.UtcNow;

        var transaction = new WalletTransaction(
            Id,
            amount,
            TransactionType.TransferIn,
            balance.Balance,
            description,
            fromWalletId.ToString());

        _transactions.Add(transaction);

        AddDomainEvent(new WalletBalanceChangedDomainEvent(
            Id,
            UserId,
            TransactionType.TransferIn.Name,
            amount.Amount,
            balance.Balance));
    }

    #endregion

    #region Wallet Status Management

    /// <summary>
    /// EN: Freeze the wallet (no transactions allowed).
    /// VI: Đóng băng ví (không cho phép giao dịch).
    /// </summary>
    public void Freeze()
    {
        if (Status == WalletStatus.Closed)
            throw new WalletDomainException("Cannot freeze a closed wallet");

        Status = WalletStatus.Frozen;
        StatusId = Status.Id;
        UpdatedAt = DateTime.UtcNow;
    }

    /// <summary>
    /// EN: Unfreeze the wallet.
    /// VI: Mở băng ví.
    /// </summary>
    public void Unfreeze()
    {
        if (Status != WalletStatus.Frozen)
            throw new WalletDomainException("Only frozen wallets can be unfrozen");

        Status = WalletStatus.Active;
        StatusId = Status.Id;
        UpdatedAt = DateTime.UtcNow;
    }

    /// <summary>
    /// EN: Close the wallet permanently.
    /// VI: Đóng ví vĩnh viễn.
    /// </summary>
    public void Close()
    {
        // Check all balances are zero
        if (_balances.Any(b => b.Balance > 0))
            throw new WalletDomainException("Cannot close wallet with positive balance in any currency");

        Status = WalletStatus.Closed;
        StatusId = Status.Id;
        UpdatedAt = DateTime.UtcNow;
    }

    private void EnsureWalletIsActive()
    {
        if (Status != WalletStatus.Active)
            throw new WalletDomainException($"Wallet is {Status.Name}. Only active wallets can perform transactions");
    }

    #endregion

    #region Escrow Methods

    /// <summary>
    /// EN: Create an escrow hold - lock funds for a campaign or order.
    /// VI: Tạo escrow hold - khóa tiền cho chiến dịch hoặc đơn hàng.
    /// </summary>
    /// <param name="amount">Amount to hold / Số tiền cần giữ</param>
    /// <param name="currencyType">Currency type / Loại tiền tệ</param>
    /// <param name="referenceType">Reference type (e.g., "CAMPAIGN") / Loại tham chiếu</param>
    /// <param name="referenceId">Reference ID (e.g., CampaignId) / ID tham chiếu</param>
    /// <param name="description">Description / Mô tả</param>
    /// <param name="expiresAt">Optional expiration / Hết hạn tùy chọn</param>
    /// <returns>The created HoldItem / Mục hold đã tạo</returns>
    public HoldItem Hold(
        decimal amount,
        CurrencyType currencyType,
        string referenceType,
        Guid referenceId,
        string description,
        DateTime? expiresAt = null)
    {
        EnsureWalletIsActive();

        if (amount <= 0)
            throw new WalletDomainException("Hold amount must be greater than zero");

        // Check sufficient balance
        var balance = _balances.FirstOrDefault(b => b.CurrencyTypeId == currencyType.Id);
        if (balance == null || !balance.HasSufficientBalance(amount))
            throw new InsufficientBalanceException(balance?.Balance ?? 0, amount);

        // Subtract from available balance
        balance.Subtract(amount);
        UpdatedAt = DateTime.UtcNow;

        // Create hold
        var hold = new HoldItem(
            Id,
            amount,
            currencyType,
            referenceType,
            referenceId,
            description,
            expiresAt);

        _holds.Add(hold);

        // Record transaction
        var transaction = new WalletTransaction(
            Id,
            new Money(amount, currencyType.Name),
            TransactionType.HoldCreated,
            balance.Balance,
            $"Hold created: {description}",
            hold.Id.ToString());

        _transactions.Add(transaction);

        AddDomainEvent(new EscrowHeldDomainEvent(
            Id,
            hold.Id,
            UserId,
            referenceType,
            referenceId,
            amount,
            currencyType.Id));

        return hold;
    }

    /// <summary>
    /// EN: Execute (commit) a portion of the held amount.
    /// VI: Thực thi (cam kết) một phần số tiền bị giữ.
    /// </summary>
    /// <param name="holdId">Hold ID / ID hold</param>
    /// <param name="amount">Amount to execute / Số tiền cần thực thi</param>
    /// <param name="executionRef">Execution reference / Tham chiếu thực thi</param>
    public void ExecuteHold(Guid holdId, decimal amount, string? executionRef = null)
    {
        EnsureWalletIsActive();

        var hold = _holds.FirstOrDefault(h => h.Id == holdId);
        if (hold == null)
            throw new WalletDomainException($"Hold {holdId} not found");

        var remainingBefore = hold.RemainingAmount;
        hold.Execute(amount, executionRef);
        UpdatedAt = DateTime.UtcNow;

        // Record transaction
        var transaction = new WalletTransaction(
            Id,
            new Money(amount, hold.CurrencyType.Name),
            TransactionType.HoldExecuted,
            GetBalance(hold.CurrencyType),
            $"Hold executed: {executionRef ?? hold.Description}",
            holdId.ToString());

        _transactions.Add(transaction);

        AddDomainEvent(new EscrowExecutedDomainEvent(
            Id,
            holdId,
            UserId,
            amount,
            hold.RemainingAmount,
            executionRef));
    }

    /// <summary>
    /// EN: Release (return) a portion of the held amount back to the wallet.
    /// VI: Giải phóng (trả lại) một phần số tiền bị giữ về ví.
    /// </summary>
    /// <param name="holdId">Hold ID / ID hold</param>
    /// <param name="amount">Amount to release (null = all remaining) / Số tiền giải phóng (null = tất cả còn lại)</param>
    public void ReleaseHold(Guid holdId, decimal? amount = null)
    {
        EnsureWalletIsActive();

        var hold = _holds.FirstOrDefault(h => h.Id == holdId);
        if (hold == null)
            throw new WalletDomainException($"Hold {holdId} not found");

        var releaseAmount = amount ?? hold.RemainingAmount;
        if (releaseAmount <= 0)
            return; // Nothing to release

        // Release from hold
        hold.Release(releaseAmount);

        // Return to available balance
        var balance = GetOrCreateBalance(hold.CurrencyType);
        balance.Add(releaseAmount);
        UpdatedAt = DateTime.UtcNow;

        // Record transaction
        var transaction = new WalletTransaction(
            Id,
            new Money(releaseAmount, hold.CurrencyType.Name),
            TransactionType.HoldReleased,
            balance.Balance,
            $"Hold released: {hold.Description}",
            holdId.ToString());

        _transactions.Add(transaction);

        AddDomainEvent(new EscrowReleasedDomainEvent(
            Id,
            holdId,
            UserId,
            releaseAmount,
            hold.RemainingAmount));
    }

    /// <summary>
    /// EN: Cancel a hold and release all remaining back to wallet.
    /// VI: Hủy hold và giải phóng tất cả số còn lại về ví.
    /// </summary>
    public void CancelHold(Guid holdId)
    {
        var hold = _holds.FirstOrDefault(h => h.Id == holdId);
        if (hold == null)
            throw new WalletDomainException($"Hold {holdId} not found");

        if (hold.RemainingAmount > 0)
        {
            ReleaseHold(holdId, hold.RemainingAmount);
        }
    }

    /// <summary>
    /// EN: Get a hold by ID.
    /// VI: Lấy hold theo ID.
    /// </summary>
    public HoldItem? GetHold(Guid holdId)
    {
        return _holds.FirstOrDefault(h => h.Id == holdId);
    }

    /// <summary>
    /// EN: Get holds by reference.
    /// VI: Lấy holds theo tham chiếu.
    /// </summary>
    public IEnumerable<HoldItem> GetHoldsByReference(string referenceType, Guid referenceId)
    {
        return _holds.Where(h => h.ReferenceType == referenceType && h.ReferenceId == referenceId);
    }

    /// <summary>
    /// EN: Get total held amount for a currency type.
    /// VI: Lấy tổng số tiền bị giữ cho một loại tiền tệ.
    /// </summary>
    public decimal GetTotalHeldAmount(CurrencyType currencyType)
    {
        return _holds
            .Where(h => h.CurrencyTypeId == currencyType.Id && h.IsActive)
            .Sum(h => h.RemainingAmount);
    }

    #endregion
}
