import type { CandleData, IndicatorData } from '../../services/backtestApi';

/** 알림 평가·멀티차트 WS와 동일한 `1m`~`1h` 문자열로 정규화 */
export function chartIntervalToWsTimeframe(interval: string): string {
  const wsFormats = ['1m', '3m', '5m', '10m', '15m', '30m', '1h', '4h', '1d', '1w', '1M'];
  const v = (interval || '1m').trim();
  if (wsFormats.includes(v)) return v;
  const mapping: Record<string, string> = {
    '1': '1m',
    '3': '3m',
    '5': '5m',
    '10': '10m',
    '15': '15m',
    '30': '30m',
    '60': '1h',
    '240': '4h',
    D: '1d',
    W: '1w',
    M: '1M',
  };
  return mapping[v] || '1m';
}

/** '3m','1h','1d' → 밀리초 (useMarketDataWebSocket·동일봉 판별과 동일 규칙) */
export function timeframeToIntervalMs(tf: string): number {
  const t = (tf || '1m').trim();
  const match = /^(\d+)(m|h|d|w|M)$/.exec(t);
  if (!match) return 60_000;
  const n = parseInt(match[1], 10);
  const u = match[2];
  if (u === 'm') return n * 60_000;
  if (u === 'h') return n * 60 * 60_000;
  if (u === 'd') return n * 24 * 60 * 60_000;
  if (u === 'w') return n * 7 * 24 * 60 * 60_000;
  if (u === 'M') return n * 30 * 24 * 60 * 60_000;
  return 60_000;
}

/** 차트 캔들 open 시각 → epoch ms (백엔드 WS toEpochMs와 동일한 스케일) */
export function chartCandleOpenToEpochMs(time: string | number | undefined): number {
  if (time == null || time === '') return NaN;
  if (typeof time === 'number') {
    return time > 1e12 ? time : time * 1000;
  }
  const ms = new Date(time as string).getTime();
  return Number.isFinite(ms) ? ms : NaN;
}

/** 캔들 time → Unix 초 (WS·차트 동일 봉 판별용) */
export function candleTimeToUnixSeconds(time: string | number | undefined): number {
  if (time == null || time === '') return NaN;
  if (typeof time === 'number') {
    return time > 1e12 ? Math.floor(time / 1000) : Math.floor(time);
  }
  const ms = new Date(time as string).getTime();
  return Number.isFinite(ms) ? Math.floor(ms / 1000) : NaN;
}

/**
 * 마지막 캔들과 동일한 time을 가진 indicator 행 인덱스.
 */
export function findIndicatorRowIndexForCandle(
  indicators: IndicatorData[],
  candleLastIdx: number,
  candleTime: string | number | undefined
): number {
  if (!indicators.length) return -1;
  const ct = candleTime != null && candleTime !== '' ? String(candleTime) : '';
  if (ct) {
    for (let i = indicators.length - 1; i >= 0; i--) {
      const ind = indicators[i] as any;
      const t = ind?.time ?? ind?.timestamp ?? ind?.date;
      if (t !== undefined && t !== null && String(t) === ct) return i;
    }
  }
  return Math.min(Math.max(0, candleLastIdx), indicators.length - 1);
}

/** 마지막 두 봉 간격(초). 비정상이면 최소 60초 가정 */
export function inferBarIntervalSecondsFromCandles(candles: CandleData[], fallbackSec = 60): number {
  if (candles.length < 2) return fallbackSec;
  const lastIdx = candles.length - 1;
  const b = candleTimeToUnixSeconds(candles[lastIdx]?.time);
  if (!Number.isFinite(b)) return fallbackSec;
  for (let i = lastIdx - 1; i >= 0; i--) {
    const a = candleTimeToUnixSeconds(candles[i]?.time);
    if (!Number.isFinite(a) || a === b) continue;
    return Math.max(60, Math.abs(b - a));
  }
  return fallbackSec;
}

/** WS 틱이 차트 마지막 봉보다 이전 봉이면 무시 */
export function isWsCandleStaleForChart(
  wsTimeSec: number,
  chartCandles: CandleData[],
  timeframe: string
): boolean {
  if (!chartCandles.length || !Number.isFinite(wsTimeSec)) return true;
  const lastOpenMs = chartCandleOpenToEpochMs(chartCandles[chartCandles.length - 1]?.time);
  const wsMs = wsTimeSec > 1e12 ? wsTimeSec : wsTimeSec * 1000;
  if (!Number.isFinite(lastOpenMs) || !Number.isFinite(wsMs)) return false;
  const intervalMs = timeframeToIntervalMs(timeframe);
  if (intervalMs <= 0) return false;
  return Math.floor(wsMs / intervalMs) < Math.floor(lastOpenMs / intervalMs);
}

/** WS 캔들 틱 시각 → epoch ms (백엔드 Realtime과 동일 스케일) */
export function wsTickEpochMs(ws: {
  timestamp?: number;
  time?: number | string | undefined;
}): number {
  const raw = ws.timestamp ?? ws.time;
  if (raw == null || raw === '') return NaN;
  if (typeof raw === 'number') {
    return raw > 1e12 ? raw : raw * 1000;
  }
  const ms = new Date(String(raw)).getTime();
  return Number.isFinite(ms) ? ms : NaN;
}

/** 차트 마지막 봉보다 버킷이 큰 WS 캔들만, 버킷당 최신 틱 하나, 시간순 */
export function wsCandlesAheadOfChartTail<
  T extends { timestamp?: number; time?: number | string; open: number; high: number; low: number; close: number; volume?: number }
>(wsCandles: T[], chartLastTime: string | number | undefined, timeframe: string): T[] {
  const intervalMs = timeframeToIntervalMs(timeframe);
  const chartMs = chartCandleOpenToEpochMs(chartLastTime);
  if (!Number.isFinite(chartMs) || chartMs <= 0 || !wsCandles?.length || intervalMs <= 0) return [];
  const chartBucket = Math.floor(chartMs / intervalMs);
  const byBucket = new Map<number, T>();
  for (const c of wsCandles) {
    const ms = wsTickEpochMs(c);
    if (!Number.isFinite(ms)) continue;
    const b = Math.floor(ms / intervalMs);
    if (b > chartBucket) {
      byBucket.set(b, c);
    }
  }
  return Array.from(byBucket.entries())
    .sort((a, b) => a[0] - b[0])
    .map(([, c]) => c);
}

/** REST/차트와 동일한 로컬 ISO 문자열 (time 필드 비교·표시용) */
export function formatCandleTimeLocalFromMs(openMs: number): string {
  const d = new Date(openMs);
  const p = (n: number) => String(n).padStart(2, '0');
  return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}T${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
}

/** 차트 마지막 time 타입에 맞춘 봉 시각 (문자열 REST vs 숫자 초) */
export function candleOpenTimeForChartRow(openMs: number, lastChartTime: string | number | undefined): string | number {
  if (typeof lastChartTime === 'number') {
    if (lastChartTime > 1e12) return Math.floor(openMs);
    return Math.floor(openMs / 1000);
  }
  return formatCandleTimeLocalFromMs(openMs);
}

/** WS 시각이 차트 마지막 봉과 같은 봉 버킷인지 (REST 중복 봉·추정 간격 오류 방지 — timeframe 필수) */
export function isWsCandleSameBarAsChartLast(
  wsTimeSec: number,
  chartCandles: CandleData[],
  timeframe: string
): boolean {
  if (!chartCandles.length || !Number.isFinite(wsTimeSec)) return false;
  const lastOpenMs = chartCandleOpenToEpochMs(chartCandles[chartCandles.length - 1]?.time);
  const wsMs = wsTimeSec > 1e12 ? wsTimeSec : wsTimeSec * 1000;
  if (!Number.isFinite(lastOpenMs) || !Number.isFinite(wsMs)) return false;
  const intervalMs = timeframeToIntervalMs(timeframe);
  if (intervalMs <= 0) return false;
  return Math.floor(wsMs / intervalMs) === Math.floor(lastOpenMs / intervalMs);
}
