using Application.Abstractions.Data;
using Domain.Entities.Stocks;
using Microsoft.EntityFrameworkCore;
namespace Infrastructure.StockData;
///
/// SeibroDerivSyncService 의 DB 반영 로직 분리 (테스트 가능) — HTTP 없이 파서 출력(fixture)으로 검증한다
/// (SeibroBondImport 패턴 미러링). 일곱 엔티티(발행·마스터·기초자산·기초자산행사·상환조건·상환종목·미상환규모) upsert 담당.
/// getDerivCombiIsinInfo/getAssetXrcInfo/getRedCondiInfo 응답에는 ISIN 이 없어(요청 파라미터로만 받음) 배치가 요청 ISIN 을 스탬핑한다.
/// getDerivCombiIssuInfo/getRedIsinInfo 는 SECN_KACD·(ISSU_DT|RED_DT) 요청값을, getBassetUnredScale 는 STD_DT·STND_BASSET_CD·SECN_TPCD 를 스탬핑한다.
///
public static class SeibroDerivImport
{
/// getDerivCombiIssuInfo 결과를 DerivIssuance 로 upsert — UQ (Isin). SECN_KACD·ISSU_DT 요청값 스탬핑. rows 는 발행일×종류 스윕 per 콜 결과(다수 종목).
public static async Task<(int Inserted, int Updated)> UpsertDerivIssuancesAsync(IAppDbContext db, string secnKacd, DateOnly issuDt, IReadOnlyList rows, CancellationToken ct)
{
if (rows.Count == 0)
{
return (0, 0);
}
var isins = rows.Select(c => c.Isin).Distinct().ToList();
var existing = await db.DerivIssuance.Where(c => isins.Contains(c.Isin)).ToListAsync(ct);
var byKey = existing.ToDictionary(c => c.Isin);
var seen = new HashSet();
var inserted = 0;
var updated = 0;
foreach (var row in rows)
{
if (!seen.Add(row.Isin))
{
continue;
}
if (byKey.TryGetValue(row.Isin, out var entity))
{
var was = entity.UpdatedAt;
entity.Update(row.KorSecnNm, row.IssucoCustno, secnKacd, issuDt);
if (entity.UpdatedAt != was)
{
updated++;
}
}
else
{
var created = DerivIssuance.Create(row.Isin, row.KorSecnNm, row.IssucoCustno, secnKacd, issuDt);
await db.DerivIssuance.AddAsync(created, ct);
byKey[row.Isin] = created;
inserted++;
}
}
await db.SaveChangesAsync(ct);
return (inserted, updated);
}
/// getDerivCombiIsinInfo 결과를 DerivMaster 로 upsert — PK Isin(요청값 스탬핑). rolling per-ISIN 1콜 결과(첫 행만). 빈결과면 no-op.
public static async Task UpsertDerivMasterAsync(IAppDbContext db, string isin, IReadOnlyList rows, CancellationToken ct)
{
if (rows.Count == 0)
{
return false;
}
var fields = rows[0];
var entity = await db.DerivMaster.FirstOrDefaultAsync(c => c.Isin == isin, ct);
if (entity is null)
{
await db.DerivMaster.AddAsync(DerivMaster.Create(isin, fields), ct);
}
else
{
entity.Update(fields);
}
await db.SaveChangesAsync(ct);
return true;
}
/// getAssetInfo 결과를 DerivAsset 으로 upsert — UQ (Isin, AssetSeq). ISIN 요청값 스탬핑. rolling per-ISIN 1콜(다수 기초자산).
public static async Task<(int Inserted, int Updated)> UpsertDerivAssetsAsync(IAppDbContext db, string isin, IReadOnlyList rows, CancellationToken ct)
{
if (rows.Count == 0)
{
return (0, 0);
}
var existing = await db.DerivAsset.Where(c => c.Isin == isin).ToListAsync(ct);
var byKey = existing.ToDictionary(c => c.AssetSeq);
var seen = new HashSet();
var inserted = 0;
var updated = 0;
foreach (var row in rows)
{
if (!seen.Add(row.AssetSeq))
{
continue;
}
if (byKey.TryGetValue(row.AssetSeq, out var entity))
{
var was = entity.UpdatedAt;
entity.Update(row.Fields);
if (entity.UpdatedAt != was)
{
updated++;
}
}
else
{
var created = DerivAsset.Create(isin, row.AssetSeq, row.Fields);
await db.DerivAsset.AddAsync(created, ct);
byKey[row.AssetSeq] = created;
inserted++;
}
}
await db.SaveChangesAsync(ct);
return (inserted, updated);
}
/// getAssetXrcInfo 결과를 DerivAssetExercise 로 upsert — UQ (Isin, AssetSeq, XrcStdSeq). ISIN 요청값 스탬핑. rolling per-ISIN 1콜(다수 행).
public static async Task<(int Inserted, int Updated)> UpsertDerivAssetExercisesAsync(IAppDbContext db, string isin, IReadOnlyList rows, CancellationToken ct)
{
if (rows.Count == 0)
{
return (0, 0);
}
var existing = await db.DerivAssetExercise.Where(c => c.Isin == isin).ToListAsync(ct);
var byKey = existing.ToDictionary(c => (c.AssetSeq, c.XrcStdSeq));
var seen = new HashSet<(int, int)>();
var inserted = 0;
var updated = 0;
foreach (var row in rows)
{
var key = (row.AssetSeq, row.XrcStdSeq);
if (!seen.Add(key))
{
continue;
}
if (byKey.TryGetValue(key, out var entity))
{
var was = entity.UpdatedAt;
entity.Update(row.XrcStdRatio, row.XrcPrice);
if (entity.UpdatedAt != was)
{
updated++;
}
}
else
{
var created = DerivAssetExercise.Create(isin, row.AssetSeq, row.XrcStdSeq, row.XrcStdRatio, row.XrcPrice);
await db.DerivAssetExercise.AddAsync(created, ct);
byKey[key] = created;
inserted++;
}
}
await db.SaveChangesAsync(ct);
return (inserted, updated);
}
/// getRedCondiInfo 결과를 DerivRedemptionCondition 으로 upsert — UQ (Isin, RedCondiTpcd, ValatNtimesSeq). ISIN 요청값 스탬핑. rolling per-ISIN 1콜(다수 조건).
public static async Task<(int Inserted, int Updated)> UpsertDerivRedemptionConditionsAsync(IAppDbContext db, string isin, IReadOnlyList rows, CancellationToken ct)
{
if (rows.Count == 0)
{
return (0, 0);
}
var existing = await db.DerivRedemptionCondition.Where(c => c.Isin == isin).ToListAsync(ct);
var byKey = existing.ToDictionary(c => (c.RedCondiTpcd, c.ValatNtimesSeq));
var seen = new HashSet<(string, int)>();
var inserted = 0;
var updated = 0;
foreach (var row in rows)
{
var key = (row.RedCondiTpcd, row.ValatNtimesSeq);
if (!seen.Add(key))
{
continue;
}
if (byKey.TryGetValue(key, out var entity))
{
var was = entity.UpdatedAt;
entity.Update(row.MidValatPayDt, row.MidValatBeginDt, row.MidValatExpryDt, row.RedCondiContent, row.RedFormulaContent);
if (entity.UpdatedAt != was)
{
updated++;
}
}
else
{
var created = DerivRedemptionCondition.Create(isin, row.RedCondiTpcd, row.ValatNtimesSeq, row.MidValatPayDt, row.MidValatBeginDt, row.MidValatExpryDt, row.RedCondiContent, row.RedFormulaContent);
await db.DerivRedemptionCondition.AddAsync(created, ct);
byKey[key] = created;
inserted++;
}
}
await db.SaveChangesAsync(ct);
return (inserted, updated);
}
///
/// getRedIsinInfo 결과를 DerivRedemption 으로 window(상환일 begin~end)×종류 delete+insert 반영.
/// 날짜 스윕(RED_DT)이라 창 재구성으로 삭제분 반영. RED_DT·SECN_KACD 요청값 스탬핑. 같은 창·종류 내 (Isin, RedDt, DerisecExerTpcd) 중복 방어.
///
public static async Task<(int Inserted, int Deleted)> ReplaceRedemptionWindowAsync(IAppDbContext db, string secnKacd, DateOnly begin, DateOnly end, IReadOnlyList rows, CancellationToken ct)
{
var deleted = await db.DerivRedemption.Where(c => c.SecnKacd == secnKacd && c.RedDt >= begin && c.RedDt <= end).ExecuteDeleteAsync(ct);
var seen = new HashSet<(string, DateOnly, string)>();
var inserted = 0;
foreach (var row in rows)
{
// 날짜 스윕은 begin=end 하루라 RedDt 는 begin 으로 스탬핑
var key = (row.Isin, begin, row.DerisecExerTpcd);
if (!seen.Add(key))
{
continue;
}
var created = DerivRedemption.Create(row.Isin, row.DerisecExerTpcd, row.RedQty, begin, secnKacd);
await db.DerivRedemption.AddAsync(created, ct);
inserted++;
}
await db.SaveChangesAsync(ct);
return (inserted, deleted);
}
/// getBassetUnredScale 결과를 ElsUnredeemedScale 로 upsert — UQ (StdDt, StndBassetCd, SecnTpcd). 요청값 3종 스탬핑. 종목당 1행(첫 행만). 빈결과면 no-op.
public static async Task UpsertUnredeemedScaleAsync(IAppDbContext db, DateOnly stdDt, string stndBassetCd, string secnTpcd, IReadOnlyList rows, CancellationToken ct)
{
if (rows.Count == 0)
{
return false;
}
var row = rows[0];
var entity = await db.ElsUnredeemedScale.FirstOrDefaultAsync(c => c.StdDt == stdDt && c.StndBassetCd == stndBassetCd && c.SecnTpcd == secnTpcd, ct);
if (entity is null)
{
await db.ElsUnredeemedScale.AddAsync(ElsUnredeemedScale.Create(stdDt, stndBassetCd, secnTpcd, row.BassetContent, row.UnredSecncnt, row.UnredRema), ct);
}
else
{
entity.Update(row.BassetContent, row.UnredSecncnt, row.UnredRema);
}
await db.SaveChangesAsync(ct);
return true;
}
}