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

namespace PromotionService.API.Application.Commands;

/// <summary>
/// EN: Handler for CreateCampaignCommand.
/// VI: Handler cho CreateCampaignCommand.
/// </summary>
public class CreateCampaignCommandHandler : IRequestHandler<CreateCampaignCommand, CampaignDto>
{
    private readonly ICampaignRepository _campaignRepository;
    private readonly IWalletServiceClient _walletService;
    private readonly ILogger<CreateCampaignCommandHandler> _logger;

    public CreateCampaignCommandHandler(
        ICampaignRepository campaignRepository,
        IWalletServiceClient walletService,
        ILogger<CreateCampaignCommandHandler> logger)
    {
        _campaignRepository = campaignRepository;
        _walletService = walletService;
        _logger = logger;
    }

    public async Task<CampaignDto> Handle(CreateCampaignCommand request, CancellationToken cancellationToken)
    {
        _logger.LogInformation("Creating campaign {Name} for merchant {MerchantId}",
            request.Name, request.MerchantId);

        // Parse enums
        var backingAssetType = request.BackingAssetType.ToLower() == "point"
            ? AssetType.Point
            : AssetType.Currency;

        var acquisitionType = request.AcquisitionType.ToLower() switch
        {
            "free" => AcquisitionType.Free,
            "exchangepoints" => AcquisitionType.ExchangePoints,
            "purchase" => AcquisitionType.Purchase,
            _ => throw new PromotionDomainException($"Invalid acquisition type: {request.AcquisitionType}")
        };

        // Create campaign aggregate
        var campaign = new Campaign(
            request.MerchantId,
            request.Name,
            request.Description,
            backingAssetType,
            request.BackingAssetCode,
            request.FaceValue,
            acquisitionType,
            request.AcquisitionPrice,
            request.TotalVouchers,
            request.StartDate,
            request.EndDate,
            request.VoucherValidityDays,
            request.MaxPerUser);

        // Create escrow hold in Wallet Service
        var escrowAmount = request.FaceValue * request.TotalVouchers;
        _logger.LogInformation("Creating escrow hold for {Amount} {Currency}",
            escrowAmount, request.BackingAssetCode);

        var holdResult = await _walletService.CreateHoldAsync(
            request.MerchantWalletId,
            escrowAmount,
            request.BackingAssetCode,
            "CAMPAIGN",
            campaign.Id,
            $"Campaign escrow: {request.Name}",
            cancellationToken);

        // Set escrow reference on campaign
        campaign.SetEscrowHold(holdResult.WalletId, holdResult.HoldId);

        // Generate voucher codes
        campaign.GenerateVouchers(request.TotalVouchers);

        // Persist
        _campaignRepository.Add(campaign);
        await _campaignRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);

        _logger.LogInformation("Campaign {CampaignId} created with {VoucherCount} vouchers",
            campaign.Id, request.TotalVouchers);

        return MapToDto(campaign);
    }

    private static CampaignDto MapToDto(Campaign campaign) => new(
        campaign.Id,
        campaign.MerchantId,
        campaign.Name,
        campaign.Description,
        campaign.BackingAssetTypeId == AssetType.Point.Id ? "Point" : "Currency",
        campaign.BackingAssetCode,
        campaign.FaceValue,
        campaign.AcquisitionTypeId == AcquisitionType.Free.Id ? "Free"
            : campaign.AcquisitionTypeId == AcquisitionType.ExchangePoints.Id ? "ExchangePoints"
            : "Purchase",
        campaign.AcquisitionPrice,
        campaign.TotalVouchers,
        campaign.IssuedVouchers,
        campaign.AvailableVoucherCount,
        campaign.StartDate,
        campaign.EndDate,
        campaign.VoucherValidityDays,
        campaign.StatusId == CampaignStatus.Draft.Id ? "Draft"
            : campaign.StatusId == CampaignStatus.Active.Id ? "Active"
            : campaign.StatusId == CampaignStatus.Paused.Id ? "Paused"
            : campaign.StatusId == CampaignStatus.Completed.Id ? "Completed"
            : "Cancelled",
        campaign.CreatedAt,
        campaign.UpdatedAt);
}

/// <summary>
/// EN: Handler for ActivateCampaignCommand.
/// VI: Handler cho ActivateCampaignCommand.
/// </summary>
public class ActivateCampaignCommandHandler : IRequestHandler<ActivateCampaignCommand, bool>
{
    private readonly ICampaignRepository _campaignRepository;
    private readonly ILogger<ActivateCampaignCommandHandler> _logger;

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

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

        campaign.Activate();
        _campaignRepository.Update(campaign);
        await _campaignRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);

        _logger.LogInformation("Campaign {CampaignId} activated", request.CampaignId);
        return true;
    }
}

/// <summary>
/// EN: Handler for PauseCampaignCommand.
/// VI: Handler cho PauseCampaignCommand.
/// </summary>
public class PauseCampaignCommandHandler : IRequestHandler<PauseCampaignCommand, bool>
{
    private readonly ICampaignRepository _campaignRepository;
    private readonly ILogger<PauseCampaignCommandHandler> _logger;

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

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

        campaign.Pause();
        _campaignRepository.Update(campaign);
        await _campaignRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);

        _logger.LogInformation("Campaign {CampaignId} paused", request.CampaignId);
        return true;
    }
}

/// <summary>
/// EN: Handler for CancelCampaignCommand.
/// VI: Handler cho CancelCampaignCommand.
/// </summary>
public class CancelCampaignCommandHandler : IRequestHandler<CancelCampaignCommand, bool>
{
    private readonly ICampaignRepository _campaignRepository;
    private readonly IWalletServiceClient _walletService;
    private readonly ILogger<CancelCampaignCommandHandler> _logger;

    public CancelCampaignCommandHandler(
        ICampaignRepository campaignRepository,
        IWalletServiceClient walletService,
        ILogger<CancelCampaignCommandHandler> logger)
    {
        _campaignRepository = campaignRepository;
        _walletService = walletService;
        _logger = logger;
    }

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

        // Release escrow if exists
        if (campaign.EscrowHoldId.HasValue && campaign.EscrowWalletId.HasValue)
        {
            _logger.LogInformation("Releasing escrow hold {HoldId} for campaign {CampaignId}",
                campaign.EscrowHoldId, request.CampaignId);

            await _walletService.CancelHoldAsync(
                campaign.EscrowWalletId.Value,
                campaign.EscrowHoldId.Value,
                cancellationToken);
        }

        campaign.Cancel();
        _campaignRepository.Update(campaign);
        await _campaignRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);

        _logger.LogInformation("Campaign {CampaignId} cancelled", request.CampaignId);
        return true;
    }
}
