using Application.Abstractions.Data;
using Application.Abstractions.Messaging;
using Microsoft.EntityFrameworkCore;
namespace Application.Features.Api.Ranking.GetRising;
///
/// 최근 7일 vs 이전 7일 후원 증가율 기준 급상승 크리에이터.
/// 집계는 실시간 Donation 테이블로 계산 (최근성이 중요하므로 스냅샷 아님).
///
internal sealed class Handler(IAppDbContext db) : IQueryHandler
{
public async Task Handle(Query request, CancellationToken ct)
{
var limit = request.Limit is < 1 or > 100 ? 20 : request.Limit;
var now = DateTime.UtcNow;
var recent7End = now;
var recent7Start = now.AddDays(-7);
var prev7Start = now.AddDays(-14);
// 최근 7일 집계
var recent = await db.Donation.AsNoTracking()
.Where(d => d.CreatedAt >= recent7Start && d.CreatedAt < recent7End && !d.IsTest)
.GroupBy(d => d.ReceiverMemberID)
.Select(g => new
{
MemberID = g.Key,
Amount = g.Sum(d => (long)d.NetAmount),
DonorCount = g.Select(d => d.SponsorMemberID).Distinct().Count()
})
.ToListAsync(ct);
if (recent.Count == 0)
{
return new Response([], now);
}
// 이전 7일 집계
var memberIds = recent.Select(r => r.MemberID).ToList();
var previous = await db.Donation.AsNoTracking()
.Where(d => d.CreatedAt >= prev7Start && d.CreatedAt < recent7Start && !d.IsTest && memberIds.Contains(d.ReceiverMemberID))
.GroupBy(d => d.ReceiverMemberID)
.Select(g => new { MemberID = g.Key, Amount = g.Sum(d => (long)d.NetAmount) })
.ToDictionaryAsync(g => g.MemberID, g => g.Amount, ct);
var rising = recent.Select(r => {
var prevAmount = previous.GetValueOrDefault(r.MemberID);
double rate;
if (prevAmount <= 0)
{
rate = r.Amount > 0 ? 1000.0 : 0.0; // 신규 유입 — 1000% 취급
}
else
{
rate = ((double)(r.Amount - prevAmount) / prevAmount) * 100.0;
}
return new { r.MemberID, r.Amount, r.DonorCount, PreviousAmount = prevAmount, Rate = rate };
})
.Where(r => r.Rate > 0)
.OrderByDescending(r => r.Rate).ThenByDescending(r => r.Amount)
.Take(limit)
.ToList();
var memberInfo = await db.Member.AsNoTracking()
.Where(m => rising.Select(r => r.MemberID).Contains(m.ID))
.Select(m => new { m.ID, m.Name, m.Thumb })
.ToListAsync(ct);
var memberDict = memberInfo.ToDictionary(m => m.ID, m => m);
var channels = await db.Channel.AsNoTracking()
.Where(c => rising.Select(r => r.MemberID).Contains(c.MemberID))
.Select(c => new
{
c.MemberID,
c.Name,
c.ThumbnailUrl
})
.ToListAsync(ct);
var channelDict = channels.GroupBy(c => c.MemberID).ToDictionary(g => g.Key, g => g.First());
var entries = rising.Select(r => {
var m = memberDict.GetValueOrDefault(r.MemberID);
var ch = channelDict.GetValueOrDefault(r.MemberID);
// Member.Thumb 우선, 없으면 Channel.ThumbnailUrl(YouTube 채널 썸네일) 폴백
return new RisingEntry(
r.MemberID,
m?.Name ?? "",
m?.Thumb ?? ch?.ThumbnailUrl,
ch?.Name,
r.Amount,
r.PreviousAmount,
r.Rate,
r.DonorCount
);
}).ToList();
return new Response(entries, now);
}
}