using AdsServingService.Domain.AggregatesModel.AuctionAggregate;
using AdsServingService.API.Application.Events;
using AdsServingService.API.Application.Services;
using AdsServingService.Infrastructure;
using System.Text.Json;
using MediatR;

namespace AdsServingService.API.Application.Queries;

/// <summary>
/// EN: Handler for ServeAdQuery - performs real-time bidding auction.
/// VI: Handler cho ServeAdQuery - thực hiện đấu giá thời gian thực.
/// </summary>
public class ServeAdQueryHandler : IRequestHandler<ServeAdQuery, ServedAdDto?>
{
    private readonly AdsServingServiceContext _context;
    private readonly IEligibleAdsProvider _eligibleAdsProvider;
    private readonly IAuctionScoringService _auctionScoringService;
    private readonly IAdServingEventPublisher _eventPublisher;
    private readonly ILogger<ServeAdQueryHandler> _logger;

    public ServeAdQueryHandler(
        AdsServingServiceContext context,
        IEligibleAdsProvider eligibleAdsProvider,
        IAuctionScoringService auctionScoringService,
        IAdServingEventPublisher eventPublisher,
        ILogger<ServeAdQueryHandler> logger)
    {
        _context = context ?? throw new ArgumentNullException(nameof(context));
        _eligibleAdsProvider = eligibleAdsProvider ?? throw new ArgumentNullException(nameof(eligibleAdsProvider));
        _auctionScoringService = auctionScoringService ?? throw new ArgumentNullException(nameof(auctionScoringService));
        _eventPublisher = eventPublisher ?? throw new ArgumentNullException(nameof(eventPublisher));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public async Task<ServedAdDto?> Handle(ServeAdQuery request, CancellationToken cancellationToken)
    {
        var startTime = DateTime.UtcNow;

        try
        {
            var eligibleAds = await _eligibleAdsProvider.GetEligibleAdsAsync(
                request.UserId,
                request.PlacementType,
                request.UserContext,
                cancellationToken);

            if (!eligibleAds.Any())
            {
                _logger.LogWarning("No eligible ads for placement {PlacementType}", request.PlacementType);
                return null;
            }

            // Step 2 - Create auction
            var auction = new Auction(request.UserId, request.PlacementType);

            // Step 3 - Add bids for each eligible ad
            foreach (var ad in eligibleAds)
            {
                var signals = _auctionScoringService.Score(
                    ad,
                    request.PlacementType,
                    request.UserContext);

                auction.AddBid(
                    ad.AdId,
                    ad.CampaignId,
                    ad.BidAmount,
                    signals.PredictedCtr,
                    signals.QualityScore);
            }

            // Step 4 - Run auction
            auction.RunAuction();

            if (auction.Result == null)
            {
                _logger.LogWarning("Auction completed but no winner for user {UserId}", request.UserId);
                return null;
            }

            // Step 5 - Get winning ad details
            var winningAd = eligibleAds.First(a => a.AdId == auction.Result.WinningAdId);

            _context.Auctions.Add(auction);
            _context.Entry(auction).Property("_bidsJson").CurrentValue = JsonSerializer.Serialize(
                auction.Bids.Select(bid => new
                {
                    bid.AdId,
                    bid.CampaignId,
                    bid.BidAmount,
                    bid.PredictedCTR,
                    bid.QualityScore,
                    bid.eCPM,
                }));
            await _context.SaveEntitiesAsync(cancellationToken);

            await _eventPublisher.PublishAdServedAsync(
                new AdServedEvent
                {
                    AuctionId = auction.Id,
                    AdId = winningAd.AdId,
                    CampaignId = winningAd.CampaignId,
                    UserId = request.UserId,
                    PlacementType = request.PlacementType,
                    FinalPrice = auction.Result.FinalPrice,
                    WinningEcpm = auction.Result.WinningeCPM,
                    ServedAt = DateTime.UtcNow,
                },
                cancellationToken);

            var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds;
            _logger.LogInformation(
                "Ad served in {Elapsed}ms - AdId: {AdId}, eCPM: {eCPM}, Price: {Price}",
                elapsed, winningAd.AdId, auction.Result.WinningeCPM, auction.Result.FinalPrice);

            return new ServedAdDto
            {
                AdId = winningAd.AdId,
                CampaignId = winningAd.CampaignId,
                AdFormat = winningAd.Format,
                Headline = winningAd.Headline,
                PrimaryText = winningAd.PrimaryText,
                CallToAction = winningAd.CallToAction,
                CreativeUrl = winningAd.CreativeUrl,
                DestinationUrl = winningAd.DestinationUrl,
                FinalPrice = auction.Result.FinalPrice,
                ServedAt = DateTime.UtcNow
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error serving ad for user {UserId}", request.UserId);
            return null;
        }
    }
}
