using Application.Abstractions.Data;
using Application.Abstractions.Messaging;
using Domain.Entities.Stocks.ValueObject;
using Microsoft.EntityFrameworkCore;
namespace Application.Features.Api.Stocks.GetWorldIndices;
///
/// 세계 주요국 증시지수 병합 — 국내(코스피)는 IndexDailyPrice 최신 거래일 실데이터, 해외는 WorldIndexSnapshot(나라당 1개).
/// 국가 코드(ISO2)로 프론트가 세계지도 핀 좌표를 매핑한다. 해외 스냅샷에 KR 이 섞여도 중복 방지로 제외.
///
internal sealed class Handler(IAppDbContext db) : IQueryHandler
{
public async Task Handle(Query request, CancellationToken ct)
{
var rows = new List();
// 국내(코스피) — IndexDailyPrice 최신 거래일. 한국 핀은 KRX 실데이터 사용.
var latestKr = await db.IndexDailyPrice.AsNoTracking().OrderByDescending(c => c.TradeDate).Select(c => (DateOnly?)c.TradeDate).FirstOrDefaultAsync(ct);
if (latestKr is not null)
{
var kospi = await db.IndexDailyPrice.AsNoTracking()
.Where(c => c.TradeDate == latestKr.Value && c.Series == MarketIndexSeries.KOSPI && c.IndexName == "코스피")
.Select(c => new
{
c.Close,
c.ChangeVal,
c.FlucRateBp,
c.TradeDate
})
.FirstOrDefaultAsync(ct);
if (kospi is not null)
{
rows.Add(new Response.Row
{
CountryCode = "KR",
Name = "코스피",
ExchangeName = "KRX",
Close = kospi.Close,
ChangeVal = kospi.ChangeVal,
FlucRateBp = kospi.FlucRateBp,
TradeDate = kospi.TradeDate
});
}
}
// 해외 — WorldIndexSnapshot. KR 은 위에서 병합했으므로 제외. 한 국가에 지수가 여러 개면(예: US ^gspc/^ixic)
// 지도는 나라당 1핀이므로 심볼 오름차순 첫 행만 남긴다(US → ^gspc=S&P500). 요약 카드는 별도 엔드포인트에서 심볼로 조회.
var foreignRaw = await db.WorldIndexSnapshot.AsNoTracking()
.Where(c => c.CountryCode != "KR")
.OrderBy(c => c.CountryCode).ThenBy(c => c.Symbol)
.Select(c => new
{
c.CountryCode,
c.Name,
c.ExchangeName,
c.Close,
c.ChangeVal,
c.FlucRateBp,
c.TradeDate
})
.ToListAsync(ct);
var seenCountry = new HashSet();
foreach (var c in foreignRaw)
{
if (!seenCountry.Add(c.CountryCode))
{
continue;
}
rows.Add(new Response.Row
{
CountryCode = c.CountryCode,
Name = c.Name,
ExchangeName = c.ExchangeName,
Close = c.Close,
ChangeVal = c.ChangeVal,
FlucRateBp = c.FlucRateBp,
TradeDate = c.TradeDate
});
}
return new Response
{
List = rows
};
}
}