namespace PromotionService.Domain.AggregatesModel.CampaignAggregate;

using PromotionService.Domain.Events;
using PromotionService.Domain.Exceptions;
using PromotionService.Domain.SeedWork;

/// <summary>
/// EN: Campaign Aggregate Root - represents a marketing campaign with vouchers.
/// VI: Campaign Aggregate Root - đại diện cho một chiến dịch marketing với voucher.
/// </summary>
public class Campaign : Entity, IAggregateRoot
{
    private readonly List<Voucher> _vouchers = new();

    /// <summary>
    /// EN: ID of the merchant who owns the campaign.
    /// VI: ID của merchant sở hữu chiến dịch.
    /// </summary>
    public Guid MerchantId { get; private set; }

    /// <summary>
    /// EN: Campaign name.
    /// VI: Tên chiến dịch.
    /// </summary>
    public string Name { get; private set; } = null!;

    /// <summary>
    /// EN: Campaign description.
    /// VI: Mô tả chiến dịch.
    /// </summary>
    public string? Description { get; private set; }

    #region Backing Asset (What the voucher contains)

    /// <summary>
    /// EN: Type of backing asset (Currency or Point).
    /// VI: Loại tài sản đảm bảo (Tiền tệ hoặc Điểm).
    /// </summary>
    public AssetType BackingAssetType { get; private set; } = null!;
    public int BackingAssetTypeId { get; private set; }

    /// <summary>
    /// EN: Currency/Point code (e.g., VND, USD, PPoint).
    /// VI: Mã tiền tệ/điểm (ví dụ: VND, USD, PPoint).
    /// </summary>
    public string BackingAssetCode { get; private set; } = null!;

    /// <summary>
    /// EN: Face value of each voucher.
    /// VI: Mệnh giá của mỗi voucher.
    /// </summary>
    public decimal FaceValue { get; private set; }

    #endregion

    #region Acquisition (How user gets the voucher)

    /// <summary>
    /// EN: How user acquires the voucher (Free, ExchangePoints, Purchase).
    /// VI: Cách người dùng nhận voucher (Miễn phí, Đổi điểm, Mua).
    /// </summary>
    public AcquisitionType AcquisitionType { get; private set; } = null!;
    public int AcquisitionTypeId { get; private set; }

    /// <summary>
    /// EN: Price to acquire (points or currency amount).
    /// VI: Giá để nhận (số điểm hoặc số tiền).
    /// </summary>
    public decimal AcquisitionPrice { get; private set; }

    #endregion

    #region Escrow (Wallet Service Hold)

    /// <summary>
    /// EN: Hold ID from Wallet Service for escrow.
    /// VI: ID giữ từ Wallet Service cho ký quỹ.
    /// </summary>
    public Guid? EscrowHoldId { get; private set; }

    /// <summary>
    /// EN: Wallet ID that provides the escrow.
    /// VI: ID ví cung cấp ký quỹ.
    /// </summary>
    public Guid? EscrowWalletId { get; private set; }

    /// <summary>
    /// EN: Total amount held in escrow.
    /// VI: Tổng số tiền ký quỹ.
    /// </summary>
    public decimal EscrowAmount { get; private set; }

    #endregion

    #region Campaign Limits

    /// <summary>
    /// EN: Total number of vouchers to issue.
    /// VI: Tổng số voucher phát hành.
    /// </summary>
    public int TotalVouchers { get; private set; }

    /// <summary>
    /// EN: Number of vouchers already issued (claimed).
    /// VI: Số voucher đã phát hành (đã nhận).
    /// </summary>
    public int IssuedVouchers { get; private set; }

    /// <summary>
    /// EN: Maximum vouchers per user (0 = unlimited).
    /// VI: Số voucher tối đa mỗi người dùng (0 = không giới hạn).
    /// </summary>
    public int MaxPerUser { get; private set; }

    /// <summary>
    /// EN: Campaign start date.
    /// VI: Ngày bắt đầu chiến dịch.
    /// </summary>
    public DateTime StartDate { get; private set; }

    /// <summary>
    /// EN: Campaign end date.
    /// VI: Ngày kết thúc chiến dịch.
    /// </summary>
    public DateTime EndDate { get; private set; }

    /// <summary>
    /// EN: Voucher validity days after claiming.
    /// VI: Số ngày voucher có hiệu lực sau khi nhận.
    /// </summary>
    public int VoucherValidityDays { get; private set; }

    #endregion

    #region Status

    /// <summary>
    /// EN: Current campaign status.
    /// VI: Trạng thái chiến dịch hiện tại.
    /// </summary>
    public CampaignStatus Status { get; private set; } = null!;
    public int StatusId { get; private set; }

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

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

    #endregion

    /// <summary>
    /// EN: Collection of vouchers in this campaign.
    /// VI: Danh sách voucher trong chiến dịch này.
    /// </summary>
    public IReadOnlyCollection<Voucher> Vouchers => _vouchers.AsReadOnly();

    // EF Core constructor
    protected Campaign() { }

    /// <summary>
    /// EN: Create a new campaign.
    /// VI: Tạo chiến dịch mới.
    /// </summary>
    public Campaign(
        Guid merchantId,
        string name,
        string? description,
        AssetType backingAssetType,
        string backingAssetCode,
        decimal faceValue,
        AcquisitionType acquisitionType,
        decimal acquisitionPrice,
        int totalVouchers,
        DateTime startDate,
        DateTime endDate,
        int voucherValidityDays = 30,
        int maxPerUser = 1)
    {
        if (string.IsNullOrWhiteSpace(name))
            throw new PromotionDomainException("Campaign name is required");
        if (faceValue <= 0)
            throw new PromotionDomainException("Face value must be greater than zero");
        if (totalVouchers <= 0)
            throw new PromotionDomainException("Total vouchers must be greater than zero");
        if (endDate <= startDate)
            throw new PromotionDomainException("End date must be after start date");
        if (acquisitionType != AcquisitionType.Free && acquisitionPrice <= 0)
            throw new PromotionDomainException("Acquisition price is required for non-free campaigns");

        Id = Guid.NewGuid();
        MerchantId = merchantId;
        Name = name;
        Description = description;
        BackingAssetType = backingAssetType;
        BackingAssetTypeId = backingAssetType.Id;
        BackingAssetCode = backingAssetCode;
        FaceValue = faceValue;
        AcquisitionType = acquisitionType;
        AcquisitionTypeId = acquisitionType.Id;
        AcquisitionPrice = acquisitionPrice;
        TotalVouchers = totalVouchers;
        IssuedVouchers = 0;
        MaxPerUser = maxPerUser;
        StartDate = startDate;
        EndDate = endDate;
        VoucherValidityDays = voucherValidityDays;
        Status = CampaignStatus.Draft;
        StatusId = Status.Id;
        CreatedAt = DateTime.UtcNow;
        UpdatedAt = DateTime.UtcNow;

        // Calculate escrow amount
        EscrowAmount = faceValue * totalVouchers;

        AddDomainEvent(new CampaignCreatedDomainEvent(Id, merchantId, name));
    }

    #region Escrow Management

    /// <summary>
    /// EN: Set the escrow hold ID after Wallet Service confirms the hold.
    /// VI: Đặt ID giữ ký quỹ sau khi Wallet Service xác nhận giữ.
    /// </summary>
    public void SetEscrowHold(Guid walletId, Guid holdId)
    {
        if (Status != CampaignStatus.Draft)
            throw new PromotionDomainException("Can only set escrow on draft campaigns");

        EscrowWalletId = walletId;
        EscrowHoldId = holdId;
        UpdatedAt = DateTime.UtcNow;
    }

    #endregion

    #region Lifecycle Methods

    /// <summary>
    /// EN: Activate the campaign (make it live).
    /// VI: Kích hoạt chiến dịch (đưa vào hoạt động).
    /// </summary>
    public void Activate()
    {
        if (Status != CampaignStatus.Draft && Status != CampaignStatus.Paused)
            throw new PromotionDomainException($"Cannot activate campaign in {Status.Name} status");

        if (!EscrowHoldId.HasValue)
            throw new PromotionDomainException("Escrow must be set before activating");

        Status = CampaignStatus.Active;
        StatusId = Status.Id;
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new CampaignActivatedDomainEvent(Id, MerchantId));
    }

    /// <summary>
    /// EN: Pause the campaign (temporarily stop).
    /// VI: Tạm dừng chiến dịch (dừng tạm thời).
    /// </summary>
    public void Pause()
    {
        if (Status != CampaignStatus.Active)
            throw new PromotionDomainException("Can only pause active campaigns");

        Status = CampaignStatus.Paused;
        StatusId = Status.Id;
        UpdatedAt = DateTime.UtcNow;
    }

    /// <summary>
    /// EN: Cancel the campaign (release escrow).
    /// VI: Hủy chiến dịch (giải phóng ký quỹ).
    /// </summary>
    public void Cancel()
    {
        if (Status == CampaignStatus.Completed || Status == CampaignStatus.Cancelled)
            throw new PromotionDomainException($"Cannot cancel campaign in {Status.Name} status");

        Status = CampaignStatus.Cancelled;
        StatusId = Status.Id;
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new CampaignCancelledDomainEvent(Id, MerchantId, EscrowHoldId));
    }

    /// <summary>
    /// EN: Complete the campaign (normal end).
    /// VI: Hoàn thành chiến dịch (kết thúc bình thường).
    /// </summary>
    public void Complete()
    {
        if (Status != CampaignStatus.Active && Status != CampaignStatus.Paused)
            throw new PromotionDomainException($"Cannot complete campaign in {Status.Name} status");

        Status = CampaignStatus.Completed;
        StatusId = Status.Id;
        UpdatedAt = DateTime.UtcNow;
    }

    /// <summary>
    /// EN: Update campaign details (admin only).
    /// VI: Cập nhật thông tin chiến dịch (chỉ admin).
    /// </summary>
    public void Update(string? name, string? description, DateTime? startDate, DateTime? endDate, int? maxPerUser)
    {
        if (!string.IsNullOrWhiteSpace(name))
            Name = name;

        if (description != null)
            Description = description;

        if (startDate.HasValue)
        {
            if (endDate.HasValue && endDate.Value <= startDate.Value)
                throw new PromotionDomainException("End date must be after start date");
            StartDate = startDate.Value;
        }

        if (endDate.HasValue)
        {
            if (endDate.Value <= StartDate)
                throw new PromotionDomainException("End date must be after start date");
            EndDate = endDate.Value;
        }

        if (maxPerUser.HasValue && maxPerUser.Value >= 0)
            MaxPerUser = maxPerUser.Value;

        UpdatedAt = DateTime.UtcNow;
    }

    #endregion

    #region Voucher Management

    /// <summary>
    /// EN: Generate voucher codes for this campaign.
    /// VI: Tạo mã voucher cho chiến dịch này.
    /// </summary>
    public void GenerateVouchers(int count)
    {
        if (Status != CampaignStatus.Draft)
            throw new PromotionDomainException("Can only generate vouchers for draft campaigns");

        if (_vouchers.Count + count > TotalVouchers)
            throw new PromotionDomainException($"Cannot generate {count} vouchers. Maximum is {TotalVouchers}");

        for (int i = 0; i < count; i++)
        {
            var code = GenerateVoucherCode();
            var voucher = new Voucher(Id, code, FaceValue, VoucherValidityDays);
            _vouchers.Add(voucher);
        }

        UpdatedAt = DateTime.UtcNow;
    }

    /// <summary>
    /// EN: Issue a voucher to a user (claim).
    /// VI: Phát voucher cho người dùng (nhận).
    /// </summary>
    public Voucher IssueVoucher(Guid userId)
    {
        if (Status != CampaignStatus.Active)
            throw new CampaignNotActiveException(Id);

        if (DateTime.UtcNow < StartDate || DateTime.UtcNow > EndDate)
            throw new PromotionDomainException("Campaign is not within active date range");

        // Check max per user
        if (MaxPerUser > 0)
        {
            var userVoucherCount = _vouchers.Count(v => v.OwnerId == userId);
            if (userVoucherCount >= MaxPerUser)
                throw new PromotionDomainException($"User already has maximum {MaxPerUser} vouchers from this campaign");
        }

        // Find available voucher
        var voucher = _vouchers.FirstOrDefault(v => v.Status == VoucherStatus.Available);
        if (voucher == null)
            throw new PromotionDomainException("No available vouchers in this campaign");

        voucher.Claim(userId, VoucherValidityDays);
        IssuedVouchers++;
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new VoucherClaimedDomainEvent(voucher.Id, Id, userId, voucher.Code));

        return voucher;
    }

    /// <summary>
    /// EN: Get a specific voucher by code.
    /// VI: Lấy voucher theo mã.
    /// </summary>
    public Voucher? GetVoucherByCode(string code)
    {
        return _vouchers.FirstOrDefault(v => v.Code.Equals(code, StringComparison.OrdinalIgnoreCase));
    }

    /// <summary>
    /// EN: Get vouchers for a specific user.
    /// VI: Lấy voucher của người dùng.
    /// </summary>
    public IEnumerable<Voucher> GetUserVouchers(Guid userId)
    {
        return _vouchers.Where(v => v.OwnerId == userId);
    }

    private string GenerateVoucherCode()
    {
        // Format: PREFIX-RANDOM (e.g., CAMP-A1B2C3)
        const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        var random = new Random();
        var code = new string(Enumerable.Repeat(chars, 6)
            .Select(s => s[random.Next(s.Length)]).ToArray());
        
        // Ensure uniqueness
        while (_vouchers.Any(v => v.Code == $"V{code}"))
        {
            code = new string(Enumerable.Repeat(chars, 6)
                .Select(s => s[random.Next(s.Length)]).ToArray());
        }
        
        return $"V{code}";
    }

    #endregion

    #region Statistics

    /// <summary>
    /// EN: Get available voucher count.
    /// VI: Lấy số voucher còn lại.
    /// </summary>
    public int AvailableVoucherCount => _vouchers.Count(v => v.Status == VoucherStatus.Available);

    /// <summary>
    /// EN: Get claimed voucher count.
    /// VI: Lấy số voucher đã nhận.
    /// </summary>
    public int ClaimedVoucherCount => _vouchers.Count(v => v.Status != VoucherStatus.Available);

    /// <summary>
    /// EN: Get redeemed voucher count.
    /// VI: Lấy số voucher đã sử dụng.
    /// </summary>
    public int RedeemedVoucherCount => _vouchers.Count(v => 
        v.Status == VoucherStatus.PartiallyRedeemed || v.Status == VoucherStatus.FullyRedeemed);

    /// <summary>
    /// EN: Get total redeemed value.
    /// VI: Lấy tổng giá trị đã sử dụng.
    /// </summary>
    public decimal TotalRedeemedValue => _vouchers.Sum(v => v.FaceValue - v.RemainingValue);

    #endregion
}
