import type { KlineResolution } from '@/stores/futures'

const WS_URL = import.meta.env.VITE_WS_MARKET_URL as string

const RES_MAP: Record<KlineResolution, string> = {
  '1': '1m', '5': '5m', '15': '15m', '30': '30m',
  '60': '1h', '240': '4h', '1D': '1d', '1W': '1w', '1M': '1mon', '3M': '3mon',
}

// 每根 K 线对应的毫秒数
export const BAR_MS: Record<KlineResolution, number> = {
  '1':   60_000,
  '5':   300_000,
  '15':  900_000,
  '30':  1_800_000,
  '60':  3_600_000,
  '240': 14_400_000,
  '1D':  86_400_000,
  '1W':  604_800_000,
  '1M':  2_592_000_000,
  '3M':  7_776_000_000,
}
export const BAR_COUNT = 200

export interface WsKlineBar {
  timestamp: number
  open: number; high: number; low: number; close: number; volume: number
}

export interface WsTickerData {
  close: string; open: string; high: string; low: string
  volume: string; turnover: string
}

export interface WsMarkData {
  markPrice: string
  fundingRate: string
  nextFundingTime: number
}

export interface WsTradeItem {
  price: string; qty: string; time: number; isSell: boolean
}

export interface WsBookData {
  bids: [string, string][]; asks: [string, string][]
}

export interface MarketWsCallbacks {
  onConnected?: () => void
  onKline?: (bar: WsKlineBar) => void
  onKlineHistory?: (bars: WsKlineBar[]) => void
  onKlineHistoryMore?: (bars: WsKlineBar[]) => void
  onTicker?: (data: WsTickerData) => void
  onMark?: (data: WsMarkData) => void
  onTrade?: (item: WsTradeItem) => void
  onBook?: (data: WsBookData) => void
}

export function useMarketWs(callbacks: MarketWsCallbacks) {
  let ws: WebSocket | null = null
  let reconnectTimer: ReturnType<typeof setTimeout> | null = null
  let destroyed = false
  let pingTimer: ReturnType<typeof setInterval> | null = null
  let lastMsgTs = 0

  function startHeartbeat() {
    if (pingTimer !== null) clearInterval(pingTimer)
    lastMsgTs = Date.now()
    pingTimer = setInterval(() => {
      if (Date.now() - lastMsgTs > 30_000) {
        stopHeartbeat()
        if (ws) { ws.onclose = null; ws.close(); ws = null }
        if (!destroyed) doConnect()
        return
      }
      if (ws?.readyState === WebSocket.OPEN) {
        ws.send(JSON.stringify({ ping: Date.now() }))
      }
    }, 15_000)
  }

  function stopHeartbeat() {
    if (pingTimer !== null) { clearInterval(pingTimer); pingTimer = null }
  }

  let curSymbol = ''
  let curResolution: KlineResolution = '60'

  function symSlug() {
    return curSymbol.toLowerCase().replace('/', '').replace('-', '')
  }

  function klineTopic() {
    return `market.${symSlug()}.kline.${RES_MAP[curResolution] ?? '1h'}`
  }

  function allTopics() {
    return [
      `market.${symSlug()}.ticket`,
      `market.${symSlug()}.mark`,
      `market.${symSlug()}.depth`,
      `market.${symSlug()}.trade`,
      klineTopic(),
    ]
  }

  function send(msg: object) {
    if (ws?.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify(msg))
    }
  }

  function requestKlines(resolution?: KlineResolution) {
    const res = resolution ?? curResolution
    const to = Date.now()
    const from = to - (BAR_MS[res] ?? BAR_MS['60']) * BAR_COUNT
    send({
      req: `market.${symSlug()}.kline.${RES_MAP[res] ?? '1h'}`,
      id: 'kline-req',
      from,
      to,
    })
  }

  // 加载更早的历史 K 线（向左滚动时调用）
  function requestKlinesRange(from: number, to: number) {
    send({
      req: klineTopic(),
      id: 'kline-req-more',
      from,
      to,
    })
  }

  function route(raw: string) {
    lastMsgTs = Date.now()
    let msg: any
    try { msg = JSON.parse(raw) } catch { return }
    // 服务器 pong 回包，仅刷新时间戳即可
    if (msg.pong !== undefined) return

    // K-line history response
    if (msg.rep && Array.isArray(msg.data)) {
      const bars: WsKlineBar[] = msg.data.map((d: any) => ({
        timestamp: d.beginTime,
        open: parseFloat(d.open),
        high: parseFloat(d.high),
        low: parseFloat(d.low),
        close: parseFloat(d.close),
        volume: parseFloat(d.volume),
      }))
      // 根据请求 id 区分初始加载和加载更多
      if (msg.id === 'kline-req-more') {
        callbacks.onKlineHistoryMore?.(bars)
      } else {
        callbacks.onKlineHistory?.(bars)
      }
      return
    }

    const ch: string = msg.ch ?? ''
    const tick = msg.tick
    if (!ch || !tick) return

    if (ch.includes('.kline.')) {
      const k = tick.k
      if (k) {
        callbacks.onKline?.({
          timestamp: k.t,
          open: parseFloat(k.o),
          high: parseFloat(k.h),
          low: parseFloat(k.l),
          close: parseFloat(k.c),
          volume: parseFloat(k.v),
        })
      }
    } else if (ch.includes('.ticket')) {
      callbacks.onTicker?.({
        close: tick.c, open: tick.o, high: tick.h,
        low: tick.l, volume: tick.v, turnover: tick.q,
      })
    } else if (ch.includes('.mark')) {
      callbacks.onMark?.({
        markPrice: tick.p,
        fundingRate: tick.r,
        nextFundingTime: tick.T,
      })
    } else if (ch.includes('.trade')) {
      callbacks.onTrade?.({
        price: tick.p, qty: tick.q, time: tick.T, isSell: tick.m,
      })
    } else if (ch.includes('.depth')) {
      callbacks.onBook?.({ bids: tick.bids ?? [], asks: tick.asks ?? [] })
    }
  }

  function doConnect() {
    if (destroyed) return
    ws = new WebSocket(WS_URL)
    ws.onopen = () => {
      send({ sub: allTopics(), id: 'init' })
      startHeartbeat()
      callbacks.onConnected?.()
    }
    ws.onmessage = (e) => route(e.data)
    ws.onerror = () => {}
    ws.onclose = () => {
      stopHeartbeat()
      if (destroyed) return
      reconnectTimer = setTimeout(doConnect, 3000)
    }
  }

  function connect(sym: string, resolution: KlineResolution) {
    destroyed = false
    curSymbol = sym
    curResolution = resolution
    if (reconnectTimer !== null) { clearTimeout(reconnectTimer); reconnectTimer = null }
    if (ws) { ws.onclose = null; ws.close(); ws = null }
    doConnect()
  }

  function changeSymbol(sym: string) {
    connect(sym, curResolution)
  }

  function changeResolution(resolution: KlineResolution) {
    if (curResolution === resolution) return
    const oldTopic = klineTopic()
    curResolution = resolution
    send({ unsub: [oldTopic], id: 'unsub-kline' })
    send({ sub: [klineTopic()], id: 'sub-kline' })
    requestKlines(resolution)
  }

  function disconnect() {
    destroyed = true
    stopHeartbeat()
    if (reconnectTimer !== null) { clearTimeout(reconnectTimer); reconnectTimer = null }
    if (ws) { ws.onclose = null; ws.close(); ws = null }
  }

  return { connect, changeSymbol, changeResolution, requestKlines, requestKlinesRange, disconnect }
}
