namespace WalletService.Domain.AggregatesModel.WalletAggregate;

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

/// <summary>
/// EN: Hold item entity representing a locked/escrowed amount in the wallet.
/// VI: Entity mục giữ đại diện cho số tiền bị khóa/ký quỹ trong ví.
/// </summary>
/// <remarks>
/// EN: Used for escrow operations where funds are locked until a condition is met.
/// VI: Được sử dụng cho các thao tác ký quỹ khi tiền bị khóa cho đến khi điều kiện được đáp ứng.
/// </remarks>
public class HoldItem : Entity
{
    /// <summary>
    /// EN: Wallet ID that owns this hold.
    /// VI: ID ví sở hữu hold này.
    /// </summary>
    public Guid WalletId { get; private set; }

    /// <summary>
    /// EN: Original amount that was held.
    /// VI: Số tiền ban đầu bị giữ.
    /// </summary>
    public decimal OriginalAmount { get; private set; }

    /// <summary>
    /// EN: Remaining amount still held (after partial releases/executions).
    /// VI: Số tiền còn lại vẫn bị giữ (sau các giải phóng/thực thi một phần).
    /// </summary>
    public decimal RemainingAmount { get; private set; }

    /// <summary>
    /// EN: Total amount that has been executed (paid out).
    /// VI: Tổng số tiền đã được thực thi (đã thanh toán).
    /// </summary>
    public decimal ExecutedAmount { get; private set; }

    /// <summary>
    /// EN: Total amount that has been released back to wallet.
    /// VI: Tổng số tiền đã được giải phóng trở lại ví.
    /// </summary>
    public decimal ReleasedAmount { get; private set; }

    /// <summary>
    /// EN: Currency type ID.
    /// VI: ID loại tiền tệ.
    /// </summary>
    public int CurrencyTypeId { get; private set; }

    /// <summary>
    /// EN: Currency type.
    /// VI: Loại tiền tệ.
    /// </summary>
    public CurrencyType CurrencyType { get; private set; } = null!;

    /// <summary>
    /// EN: Reference type (e.g., "CAMPAIGN", "ORDER").
    /// VI: Loại tham chiếu (ví dụ: "CAMPAIGN", "ORDER").
    /// </summary>
    public string ReferenceType { get; private set; } = null!;

    /// <summary>
    /// EN: Reference ID (e.g., CampaignId, OrderId).
    /// VI: ID tham chiếu (ví dụ: CampaignId, OrderId).
    /// </summary>
    public Guid ReferenceId { get; private set; }

    /// <summary>
    /// EN: Description of the hold.
    /// VI: Mô tả của hold.
    /// </summary>
    public string Description { get; private set; } = null!;

    /// <summary>
    /// EN: Hold status.
    /// VI: Trạng thái hold.
    /// </summary>
    public HoldStatus 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: Hold creation timestamp.
    /// VI: Thời điểm tạo hold.
    /// </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; }

    /// <summary>
    /// EN: Hold expiration date (optional).
    /// VI: Ngày hết hạn hold (tùy chọn).
    /// </summary>
    public DateTime? ExpiresAt { get; private set; }

    // EF Core constructor
    protected HoldItem() { }

    /// <summary>
    /// EN: Create a new hold item.
    /// VI: Tạo mục giữ mới.
    /// </summary>
    public HoldItem(
        Guid walletId,
        decimal amount,
        CurrencyType currencyType,
        string referenceType,
        Guid referenceId,
        string description,
        DateTime? expiresAt = null)
    {
        if (amount <= 0)
            throw new WalletDomainException("Hold amount must be greater than zero");

        Id = Guid.NewGuid();
        WalletId = walletId;
        OriginalAmount = amount;
        RemainingAmount = amount;
        ExecutedAmount = 0;
        ReleasedAmount = 0;
        CurrencyType = currencyType;
        CurrencyTypeId = currencyType.Id;
        ReferenceType = referenceType;
        ReferenceId = referenceId;
        Description = description;
        Status = HoldStatus.Active;
        StatusId = Status.Id;
        CreatedAt = DateTime.UtcNow;
        UpdatedAt = DateTime.UtcNow;
        ExpiresAt = expiresAt;
    }

    /// <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="amount">Amount to execute / Số tiền cần thực thi</param>
    /// <param name="executionRef">Optional reference for this execution / Tham chiếu tùy chọn cho thực thi này</param>
    public void Execute(decimal amount, string? executionRef = null)
    {
        ValidateCanModify();

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

        if (amount > RemainingAmount)
            throw new WalletDomainException($"Cannot execute {amount}. Only {RemainingAmount} remaining");

        RemainingAmount -= amount;
        ExecutedAmount += amount;
        UpdatedAt = DateTime.UtcNow;
        UpdateStatus();
    }

    /// <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="amount">Amount to release / Số tiền cần giải phóng</param>
    public void Release(decimal amount)
    {
        ValidateCanModify();

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

        if (amount > RemainingAmount)
            throw new WalletDomainException($"Cannot release {amount}. Only {RemainingAmount} remaining");

        RemainingAmount -= amount;
        ReleasedAmount += amount;
        UpdatedAt = DateTime.UtcNow;
        UpdateStatus();
    }

    /// <summary>
    /// EN: Release all remaining amount back to the wallet.
    /// VI: Giải phóng tất cả số tiền còn lại về ví.
    /// </summary>
    public void ReleaseAll()
    {
        if (RemainingAmount > 0)
        {
            Release(RemainingAmount);
        }
    }

    /// <summary>
    /// EN: Cancel the hold (release all remaining back to wallet).
    /// VI: Hủy hold (giải phóng tất cả số còn lại về ví).
    /// </summary>
    public void Cancel()
    {
        ValidateCanModify();
        
        ReleaseAll();
        Status = HoldStatus.Cancelled;
        StatusId = Status.Id;
        UpdatedAt = DateTime.UtcNow;
    }

    /// <summary>
    /// EN: Check if hold has expired.
    /// VI: Kiểm tra xem hold đã hết hạn chưa.
    /// </summary>
    public bool IsExpired => ExpiresAt.HasValue && DateTime.UtcNow > ExpiresAt.Value;

    /// <summary>
    /// EN: Check if hold is still active (can be modified).
    /// VI: Kiểm tra xem hold còn hoạt động không (có thể sửa đổi).
    /// </summary>
    public bool IsActive => Status == HoldStatus.Active || Status == HoldStatus.PartiallyReleased;

    private void ValidateCanModify()
    {
        if (!IsActive)
            throw new WalletDomainException($"Cannot modify hold in {Status.Name} status");

        if (IsExpired)
            throw new WalletDomainException("Hold has expired");
    }

    private void UpdateStatus()
    {
        if (RemainingAmount == 0)
        {
            // All amount has been used (executed or released)
            Status = ExecutedAmount > 0 ? HoldStatus.Executed : HoldStatus.Released;
        }
        else if (ReleasedAmount > 0)
        {
            // Some amount was released but not all
            Status = HoldStatus.PartiallyReleased;
        }
        // else status remains Active
        
        StatusId = Status.Id;
    }
}
