namespace Application.Helpers;
///
/// KRX 일별 수집 배치의 공용 백필 오케스트레이션 (순수 로직, DB 비의존 — 테스트 가능).
/// endDate 에서 startDate 방향(과거)으로 영업일을 훑으며, 아직 적재되지 않은 날만 수집한다:
/// • 주말/휴장일(holidays) 은 건너뛴다.
/// • existsForDate(day) == true (이미 적재됨) 이면 건너뛴다 → resumable.
/// • 그 외에는 fetchAndUpsertForDate(day) 를 호출하고, quota 보호를 위해 호출 사이에 delayMs 만큼 쉰다.
/// • fetch 횟수가 maxPerRun 에 도달하면 즉시 멈춘다 → 하루 배치가 quota 상한 안에서 조금씩 채운다.
/// 최신일부터 채우므로(recent-first) 여러 번의 일일 실행에 걸쳐 3년치를 메우고 이후엔 최신만 유지한다.
/// 반환값은 이번 실행에서 실제 fetch 한 날 수.
///
public static class KrxBackfill
{
/// 해당 날짜가 이미 적재되어 있는지 (true = skip)
/// 해당 날짜를 수집·upsert (부작용 담당)
/// 백필 하한(포함) — 보통 today.AddYears(-BackfillYears)
/// 백필 상한(포함) — 보통 직전 영업일. 여기서부터 과거로 훑는다
/// 휴장일 집합 (주말은 자동 제외)
/// 이번 실행 최대 fetch 수 (quota 상한). 0 이하면 fetch 안 함
/// fetch 호출 사이 지연(ms). 0 이하면 지연 없음
public static async Task RunAsync(
Func> existsForDate,
Func fetchAndUpsertForDate,
DateOnly startDate,
DateOnly endDate,
IReadOnlySet holidays,
int maxPerRun,
int delayMs,
CancellationToken ct)
{
if (maxPerRun <= 0)
{
return 0;
}
var fetched = 0;
for (var day = endDate; day >= startDate; day = day.AddDays(-1))
{
ct.ThrowIfCancellationRequested();
if (day.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday || holidays.Contains(day))
{
continue;
}
if (await existsForDate(day, ct))
{
continue;
}
if (fetched > 0 && delayMs > 0)
{
await Task.Delay(delayMs, ct);
}
await fetchAndUpsertForDate(day, ct);
fetched++;
if (fetched >= maxPerRun)
{
break;
}
}
return fetched;
}
}