using Application.Abstractions.Data; using Application.Abstractions.Messaging; using Microsoft.EntityFrameworkCore; using SharedKernel.Results; namespace Application.Features.Api.Member.GetLevel; internal sealed class Handler(IAppDbContext db) : IQueryHandler> { public async Task> Handle(Query request, CancellationToken ct) { var member = await db.Member.AsNoTracking() .Where(c => c.ID == request.MemberID) .Select(c => new { c.ID, c.MemberGradeID }) .FirstOrDefaultAsync(ct); if (member is null) { return Result.Failure(Error.NotFound("Member.NotFound", "회원 정보를 찾을 수 없습니다.")); } var exp = await db.MemberStats.AsNoTracking() .Where(c => c.MemberID == request.MemberID) .Select(c => (long?)c.Exp) .FirstOrDefaultAsync(ct) ?? 0; // 활성 등급을 RequiredExp 오름차순으로 로드 (소량) var grades = await db.MemberGrade.AsNoTracking() .Where(c => c.IsActive) .OrderBy(c => c.RequiredExp) .Select(c => new { c.ID, c.KorName, c.RequiredExp, c.Image, c.TextColor }) .ToListAsync(ct); // 현재 등급 = 회원 등급 스냅샷 (없으면 충족하는 최고 등급으로 추정) var current = member.MemberGradeID is int gid ? grades.FirstOrDefault(c => c.ID == gid) : grades.LastOrDefault(c => c.RequiredExp <= exp); // 다음 등급 = 현재 RequiredExp 초과 첫 등급 var currentRequired = current?.RequiredExp ?? -1; var next = grades.FirstOrDefault(c => c.RequiredExp > currentRequired); long? expToNext = next is not null ? Math.Max(0, next.RequiredExp - exp) : null; return Result.Success(new Response( exp, current?.ID, current?.KorName, current?.Image, current?.TextColor, next?.ID, next?.KorName, next?.RequiredExp, expToNext )); } }