/**
 * Upbit 캔들 REST 요청 중복 제거(deduplication) + 단기 캐시
 *
 * 멀티차트 모드에서 동일한 종목/타임프레임을 여러 슬롯이 동시에 요청하면
 * 단 1개의 HTTP 요청만 실행하고 나머지는 같은 Promise를 공유한다.
 * 데이터는 CACHE_TTL(30초) 동안 메모리에 보관해 재요청을 방지한다.
 *
 * ┌ 슬롯0 ─── fetchUpbitCandlesCached('KRW-BTC', '1D', 300) ───┐
 * ├ 슬롯1 ─── fetchUpbitCandlesCached('KRW-BTC', '1D', 300) ───┼─▶ fetchUpbitCandles 1회만 실행
 * └ 슬롯2 ─── fetchUpbitCandlesCached('KRW-BTC', '1D', 300) ───┘
 */
import type { OHLCVBar, Timeframe } from '../types';
import { fetchUpbitCandles } from './upbitApi';

// ─── 과거(이전 시점) 데이터 전용 캐시 ─────────────────────────────────────────
// 역사 캔들은 변하지 않으므로 일반 30초보다 긴 TTL 을 사용한다.
const HISTORY_CACHE_TTL = 5 * 60_000;  // 5분

const historyCache   = new Map<string, { data: OHLCVBar[]; fetchedAt: number }>();
const historyPending = new Map<string, Promise<OHLCVBar[]>>();

function historyKey(market: string, timeframe: Timeframe, count: number, before: string) {
  return `${market.toUpperCase()}:${timeframe}:${count}:${before}`;
}

/** 캐시 유효 기간 (ms) */
const CACHE_TTL = 30_000;

interface CacheEntry {
  data:      OHLCVBar[];
  fetchedAt: number;
}

// 완료된 요청 캐시 (market:timeframe:count → entry)
const dataCache = new Map<string, CacheEntry>();

// 진행 중인 요청 (중복 방지용, market:timeframe:count → Promise)
const pendingMap = new Map<string, Promise<OHLCVBar[]>>();

function cacheKey(market: string, timeframe: Timeframe, count: number): string {
  return `${market.toUpperCase()}:${timeframe}:${count}`;
}

/**
 * 캐시·in-flight 중복 제거를 적용한 캔들 요청.
 * - 동일 인자로 동시 호출 시: 첫 번째 요청 Promise 를 공유한다.
 * - CACHE_TTL 이내 재호출 시: 캐시된 데이터를 즉시 반환한다.
 * - invalidate=true 로 호출 시: 캐시를 무효화하고 새 요청을 실행한다.
 */
export async function fetchUpbitCandlesCached(
  market:     string,
  timeframe:  Timeframe,
  count:      number,
  invalidate  = false,
): Promise<OHLCVBar[]> {
  const key = cacheKey(market, timeframe, count);

  // 캐시 무효화 요청이면 기존 항목 삭제
  if (invalidate) {
    dataCache.delete(key);
    pendingMap.delete(key);
  }

  // 유효한 캐시가 있으면 즉시 반환
  const cached = dataCache.get(key);
  if (cached && Date.now() - cached.fetchedAt < CACHE_TTL) {
    return cached.data;
  }

  // 이미 진행 중인 요청이 있으면 공유
  const pending = pendingMap.get(key);
  if (pending) return pending;

  // 새 요청 실행
  const promise = fetchUpbitCandles(market, timeframe, count)
    .then(data => {
      dataCache.set(key, { data, fetchedAt: Date.now() });
      pendingMap.delete(key);
      return data;
    })
    .catch(err => {
      // 실패 시 pending 제거 → 다음 호출이 재시도할 수 있도록
      pendingMap.delete(key);
      throw err;
    });

  pendingMap.set(key, promise);
  return promise;
}

/** 특정 종목의 캐시 항목을 모두 삭제 (종목 변경 시 호출) */
export function invalidateMarketCache(market: string): void {
  const prefix = market.toUpperCase() + ':';
  for (const key of [...dataCache.keys(), ...pendingMap.keys()]) {
    if (key.startsWith(prefix)) {
      dataCache.delete(key);
      pendingMap.delete(key);
    }
  }
  // 역사 캐시도 함께 삭제
  for (const key of [...historyCache.keys(), ...historyPending.keys()]) {
    if (key.startsWith(prefix)) {
      historyCache.delete(key);
      historyPending.delete(key);
    }
  }
}

/**
 * 특정 시각 이전 캔들 페치 (캐시·in-flight 중복 제거 적용).
 *
 * @param before - ISO 8601 UTC exclusive (예: '2024-01-01T00:00:00')
 *   candleEndpoint 에서 'Z' 를 붙여 요청하므로 'Z' 없이 전달한다.
 */
export async function fetchUpbitCandlesBeforeCached(
  market:    string,
  timeframe: Timeframe,
  count:     number,
  before:    string,
): Promise<OHLCVBar[]> {
  const key = historyKey(market, timeframe, count, before);

  const cached = historyCache.get(key);
  if (cached && Date.now() - cached.fetchedAt < HISTORY_CACHE_TTL) {
    return cached.data;
  }

  const pending = historyPending.get(key);
  if (pending) return pending;

  const promise = fetchUpbitCandles(market, timeframe, count, before)
    .then(data => {
      historyCache.set(key, { data, fetchedAt: Date.now() });
      historyPending.delete(key);
      return data;
    })
    .catch(err => {
      historyPending.delete(key);
      throw err;
    });

  historyPending.set(key, promise);
  return promise;
}
