namespace WalletService.Domain.AggregatesModel.PointAccountAggregate;

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

/// <summary>
/// EN: Point account aggregate root representing a user's loyalty points
/// VI: Aggregate root tài khoản điểm đại diện cho điểm thưởng của người dùng
/// </summary>
public class PointAccount : 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: Total points ever earned (lifetime)
    /// VI: Tổng điểm đã từng tích được (suốt đời)
    /// </summary>
    public long TotalPoints { get; private set; }

    /// <summary>
    /// EN: Currently available points
    /// VI: Điểm hiện có thể sử dụng
    /// </summary>
    public long AvailablePoints { get; private set; }

    /// <summary>
    /// EN: Account creation timestamp
    /// VI: Thời điểm tạo tài khoản
    /// </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<PointTransaction> _transactions = new();

    /// <summary>
    /// EN: Point transactions
    /// VI: Các giao dịch điểm
    /// </summary>
    public IReadOnlyCollection<PointTransaction> Transactions => _transactions.AsReadOnly();

    protected PointAccount() { }

    /// <summary>
    /// EN: Create a new point account for a user
    /// VI: Tạo tài khoản điểm mới cho người dùng
    /// </summary>
    public PointAccount(Guid userId)
    {
        Id = Guid.NewGuid();
        UserId = userId;
        TotalPoints = 0;
        AvailablePoints = 0;
        CreatedAt = DateTime.UtcNow;
        UpdatedAt = DateTime.UtcNow;
    }

    /// <summary>
    /// EN: Earn points (from purchases, promotions, etc.)
    /// VI: Tích điểm (từ mua hàng, khuyến mãi, v.v.)
    /// </summary>
    /// <param name="points">Number of points to earn</param>
    /// <param name="source">Source of points (e.g., order ID)</param>
    /// <param name="description">Description of the transaction</param>
    /// <param name="expiresAt">When the points expire (optional)</param>
    public void EarnPoints(long points, string source, string description, DateTime? expiresAt = null)
    {
        if (points <= 0)
            throw new PointsDomainException("Points to earn must be greater than zero");

        TotalPoints += points;
        AvailablePoints += points;
        UpdatedAt = DateTime.UtcNow;

        var transaction = new PointTransaction(
            Id,
            points,
            PointTransactionType.Earn,
            AvailablePoints,
            source,
            description,
            expiresAt);

        _transactions.Add(transaction);

        AddDomainEvent(new PointsEarnedDomainEvent(Id, UserId, points, AvailablePoints, source));
    }

    /// <summary>
    /// EN: Spend points (for purchases, rewards, etc.)
    /// VI: Tiêu điểm (để mua hàng, đổi thưởng, v.v.)
    /// </summary>
    public void SpendPoints(long points, string source, string description)
    {
        if (points <= 0)
            throw new PointsDomainException("Points to spend must be greater than zero");

        if (AvailablePoints < points)
            throw new InsufficientPointsException(AvailablePoints, points);

        AvailablePoints -= points;
        UpdatedAt = DateTime.UtcNow;

        var transaction = new PointTransaction(
            Id,
            points,
            PointTransactionType.Spend,
            AvailablePoints,
            source,
            description);

        _transactions.Add(transaction);

        AddDomainEvent(new PointsSpentDomainEvent(Id, UserId, points, AvailablePoints, source));
    }

    /// <summary>
    /// EN: Expire points (automatic process for expired points)
    /// VI: Hết hạn điểm (quy trình tự động cho điểm hết hạn)
    /// </summary>
    public void ExpirePoints(long points, string description)
    {
        if (points <= 0)
            throw new PointsDomainException("Points to expire must be greater than zero");

        if (AvailablePoints < points)
            points = AvailablePoints; // Expire only available points

        AvailablePoints -= points;
        UpdatedAt = DateTime.UtcNow;

        var transaction = new PointTransaction(
            Id,
            points,
            PointTransactionType.Expire,
            AvailablePoints,
            "SYSTEM",
            description);

        _transactions.Add(transaction);
    }

    /// <summary>
    /// EN: Add bonus points (from special promotions)
    /// VI: Thêm điểm thưởng (từ khuyến mãi đặc biệt)
    /// </summary>
    public void AddBonusPoints(long points, string source, string description, DateTime? expiresAt = null)
    {
        if (points <= 0)
            throw new PointsDomainException("Bonus points must be greater than zero");

        TotalPoints += points;
        AvailablePoints += points;
        UpdatedAt = DateTime.UtcNow;

        var transaction = new PointTransaction(
            Id,
            points,
            PointTransactionType.Bonus,
            AvailablePoints,
            source,
            description,
            expiresAt);

        _transactions.Add(transaction);

        AddDomainEvent(new PointsEarnedDomainEvent(Id, UserId, points, AvailablePoints, source));
    }

    /// <summary>
    /// EN: Adjust points manually (admin operation)
    /// VI: Điều chỉnh điểm thủ công (thao tác admin)
    /// </summary>
    public void AdjustPoints(long adjustment, string reason)
    {
        if (adjustment == 0)
            throw new PointsDomainException("Adjustment cannot be zero");

        if (adjustment < 0 && AvailablePoints < Math.Abs(adjustment))
            throw new InsufficientPointsException(AvailablePoints, Math.Abs(adjustment));

        AvailablePoints += adjustment;
        if (adjustment > 0)
            TotalPoints += adjustment;

        UpdatedAt = DateTime.UtcNow;

        var transaction = new PointTransaction(
            Id,
            Math.Abs(adjustment),
            PointTransactionType.Adjust,
            AvailablePoints,
            "ADMIN",
            reason);

        _transactions.Add(transaction);
    }
}
