using Application.Abstractions.Data;
using Application.Abstractions.Messaging;
using Domain.Entities.Stocks.ValueObject;
using Microsoft.EntityFrameworkCore;
namespace Application.Features.Api.Stocks.GetExchangeRates;
///
/// 환율 목록 — Yahoo Finance 수집분(MarketQuoteSnapshot, Category=Fx·GroupCode="macro")에서 통화별 최신 스냅샷을 반환 (익명).
/// koreaexim AP01 은 봇차단 WAF(TLS 리셋+쿠키챌린지)로 이 서버망에서 수집 불가 → 이미 수집 중인 Yahoo 매크로 시세로 소스 전환 (2026-07-14).
/// Yahoo 는 단일가(regularMarketPrice)만 제공하므로 DealBasRate=Close, Ttb/Tts=null. USDKRW=X 가 KRW=X 로 정규화돼 중복 저장되는
/// 케이스는 통화코드 파생 후 통화별 최신 TradeDate 로 dedup 한다. Date 파라미터는 스냅샷 특성상 무시(항상 최신)한다.
///
internal sealed class Handler(IAppDbContext db) : IQueryHandler
{
public async Task Handle(Query request, CancellationToken ct)
{
var rows = await db.MarketQuoteSnapshot.AsNoTracking()
.Where(c => c.Category == QuoteCategory.Fx && c.GroupCode == "macro")
.Select(c => new { c.Symbol, c.Name, c.Close, c.TradeDate })
.ToListAsync(ct);
if (rows.Count == 0)
{
return new Response
{
Total = 0,
TradeDate = null,
List = []
};
}
// 통화코드 파생 + 통화별 최신 TradeDate 로 dedup (USDKRW=X ↔ KRW=X 정규화 중복 방어)
var list = rows
.GroupBy(c => ToCurrencyUnit(c.Symbol))
.Select(g => g.OrderByDescending(x => x.TradeDate).First())
.OrderBy(c => ToCurrencyUnit(c.Symbol))
.Select(c => new Response.Row
{
CurrencyUnit = ToCurrencyUnit(c.Symbol),
CurrencyName = c.Name,
DealBasRate = c.Close,
Ttb = null,
Tts = null
})
.ToList();
return new Response
{
Total = list.Count,
TradeDate = rows.Max(c => c.TradeDate),
List = list
};
}
/// Yahoo FX 심볼 → 통화코드. USDKRW=X 는 Yahoo 가 KRW=X 로 정규화하므로 둘 다 USD 로 매핑.
private static string ToCurrencyUnit(string symbol)
{
var s = symbol.Trim().ToUpperInvariant();
if (s is "KRW=X" or "USDKRW=X")
{
return "USD";
}
var idx = s.IndexOf("KRW=X", StringComparison.Ordinal);
return idx > 0 ? s[..idx] : s.Replace("=X", "");
}
}