import { useEffect, useRef, useState, useCallback } from 'react';
import {
  UPBIT_KR_WS_URL,
  intervalToUpbitCandleType,
  parseUpbitCandleMessage,
  type UpbitCandleFrame,
} from '../utils/upbitRealtime';

export type UpbitCandleWsStatus = 'disconnected' | 'connecting' | 'connected' | 'error';

export interface UseUpbitCandleWebSocketOptions {
  market: string;
  interval: string;
  enabled: boolean;
  onCandle: (candle: UpbitCandleFrame) => void;
  onError?: (err: unknown) => void;
}

/**
 * 업비트 공개 WebSocket에 직접 연결하여 선택 종목·봉 단위 캔들 스트림 수신
 */
export function useUpbitCandleWebSocket({
  market,
  interval,
  enabled,
  onCandle,
  onError,
}: UseUpbitCandleWebSocketOptions): UpbitCandleWsStatus {
  const [status, setStatus] = useState<UpbitCandleWsStatus>('disconnected');
  const wsRef = useRef<WebSocket | null>(null);
  const reconnectTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const onCandleRef = useRef(onCandle);
  const onErrorRef = useRef(onError);
  const reconnectAttemptsRef = useRef(0);

  useEffect(() => {
    onCandleRef.current = onCandle;
    onErrorRef.current = onError;
  }, [onCandle, onError]);

  const clearReconnect = useCallback(() => {
    if (reconnectTimerRef.current) {
      clearTimeout(reconnectTimerRef.current);
      reconnectTimerRef.current = null;
    }
  }, []);

  useEffect(() => {
    if (!enabled || !market) {
      clearReconnect();
      if (wsRef.current) {
        try {
          wsRef.current.close();
        } catch {
          /* noop */
        }
        wsRef.current = null;
      }
      setStatus('disconnected');
      return;
    }

    let cancelled = false;

    const openSocket = () => {
      if (cancelled) return;

      clearReconnect();
      const prev = wsRef.current;
      if (prev && (prev.readyState === WebSocket.OPEN || prev.readyState === WebSocket.CONNECTING)) {
        try {
          prev.close();
        } catch {
          /* noop */
        }
      }
      wsRef.current = null;

      setStatus('connecting');
      const candleType = intervalToUpbitCandleType(interval);

      let ws: WebSocket;
      try {
        ws = new WebSocket(UPBIT_KR_WS_URL);
      } catch (e) {
        setStatus('error');
        onErrorRef.current?.(e);
        return;
      }

      wsRef.current = ws;

      ws.onopen = () => {
        if (cancelled) return;
        reconnectAttemptsRef.current = 0;
        setStatus('connected');
        const ticket = `ga-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
        const sub = [
          { ticket },
          { type: candleType, codes: [market], isOnlyRealtime: false },
          { format: 'DEFAULT' },
        ];
        try {
          ws.send(JSON.stringify(sub));
        } catch (e) {
          onErrorRef.current?.(e);
        }
      };

      const parseText = (text: string) => {
        try {
          const data = JSON.parse(text) as unknown;
          const frame = parseUpbitCandleMessage(data);
          if (frame) onCandleRef.current(frame);
        } catch {
          /* ignore */
        }
      };

      ws.onmessage = (ev: MessageEvent<string | ArrayBuffer | Blob>) => {
        if (typeof ev.data === 'string') parseText(ev.data);
        else if (ev.data instanceof Blob) {
          ev.data.text().then(parseText).catch(() => {});
        } else if (ev.data instanceof ArrayBuffer) {
          try {
            parseText(new TextDecoder('utf-8').decode(ev.data));
          } catch {
            /* noop */
          }
        }
      };

      ws.onerror = () => {
        if (!cancelled) setStatus('error');
      };

      ws.onclose = () => {
        wsRef.current = null;
        if (cancelled) return;
        setStatus('disconnected');
        reconnectAttemptsRef.current += 1;
        const delay = Math.min(2000 * Math.pow(2, Math.min(reconnectAttemptsRef.current, 6)), 60_000);
        reconnectTimerRef.current = setTimeout(() => {
          reconnectTimerRef.current = null;
          openSocket();
        }, delay);
      };
    };

    openSocket();

    return () => {
      cancelled = true;
      clearReconnect();
      reconnectAttemptsRef.current = 0;
      const w = wsRef.current;
      wsRef.current = null;
      if (w && (w.readyState === WebSocket.OPEN || w.readyState === WebSocket.CONNECTING)) {
        try {
          w.close();
        } catch {
          /* noop */
        }
      }
    };
  }, [enabled, market, interval, clearReconnect]);

  return status;
}
