using MediatR;
using Microsoft.Extensions.Logging;
using PromotionService.API.Application.DTOs;
using PromotionService.API.Application.Services;
using PromotionService.Domain.AggregatesModel.CampaignAggregate;
using PromotionService.Domain.AggregatesModel.RedemptionAggregate;
using PromotionService.Domain.Events;
using PromotionService.Domain.Exceptions;

namespace PromotionService.API.Application.Commands;

/// <summary>
/// EN: Handler for ClaimVoucherCommand (free vouchers).
/// VI: Handler cho ClaimVoucherCommand (voucher miễn phí).
/// </summary>
public class ClaimVoucherCommandHandler : IRequestHandler<ClaimVoucherCommand, VoucherDto>
{
    private readonly ICampaignRepository _campaignRepository;
    private readonly ILogger<ClaimVoucherCommandHandler> _logger;

    public ClaimVoucherCommandHandler(
        ICampaignRepository campaignRepository,
        ILogger<ClaimVoucherCommandHandler> logger)
    {
        _campaignRepository = campaignRepository;
        _logger = logger;
    }

    public async Task<VoucherDto> Handle(ClaimVoucherCommand request, CancellationToken cancellationToken)
    {
        var campaign = await _campaignRepository.GetByIdAsync(request.CampaignId)
            ?? throw new PromotionDomainException($"Campaign {request.CampaignId} not found");

        // Verify it's a free campaign
        if (campaign.AcquisitionTypeId != AcquisitionType.Free.Id)
            throw new PromotionDomainException("This campaign requires payment or point exchange");

        var voucher = campaign.IssueVoucher(request.UserId);
        _campaignRepository.Update(campaign);
        await _campaignRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);

        _logger.LogInformation("Voucher {VoucherCode} claimed by user {UserId}", voucher.Code, request.UserId);

        return MapToDto(voucher);
    }

    private static VoucherDto MapToDto(Voucher voucher) => new(
        voucher.Id,
        voucher.CampaignId,
        voucher.Code,
        voucher.OwnerId,
        voucher.FaceValue,
        voucher.RemainingValue,
        voucher.StatusId == VoucherStatus.Available.Id ? "Available"
            : voucher.StatusId == VoucherStatus.Claimed.Id ? "Claimed"
            : voucher.StatusId == VoucherStatus.PartiallyRedeemed.Id ? "PartiallyRedeemed"
            : voucher.StatusId == VoucherStatus.FullyRedeemed.Id ? "FullyRedeemed"
            : "Expired",
        voucher.ClaimedAt,
        voucher.ExpiresAt,
        voucher.RedeemedAt);
}

/// <summary>
/// EN: Handler for RedeemVoucherCommand.
/// VI: Handler cho RedeemVoucherCommand.
/// </summary>
public class RedeemVoucherCommandHandler : IRequestHandler<RedeemVoucherCommand, RedemptionDto>
{
    private readonly ICampaignRepository _campaignRepository;
    private readonly IRedemptionRepository _redemptionRepository;
    private readonly IWalletServiceClient _walletService;
    private readonly ILogger<RedeemVoucherCommandHandler> _logger;

    public RedeemVoucherCommandHandler(
        ICampaignRepository campaignRepository,
        IRedemptionRepository redemptionRepository,
        IWalletServiceClient walletService,
        ILogger<RedeemVoucherCommandHandler> logger)
    {
        _campaignRepository = campaignRepository;
        _redemptionRepository = redemptionRepository;
        _walletService = walletService;
        _logger = logger;
    }

    public async Task<RedemptionDto> Handle(RedeemVoucherCommand request, CancellationToken cancellationToken)
    {
        // Find voucher by code
        var voucher = await _campaignRepository.GetVoucherByCodeAsync(request.VoucherCode)
            ?? throw new PromotionDomainException($"Voucher {request.VoucherCode} not found");

        // Verify ownership
        if (voucher.OwnerId != request.UserId)
            throw new PromotionDomainException("You do not own this voucher");

        // Validate voucher can be redeemed
        if (!voucher.IsValidForRedemption())
        {
            if (voucher.IsExpired)
                throw new VoucherExpiredException(voucher.Id, voucher.Code);
            throw new PromotionDomainException("Voucher cannot be redeemed");
        }

        // Get campaign for escrow info
        var campaign = await _campaignRepository.GetByIdAsync(voucher.CampaignId)
            ?? throw new PromotionDomainException("Campaign not found");

        // Calculate amounts
        var orderAmount = request.OrderAmount;
        var voucherValue = voucher.RemainingValue;
        var amountUsed = Math.Min(orderAmount, voucherValue);
        var amountRefunded = voucherValue - amountUsed; // Surplus goes back to merchant

        _logger.LogInformation("Redeeming voucher {VoucherCode}: OrderAmount={OrderAmount}, VoucherValue={VoucherValue}, AmountUsed={AmountUsed}, Refund={Refund}",
            request.VoucherCode, orderAmount, voucherValue, amountUsed, amountRefunded);

        // Execute escrow for amount used
        if (campaign.EscrowHoldId.HasValue && campaign.EscrowWalletId.HasValue)
        {
            var executionRef = $"REDEEM:{voucher.Code}:{request.OrderId}";

            await _walletService.ExecuteHoldAsync(
                campaign.EscrowWalletId.Value,
                campaign.EscrowHoldId.Value,
                amountUsed,
                executionRef,
                cancellationToken);

            // Release surplus back to merchant if any
            if (amountRefunded > 0)
            {
                await _walletService.ReleaseHoldAsync(
                    campaign.EscrowWalletId.Value,
                    campaign.EscrowHoldId.Value,
                    amountRefunded,
                    cancellationToken);
            }
        }

        // Update voucher
        voucher.Redeem(voucherValue); // Redeem full value

        // Create redemption record
        var redemption = new Redemption(
            voucher.Id,
            voucher.CampaignId,
            request.UserId,
            request.OrderId,
            amountUsed,
            amountRefunded,
            $"REDEEM:{voucher.Code}");

        _redemptionRepository.Add(redemption);
        _campaignRepository.Update(campaign);
        await _campaignRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);

        _logger.LogInformation("Voucher {VoucherCode} redeemed, AmountUsed={AmountUsed}",
            voucher.Code, amountUsed);

        return new RedemptionDto(
            redemption.Id,
            redemption.VoucherId,
            redemption.CampaignId,
            redemption.UserId,
            redemption.OrderId,
            redemption.AmountUsed,
            redemption.AmountRefunded,
            redemption.RedeemedAt);
    }
}
