using AdsBillingService.Domain.AggregatesModel.ChargeAggregate;
using AdsBillingService.Infrastructure;
using AdsBillingService.Domain.Exceptions;
using Microsoft.EntityFrameworkCore;
using MediatR;

namespace AdsBillingService.API.Application.Commands;

/// <summary>
/// EN: Handler for ChargeAdvertiserCommand - processes ad charges and debits from wallet.
/// VI: Handler cho ChargeAdvertiserCommand - xử lý charge quảng cáo và trừ tiền từ ví.
/// </summary>
public class ChargeAdvertiserCommandHandler : IRequestHandler<ChargeAdvertiserCommand, bool>
{
    private readonly AdsBillingServiceContext _context;
    private readonly ILogger<ChargeAdvertiserCommandHandler> _logger;

    public ChargeAdvertiserCommandHandler(
        AdsBillingServiceContext context,
        ILogger<ChargeAdvertiserCommandHandler> logger)
    {
        _context = context ?? throw new ArgumentNullException(nameof(context));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public async Task<bool> Handle(ChargeAdvertiserCommand request, CancellationToken cancellationToken)
    {
        var chargeType = request.ChargeType.ToLower() == "impression" 
            ? ChargeType.Impression 
            : ChargeType.Click;

        var charge = new AdCharge(
            request.AdvertiserId,
            request.CampaignId,
            request.AdId,
            chargeType,
            request.Amount
        );

        var billingAccount = await _context.BillingAccounts
            .FirstOrDefaultAsync(account => account.AdvertiserId == request.AdvertiserId, cancellationToken);

        if (billingAccount == null)
        {
            _logger.LogWarning(
                "No billing account found for advertiser {AdvertiserId}",
                request.AdvertiserId);
            return false;
        }

        try
        {
            billingAccount.ApplyCharge(request.Amount);
            charge.MarkAsProcessed();
        }
        catch (AdsBillingDomainException ex)
        {
            _logger.LogWarning(
                ex,
                "Failed to apply charge for advertiser {AdvertiserId}",
                request.AdvertiserId);
            return false;
        }

        _context.AdCharges.Add(charge);
        await _context.SaveEntitiesAsync(cancellationToken);

        _logger.LogInformation(
            "Charged advertiser {AdvertiserId} amount {Amount} for {ChargeType}",
            request.AdvertiserId, request.Amount, chargeType);

        return true;
    }
}
