/**
 * 업비트 마켓 목록 + 실시간 시세 훅
 *
 * 동작:
 *  1) 앱 마운트 시 /v1/market/all 로 전체 마켓 목록 + 한글명 로드
 *  2) /v1/ticker?markets=... 로 초기 시세 로드 (100개 단위 분할)
 *  3) WebSocket ticker 구독으로 실시간 시세 수신 (배치 업데이트 500ms)
 *  4) REST 폴링 30초마다 보조 갱신 (WebSocket 누락 방지)
 */

import { useState, useEffect, useRef, useCallback } from 'react';
import { setMarketNames } from '../utils/marketNameCache';

export type ChangeDir = 'RISE' | 'FALL' | 'EVEN';

export interface TickerData {
  market:          string;
  koreanName:      string;
  tradePrice:      number | null;
  changeRate:      number | null;   // 전일대비 등락률 (0.01 = +1%)
  changePrice:     number | null;   // 전일대비 등락금액
  accTradePrice24: number;          // 24h 누적 거래대금 (KRW) – 정렬용 (0 fallback)
  accTradeVolume24: number | null; // 24h 누적 거래량 (코인)
  openingPrice:    number | null;
  highPrice:       number | null;
  lowPrice:        number | null;
  change:          ChangeDir;       // RISE / FALL / EVEN
}

export interface MarketInfo {
  market:      string;
  koreanName:  string;
  englishName: string;
}

const REST_POLL_INTERVAL = 30_000; // 30초 보조 폴링
const WS_BATCH_MS        = 400;    // WebSocket 업데이트 배치 간격

// ── 내부 API 타입 ─────────────────────────────────────────────────────────

function toChangeDir(raw: UpbitTickerRaw): ChangeDir {
  if (raw.change === 'RISE' || raw.change === 'FALL' || raw.change === 'EVEN') return raw.change;
  const r = raw.signed_change_rate ?? 0;
  return r > 0 ? 'RISE' : r < 0 ? 'FALL' : 'EVEN';
}

interface UpbitMarketAll {
  market:       string;
  korean_name:  string;
  english_name: string;
}

interface UpbitTickerRaw {
  market?:             string;  // REST
  code?:               string;  // WebSocket
  trade_price:         number;
  signed_change_rate:  number;
  signed_change_price: number;
  acc_trade_price_24h: number;
  acc_trade_volume_24h?: number;
  opening_price:       number;
  high_price:          number;
  low_price:           number;
  change?:             'RISE' | 'FALL' | 'EVEN';
}

function getMarketCode(raw: UpbitTickerRaw): string {
  return (raw.code ?? raw.market ?? '') as string;
}

async function fetchAllMarkets(): Promise<MarketInfo[]> {
  const res = await fetch('/upbit-api/v1/market/all?isDetails=true');
  if (!res.ok) throw new Error(`market/all ${res.status}`);
  const data: UpbitMarketAll[] = await res.json();
  const krw = data.filter(m => m.market.startsWith('KRW-'));
  const nameMap: Record<string, string> = {};
  krw.forEach(m => { nameMap[m.market] = m.korean_name; });
  setMarketNames(nameMap);
  return krw.map(m => ({
    market:      m.market,
    koreanName:  m.korean_name,
    englishName: m.english_name,
  }));
}

async function fetchAllTickers(markets: string[]): Promise<UpbitTickerRaw[]> {
  const results: UpbitTickerRaw[] = [];
  for (let i = 0; i < markets.length; i += 100) {
    const chunk = markets.slice(i, i + 100);
    try {
      const res = await fetch(`/upbit-api/v1/ticker?markets=${chunk.join(',')}`);
      if (res.ok) results.push(...(await res.json()));
    } catch { /* 부분 실패 무시 */ }
  }
  return results;
}

function rawToTicker(raw: UpbitTickerRaw, koreanName: string): TickerData {
  return {
    market:          getMarketCode(raw),
    koreanName,
    tradePrice:      raw.trade_price         ?? null,
    changeRate:      raw.signed_change_rate  ?? null,
    changePrice:     raw.signed_change_price ?? null,
    accTradePrice24: raw.acc_trade_price_24h ?? 0,
    accTradeVolume24: raw.acc_trade_volume_24h ?? null,
    openingPrice:    raw.opening_price       ?? null,
    highPrice:       raw.high_price          ?? null,
    lowPrice:        raw.low_price           ?? null,
    change:          toChangeDir(raw),
  };
}

// ── WebSocket 연결 관리 ───────────────────────────────────────────────────
function createTickerWs(
  markets: string[],
  onBatch: (rawList: UpbitTickerRaw[]) => void,
): () => void {
  const proto = window.location.protocol === 'https:' ? 'wss' : 'ws';
  let ws: WebSocket | null = null;
  let destroyed = false;
  let reconnectTimer: ReturnType<typeof setTimeout> | null = null;

  // 배치 버퍼: 너무 빈번한 state 업데이트 방지 (500ms 단위로 모아서 전달)
  const batch: Map<string, UpbitTickerRaw> = new Map();
  let batchTimer: ReturnType<typeof setTimeout> | null = null;

  function flushBatch() {
    batchTimer = null;
    if (batch.size === 0) return;
    onBatch(Array.from(batch.values()));
    batch.clear();
  }

  function connect() {
    if (destroyed) return;
    ws = new WebSocket(`${proto}://${window.location.host}/upbit-ws`);
    ws.binaryType = 'arraybuffer';

    ws.onopen = () => {
      ws?.send(JSON.stringify([
        { ticket: 'market-panel-ticker' },
        { type: 'ticker', codes: markets },
      ]));
    };

    ws.onmessage = (e: MessageEvent) => {
      try {
        const decoder = new TextDecoder('utf-8');
        const text = typeof e.data === 'string'
          ? e.data
          : decoder.decode(e.data as ArrayBuffer);
        const msg: UpbitTickerRaw & { type?: string } = JSON.parse(text);
        if (msg.type !== 'ticker' && !msg.code) return;
        const code = getMarketCode(msg);
        if (!code) return;
        batch.set(code, msg);
        if (!batchTimer) {
          batchTimer = setTimeout(flushBatch, WS_BATCH_MS);
        }
      } catch { /* ignore */ }
    };

    ws.onerror = () => { /* ignore – onclose will handle */ };
    ws.onclose = () => {
      if (!destroyed) {
        reconnectTimer = setTimeout(connect, 5_000);
      }
    };
  }

  connect();

  return () => {
    destroyed = true;
    if (batchTimer) { clearTimeout(batchTimer); flushBatch(); }
    if (reconnectTimer) clearTimeout(reconnectTimer);
    ws?.close();
  };
}

// ── 훅 ──────────────────────────────────────────────────────────────────
export function useMarketTicker() {
  const [tickers,     setTickers]     = useState<Map<string, TickerData>>(new Map());
  const [marketInfos, setMarketInfos] = useState<MarketInfo[]>([]);
  const [loading,     setLoading]     = useState(true);
  const [usdRate,     setUsdRate]     = useState(1_300);

  const mountedRef       = useRef(true);
  const marketListRef    = useRef<string[]>([]);
  const marketInfoMapRef = useRef<Map<string, MarketInfo>>(new Map());

  const applyRawList = useCallback((rawList: UpbitTickerRaw[]) => {
    if (!mountedRef.current) return;

    setTickers(prev => {
      const next = new Map(prev);
      rawList.forEach(raw => {
        const code = getMarketCode(raw);
        if (!code) return;
        const info = marketInfoMapRef.current.get(code);
        const name = info?.koreanName ?? next.get(code)?.koreanName ?? code.replace(/^KRW-/, '');
        next.set(code, rawToTicker(raw, name));
      });
      return next;
    });

    const usdt = rawList.find(t => getMarketCode(t) === 'KRW-USDT');
    if (usdt?.trade_price) setUsdRate(usdt.trade_price);
  }, []);

  useEffect(() => {
    mountedRef.current = true;
    let pollTimer: ReturnType<typeof setInterval> | null = null;
    let destroyWs: (() => void) | null = null;

    (async () => {
      // 1) 마켓 목록
      let infos: MarketInfo[] = [];
      try { infos = await fetchAllMarkets(); }
      catch { /* 실패 시 빈 목록 */ }
      if (!mountedRef.current) return;

      infos.forEach(m => marketInfoMapRef.current.set(m.market, m));
      setMarketInfos(infos);

      const markets = infos.map(m => m.market);
      if (!markets.includes('KRW-USDT')) markets.push('KRW-USDT');
      marketListRef.current = markets;

      // 2) 초기 시세 (REST)
      try {
        const rawList = await fetchAllTickers(markets);
        if (mountedRef.current) applyRawList(rawList);
      } catch { /* ignore */ }

      if (!mountedRef.current) return;
      setLoading(false);

      // 3) WebSocket 실시간 구독
      destroyWs = createTickerWs(markets, applyRawList);

      // 4) REST 보조 폴링 (30초 – WebSocket 누락 보완)
      pollTimer = setInterval(async () => {
        if (!mountedRef.current) return;
        try {
          const rawList = await fetchAllTickers(marketListRef.current);
          applyRawList(rawList);
        } catch { /* ignore */ }
      }, REST_POLL_INTERVAL);
    })();

    return () => {
      mountedRef.current = false;
      destroyWs?.();
      if (pollTimer) clearInterval(pollTimer);
    };
  }, [applyRawList]);

  return { tickers, marketInfos, loading, usdRate };
}
