import type { OHLCVBar, Timeframe } from '../types';

// ─── Upbit REST API ────────────────────────────────────────────────────────
// 개발: Vite proxy (/upbit-api) → https://api.upbit.com
// 프로덕션: nginx proxy (/upbit-api) → https://api.upbit.com
// 항상 동일한 상대 경로를 사용하므로 CORS 없이 동일 오리진으로 요청됨
const UPBIT_API = '/upbit-api/v1';

export const UPBIT_MARKETS = [
  'KRW-BTC', 'KRW-ETH', 'KRW-XRP', 'KRW-SOL', 'KRW-DOGE',
  'KRW-ADA', 'KRW-DOT', 'KRW-AVAX', 'KRW-LINK', 'KRW-MATIC',
  'KRW-SHIB', 'KRW-UNI', 'KRW-ATOM', 'KRW-LTC', 'KRW-ETC',
];

export function isUpbitMarket(symbol: string): boolean {
  return /^(KRW|BTC|USDT)-\w+$/.test(symbol.toUpperCase());
}

// ─── Upbit candle → OHLCVBar ───────────────────────────────────────────────
export interface UpbitCandle {
  market:                  string;
  candle_date_time_utc:    string;
  opening_price:           number;
  high_price:              number;
  low_price:               number;
  trade_price:             number;
  timestamp:               number;
  candle_acc_trade_price:  number;
  candle_acc_trade_volume: number;
  unit?:                   number;
}

export function candleToBar(c: UpbitCandle): OHLCVBar {
  const time = Math.floor(new Date(c.candle_date_time_utc + 'Z').getTime() / 1000);
  return {
    time,
    open:   c.opening_price,
    high:   c.high_price,
    low:    c.low_price,
    close:  c.trade_price,
    volume: c.candle_acc_trade_volume,
  };
}

/** Upbit 캔들 API 1회 요청 최대 개수 */
const UPBIT_MAX_PER_REQ = 200;

/**
 * 지표 워밍업 바 수 분석
 * ─────────────────────────────────────────────────────
 *  지표              | 최대 계산 기간 | 워밍업 바
 *  BB(20)            | 20             | 20
 *  EMA(20/50)        | 50             | 50
 *  RSI(14)           | 14             | 14
 *  MACD(12,26,9)     | 26 + 9         | 35
 *  DMI/ADX(14)       | 14 × 2         | 28
 *  일목균형표 Span B  | 52             | 52  ← 최대
 * ─────────────────────────────────────────────────────
 *  → 최소 52바, 안전 마진 포함 100바 워밍업 필요
 *  → 표시 200 + 워밍업 100 = 300바 = 요청 2회 (200 + 100)
 */
export const INDICATOR_WARMUP = 100;   // 안전 마진 포함 워밍업 바 수

/** OBV 누적 정확도용 초기 캔들 수 (표시 200 + 워밍업 600) */
export const OBV_DEEP_HISTORY = 800;

function candleEndpoint(
  timeframe: Timeframe,
  market:    string,
  count:     number,
  to?:       string,   // ISO 8601 (exclusive) - 이 시각 이전 캔들을 반환
): string {
  const base    = `${UPBIT_API}/candles`;
  const toParam = to ? `&to=${encodeURIComponent(to + 'Z')}` : '';
  switch (timeframe) {
    case '1m':  return `${base}/minutes/1?market=${market}&count=${count}${toParam}`;
    case '3m':  return `${base}/minutes/3?market=${market}&count=${count}${toParam}`;
    case '5m':  return `${base}/minutes/5?market=${market}&count=${count}${toParam}`;
    case '15m': return `${base}/minutes/15?market=${market}&count=${count}${toParam}`;
    case '30m': return `${base}/minutes/30?market=${market}&count=${count}${toParam}`;
    case '1h':  return `${base}/minutes/60?market=${market}&count=${count}${toParam}`;
    case '4h':  return `${base}/minutes/240?market=${market}&count=${count}${toParam}`;
    case '1D':  return `${base}/days?market=${market}&count=${count}${toParam}`;
    case '1W':  return `${base}/weeks?market=${market}&count=${count}${toParam}`;
    case '1M':  return `${base}/months?market=${market}&count=${count}${toParam}`;
    default:    return `${base}/minutes/1?market=${market}&count=${count}${toParam}`;
  }
}

/**
 * 업비트 캔들 페치 (count > 200 이면 자동 분할 요청)
 *
 * Upbit API 는 1회 최대 200개 반환.
 * count=300 이면:
 *   - 1차: 최신 200개
 *   - 2차: 그 이전 100개 (to = 1차 결과 중 가장 오래된 시각)
 * 결과는 시간 오름차순(오래된→최신)으로 반환.
 *
 * @param before - ISO 8601 UTC (exclusive) — 이 시각 **이전** 캔들을 반환.
 *   과거 데이터 추가 로드 시 현재 가장 오래된 캔들의 시각을 전달한다.
 */
export async function fetchUpbitCandles(
  market:    string,
  timeframe: Timeframe,
  count =    200,
  before?:   string,   // ISO 8601 UTC exclusive (예: '2024-01-01T00:00:00')
): Promise<OHLCVBar[]> {
  const mkt = market.toUpperCase();
  let remaining = count;
  let to: string | undefined = before;   // before 가 있으면 그 시각 이전부터 시작
  const batches: UpbitCandle[][] = [];

  while (remaining > 0) {
    const batchCount = Math.min(remaining, UPBIT_MAX_PER_REQ);
    const url = candleEndpoint(timeframe, mkt, batchCount, to);
    const res = await fetch(url, { headers: { Accept: 'application/json' } });
    if (!res.ok) throw new Error(`Upbit API 오류: ${res.status} ${res.statusText}`);
    const data: UpbitCandle[] = await res.json();
    if (data.length === 0) break;

    batches.push(data);            // 각 배치: Upbit 반환순 (최신→오래된)
    remaining -= data.length;

    if (data.length < batchCount) break;   // 서버에 데이터 더 없음

    // 다음 배치 기준: 현재 배치에서 가장 오래된 캔들 시각 (exclusive)
    to = data[data.length - 1].candle_date_time_utc;

    // requestCache 의 deduplication 으로 동시 요청이 1건으로 줄어들었으므로
    // 배치 간 딜레이는 100ms 로 유지 (2번째 배치가 동시에 날아가지 않도록)
    if (remaining > 0) await new Promise(r => setTimeout(r, 100));
  }

  // 배치 역순 합산 후 시간 오름차순 정렬 → 중복 제거
  const allCandles = batches.reverse().flat();
  const seen = new Set<string>();
  const unique = allCandles.filter(c => {
    if (seen.has(c.candle_date_time_utc)) return false;
    seen.add(c.candle_date_time_utc);
    return true;
  });
  unique.sort((a, b) => a.candle_date_time_utc.localeCompare(b.candle_date_time_utc));
  return unique.map(candleToBar);
}

// ─── Upbit WebSocket ───────────────────────────────────────────────────────
export interface UpbitTrade {
  type:            'trade';
  code:            string;
  trade_price:     number;
  trade_volume:    number;
  ask_bid:         'ASK' | 'BID';
  trade_timestamp: number;
  trade_date:      string;
  trade_time:      string;
  sequential_id:   number;
  stream_type:     'REALTIME' | 'SNAPSHOT';
}
export interface UpbitTickerSimple {
  type:             'ticker';
  code:             string;
  trade_price:      number;
  trade_volume:     number;
  opening_price:    number;
  high_price:       number;
  low_price:        number;
  acc_trade_volume: number;
  trade_timestamp:  number;
  stream_type:      string;
}
export type UpbitMessage = UpbitTrade | UpbitTickerSimple;

export interface UpbitWsOptions {
  market:       string;
  onMessage:    (msg: UpbitMessage) => void;
  onConnect:    () => void;
  onDisconnect: () => void;
  onError:      (err: string) => void;
}

// 개발: Vite proxy (/upbit-ws) → wss://api.upbit.com/websocket/v1
// 프로덕션: nginx proxy (/upbit-ws) → wss://api.upbit.com/websocket/v1
// 항상 동일 오리진 상대 경로를 사용 → CORS / Origin 차단 모두 우회
function getWsUrl(): string {
  const proto = window.location.protocol === 'https:' ? 'wss' : 'ws';
  return `${proto}://${window.location.host}/upbit-ws`;
}

const INITIAL_DELAY   = 80;     // React StrictMode 이중마운트 방지 (cleanup < 80ms면 연결 안 함)
const RECONNECT_BASE  = 2000;   // 최초 재연결 대기
const RECONNECT_MAX   = 30000;  // 최대 재연결 대기
const PING_INTERVAL   = 20000;  // 20초마다 PING (서버가 60초 내 응답 없으면 끊음)

export class UpbitWebSocketClient {
  private ws:           WebSocket | null = null;
  private opts:         UpbitWsOptions;
  private destroyed     = false;
  private retryCount    = 0;
  private reconnectTimer: ReturnType<typeof setTimeout>  | null = null;
  private pingTimer:      ReturnType<typeof setInterval> | null = null;

  constructor(opts: UpbitWsOptions) {
    this.opts = opts;
    // 80ms 지연으로 React StrictMode 이중 마운트 방지
    // (cleanup 이 80ms 안에 호출되면 connect 가 취소됨)
    this.reconnectTimer = setTimeout(() => {
      this.reconnectTimer = null;
      this.connect();
    }, INITIAL_DELAY);
  }

  private connect() {
    if (this.destroyed) return;

    try {
      const ws = new WebSocket(getWsUrl());
      this.ws = ws;
      ws.binaryType = 'arraybuffer';

      ws.onopen = () => {
        this.retryCount = 0;

        // ── 구독 메시지 ──────────────────────────────────────────────
        // format 생략 시 DEFAULT (긴 키명), SIMPLE이면 단축 키명
        ws.send(JSON.stringify([
          { ticket: `react_chart_${Date.now()}` },
          { type: 'trade', codes: [this.opts.market.toUpperCase()] },
          { format: 'DEFAULT' },
        ]));

        // ── PING keepalive ─────────────────────────────────────────
        this.clearPing();
        this.pingTimer = setInterval(() => {
          if (ws.readyState === WebSocket.OPEN) {
            ws.send('PING');
          }
        }, PING_INTERVAL);

        this.opts.onConnect();
      };

      ws.onmessage = (event) => {
        // ① 텍스트 메시지: 서버 PING → PONG 응답, 상태 메시지 → 무시
        if (typeof event.data === 'string') {
          if (event.data === 'PING') ws.send('PONG');
          return;
        }

        // ② 바이너리 메시지: 체결/시세 데이터
        let msg: UpbitMessage | null = null;
        try {
          const text = new TextDecoder('utf-8').decode(event.data as ArrayBuffer);
          if (text === 'PING') { ws.send('PONG'); return; }
          const parsed = JSON.parse(text) as UpbitMessage;
          if (!('type' in parsed)) return;
          msg = parsed;
        } catch {
          // JSON 파싱 실패 → 무시
          return;
        }
        // JSON 파싱과 onMessage 를 분리: 차트 업데이트 오류가 WS 메시지 처리를 중단시키지 않도록
        try {
          this.opts.onMessage(msg);
        } catch (err) {
          console.error('[UpbitWS] onMessage 처리 중 오류:', err);
        }
      };

      ws.onclose = (ev) => {
        this.clearPing();
        if (!this.destroyed) {
          // 정상 종료(1000/1001)가 아닐 때만 오류 표시
          if (ev.code !== 1000 && ev.code !== 1001) {
            this.opts.onError(`연결 끊김 (code ${ev.code})`);
          }
          this.opts.onDisconnect();
          this.scheduleReconnect();
        }
      };

      ws.onerror = () => {
        // onerror 직후 onclose가 반드시 발생하므로 여기서는 로그만
        // (onclose에서 재연결 처리)
        console.warn('[UpbitWS] onerror 발생 → onclose 대기');
      };

    } catch (err) {
      this.opts.onError(`WebSocket 초기화 실패: ${String(err)}`);
      this.scheduleReconnect();
    }
  }

  private clearPing() {
    if (this.pingTimer) { clearInterval(this.pingTimer); this.pingTimer = null; }
  }

  private scheduleReconnect() {
    if (this.destroyed) return;
    const delay = Math.min(RECONNECT_BASE * 2 ** this.retryCount, RECONNECT_MAX);
    this.retryCount++;
    console.info(`[UpbitWS] ${delay}ms 후 재연결 시도 (${this.retryCount}회차)`);
    this.reconnectTimer = setTimeout(() => this.connect(), delay);
  }

  destroy() {
    this.destroyed = true;
    this.clearPing();
    if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = null; }
    if (this.ws) {
      this.ws.onclose = null;
      this.ws.onerror = null;
      this.ws.close(1000, 'destroy');
      this.ws = null;
    }
  }
}

// ─── 타임프레임 초 단위 ───────────────────────────────────────────────────
export const TF_SECONDS: Record<Timeframe, number> = {
  '1m': 60, '3m': 180, '5m': 300, '15m': 900, '30m': 1800,
  '1h': 3600, '4h': 14400, '1D': 86400, '1W': 604800, '1M': 2592000,
};

export function getCandleStart(tradeTimestampMs: number, timeframe: Timeframe): number {
  const sec = Math.floor(tradeTimestampMs / 1000);
  const tfSec = TF_SECONDS[timeframe] ?? 60;
  return Math.floor(sec / tfSec) * tfSec;
}
