import { EventEmitter } from 'events'
import WebSocket from 'ws'
import { ExchangeAdapter, PlaceOrderReq, Order, Position, Balance, Depth } from '../ExchangeAdapter'
import { PacificaClient } from './PacificaClient'
import { PacificaOrdersAdapter } from './OrdersAdapter'
import { PacificaAccountAdapter } from './AccountAdapter'
import { logger } from '../../utils/logger.js'

export class PacificaAdapter implements ExchangeAdapter {
  private events = new EventEmitter()
  private depthSocket?: WebSocket
  private ordersSocket?: WebSocket
  private balanceSocket?: WebSocket
  private tradesSocket?: WebSocket
  private orders: PacificaOrdersAdapter
  private account: PacificaAccountAdapter
  private startHeartbeat(ws: WebSocket) {
    const timer = setInterval(() => {
      try {
        ws.send(JSON.stringify({ method: 'ping' }))
      } catch {}
    }, 30000)
    ;(ws as any).__hb = timer
    const cleanup = () => {
      try {
        clearInterval((ws as any).__hb)
      } catch {}
    }
    ws.on('close', cleanup)
    ws.on('error', cleanup)
  }
  constructor(private client: PacificaClient) {
    this.orders = new PacificaOrdersAdapter(client)
    this.account = new PacificaAccountAdapter(client)
  }

  name() {
    return 'pacifica'
  }
  async time(): Promise<number> {
    return Date.now()
  }
  async symbols(): Promise<string[]> {
    const response = await this.client.getPublic<any>(this.client.endpoints.symbols)
    const data = Array.isArray(response) ? response : response?.data ?? []
    return (Array.isArray(data) ? data : []).map((m: any) => String(m.symbol)).filter(Boolean)
  }
  async balances(): Promise<Balance[]> {
    const data = await this.account.balances()
    return (Array.isArray(data) ? data : [])
      .map((balance: any) => ({
        asset: String(balance.asset ?? balance.symbol ?? ''),
        total: String(balance.total ?? balance.balance ?? '0'),
        free: String(balance.free ?? balance.available ?? balance.balance ?? '0'),
      }))
      .filter(b => b.asset)
  }
  async positions(): Promise<Position[]> {
    const data = await this.account.positions()
    const list = Array.isArray(data) ? data : []
    return list
      .map((pos: any) => {
        const side = (pos.side ?? 'bid') === 'ask' ? 'SHORT' : ('LONG' as const)
        return {
          symbol: String(pos.symbol),
          side,
          qty: String(pos.amount ?? pos.qty ?? '0'),
          entryPrice: String(pos.entry_price ?? pos.entryPrice ?? '0'),
          unrealizedPnl: String(pos.funding ?? pos.unrealizedPnL ?? '0'),
          leverage: Number(pos.leverage ?? 0),
        } as Position
      })
      .filter(p => p.symbol)
  }
  async placeOrder(req: PlaceOrderReq): Promise<Order> {
    const account = this.client.requireAccount((req as any).account)
    const payload: any = {
      account,
      symbol: this.normalizeSymbol(req.symbol),
      amount: String(req.quantity),
      side: (req.side === 'BUY' ? 'bid' : 'ask') as 'bid' | 'ask',
      reduceOnly: !!req.reduceOnly,
      clientOrderId: req.clientOrderId,
      tif: req.tif as any,
      price: req.price ? String(req.price) : undefined,
      agentWallet: (req as any).meta?.agentWallet || this.client.cfg.agentWallet,
    }
    let response: any
    if (req.type === 'MARKET')
      response = await this.orders.createMarketOrder({
        ...payload,
        slippagePercent: (req as any).meta?.slippagePercent || '0.5',
        tif: 'IOC' as any,
      })
    else if (req.type === 'LIMIT' || req.type === 'POST_ONLY')
      response = await this.orders.createLimitOrder({
        ...payload,
        tif: req.type === 'POST_ONLY' ? 'ALO' : payload.tif,
        reduceOnly: payload.reduceOnly,
      })
    else response = await this.orders.createStopOrder(payload)
    const data = response?.data ?? response
    const status = String(data?.status ?? 'NEW').toUpperCase() as Order['status']
    return {
      id: String(data?.order_id ?? data?.orderId ?? data?.id ?? ''),
      symbol: String(data?.symbol ?? req.symbol),
      status,
      side: req.side,
      type: req.type,
      price: String(data?.price ?? req.price ?? '0'),
      origQty: String(data?.amount ?? data?.origQty ?? req.quantity),
      executedQty: String(data?.filled ?? data?.executedQty ?? '0'),
      updateTime: Number(data?.timestamp ?? Date.now()),
    }
  }
  async cancelOrder(symbol: string, orderId: string): Promise<void> {
    await this.orders.cancelOrder({ account: this.client.requireAccount(), symbol, orderId: Number(orderId) })
  }
  async cancelAll(symbol: string): Promise<void> {
    await this.orders.cancelAll({ symbol })
  }
  async getOrder(symbol: string, orderId: string): Promise<Order> {
    const account = this.client.requireAccount()
    const response = await this.client.get<any>(this.client.endpoints.orderGet, { symbol, orderId, account })
    const data = response?.data ?? response
    return {
      id: String(data?.order_id ?? data?.orderId ?? orderId),
      symbol: String(data?.symbol ?? symbol),
      status: String(data?.status ?? 'NEW').toUpperCase() as Order['status'],
      side: String(data?.side ?? 'bid').toUpperCase() === 'BID' ? 'BUY' : 'SELL',
      type: String(data?.type ?? 'LIMIT').toUpperCase() as any,
      price: String(data?.price ?? '0'),
      origQty: String(data?.amount ?? data?.origQty ?? '0'),
      executedQty: String(data?.filled ?? data?.executedQty ?? '0'),
      updateTime: Number(data?.timestamp ?? data?.updated_at ?? Date.now()),
    }
  }
  async openOrders(symbol: string): Promise<Order[]> {
    const account = this.client.requireAccount()
    let response: any
    try {
      response = await this.orders.openOrders(symbol, account)
    } catch (e: any) {
      const msg = String(e?.message || '')
      if (msg.startsWith('HTTP 403')) {
        // eslint-disable-next-line no-console
        console.warn('[pacifica.openOrders] 403 forbidden; returning empty list (likely CDN/whitelist).')
        return []
      }
      throw e
    }
    const list = response?.data ?? response
    return (Array.isArray(list) ? list : []).map((o: any) => ({
      id: String(o.order_id ?? o.orderId ?? o.id),
      symbol: String(o.symbol ?? symbol),
      status: String(o.status ?? 'NEW').toUpperCase() as Order['status'],
      side: String(o.side ?? 'bid').toUpperCase() === 'BID' ? 'BUY' : 'SELL',
      type: String(o.type ?? 'LIMIT').toUpperCase() as any,
      price: String(o.price ?? '0'),
      origQty: String(o.amount ?? o.origQty ?? '0'),
      executedQty: String(o.filled ?? o.executedQty ?? '0'),
      updateTime: Number(o.timestamp ?? o.updated_at ?? Date.now()),
    }))
  }
  async depth(symbol: string, limit = 50): Promise<Depth> {
    const endpoint = this.client.endpoints.depth
    const baseSymbol = this.normalizeSymbol(symbol)
    // Try robust combinations: symbol/market keys, public and signed
    const candidateQueries = [
      { symbol: baseSymbol },
      { market: baseSymbol },
      { market: baseSymbol, limit },
      { symbol: baseSymbol, limit },
    ]
    let response: any | null = null
    for (const q of candidateQueries) {
      const qs = `?${new URLSearchParams(q as any).toString()}`
      try {
        // debug
        // eslint-disable-next-line no-console
        console.log('[pacifica.depth] try public', endpoint + qs)
        response = await this.client.getPublic<any>(endpoint + qs)
        if (response) break
      } catch (e) {
        // eslint-disable-next-line no-console
        console.log('[pacifica.depth] public failed', (e as any)?.message || e)
      }
      try {
        // eslint-disable-next-line no-console
        console.log('[pacifica.depth] try signed', endpoint, q)
        response = await this.client.get<any>(endpoint, q as any)
        if (response) break
      } catch (e) {
        // eslint-disable-next-line no-console
        console.log('[pacifica.depth] signed failed', (e as any)?.message || e)
      }
    }
    if (!response) throw new Error('orderbook_not_found')
    const payload = response?.data ?? response
    // Doc format: payload.l = [ bids[], asks[] ], where each element is { p: price, a: amount, n: numOrders }
    if (Array.isArray(payload?.l) && payload.l.length >= 2) {
      const rawBids = Array.isArray(payload.l[0]) ? payload.l[0] : []
      const rawAsks = Array.isArray(payload.l[1]) ? payload.l[1] : []
      const bids = rawBids
        .map((lvl: any) => ({ price: String(lvl?.p ?? lvl?.price ?? ''), qty: String(lvl?.a ?? lvl?.amount ?? '') }))
        .filter(x => x.price && x.qty)
      const asks = rawAsks
        .map((lvl: any) => ({ price: String(lvl?.p ?? lvl?.price ?? ''), qty: String(lvl?.a ?? lvl?.amount ?? '') }))
        .filter(x => x.price && x.qty)
      return { bids, asks, ts: Number(payload?.t ?? Date.now()) }
    }
    // Fallback: bids/asks as [price, qty]
    const bids = (payload?.bids ?? []).map((x: any) => ({ price: String(x[0]), qty: String(x[1]) }))
    const asks = (payload?.asks ?? []).map((x: any) => ({ price: String(x[0]), qty: String(x[1]) }))
    return { bids, asks, ts: Number(payload?.ts ?? Date.now()) }
  }
  private normalizeSymbol(input: string): string {
    // Per docs, orderbook expects plain base symbol like 'BTC'
    if (!input) return input
    const s = String(input).toUpperCase()
    if (s.includes('-')) return s.split('-')[0]
    if (s.endsWith('USDT')) return s.replace('USDT', '')
    if (s.endsWith('USD')) return s.replace('USD', '')
    return s
  }
  ws() {
    return this.events
  }

  subscribeDepth(symbol: string) {
    if (this.depthSocket) this.depthSocket.close()
    this.depthSocket = this.client.wsConnect({}, false)
    this.depthSocket.on('open', () => {
      this.startHeartbeat(this.depthSocket as WebSocket)
      // 订阅 orderbook（book）
      const sub = {
        method: 'subscribe',
        params: { source: 'book', symbol: this.normalizeSymbol(symbol), agg_level: 1 },
      }
      ;(this.depthSocket as WebSocket).send(JSON.stringify(sub))
    })
    this.depthSocket.on('message', (raw: WebSocket.RawData) => {
      try {
        const msg = JSON.parse(String(raw))
        if (msg?.channel === 'book' && msg?.data?.l) {
          const payload = msg.data
          const rawBids = Array.isArray(payload.l[0]) ? payload.l[0] : []
          const rawAsks = Array.isArray(payload.l[1]) ? payload.l[1] : []
          this.events.emit('depth', {
            symbol,
            bids: rawBids.map((lvl: any) => ({ price: String(lvl?.p ?? ''), qty: String(lvl?.a ?? '') })),
            asks: rawAsks.map((lvl: any) => ({ price: String(lvl?.p ?? ''), qty: String(lvl?.a ?? '') })),
            ts: Number(payload?.t ?? Date.now()),
          })
        }
      } catch {}
    })
    this.depthSocket.on('close', () => this.events.emit('ws_close', { channel: 'depth', symbol }))
    this.depthSocket.on('error', e => this.events.emit('ws_error', e))
  }

  // ---- Private WS channels ----
  private subscribeChannel(
    topic: 'orders' | 'balance' | 'trades' | 'prices' | 'account_info' | 'account_positions',
    params: Record<string, any>,
    onMessage: (msg: any) => void,
  ) {
    const sockKey = `${topic}Socket` as const
    const existing = (this as any)[sockKey] as WebSocket | undefined
    if (existing) existing.close()
    const ws = this.client.wsConnect(params, true)
    ;(this as any)[sockKey] = ws
    ws.on('open', () => {
      this.startHeartbeat(ws)
      const sub = { method: 'subscribe', params: { source: topic, ...params } } as any
      // map aliases
      if (topic === 'orders') sub.params.source = 'account_orders'
      if (topic === 'balance') sub.params.source = 'account_balance'
      if (topic === 'trades') sub.params.source = 'trades'
      ws.send(JSON.stringify(sub))
    })
    ws.on('message', (raw: WebSocket.RawData) => {
      try {
        const msg = JSON.parse(String(raw))
        onMessage(msg)
      } catch {}
    })
    ws.on('close', () => this.events.emit('ws_close', { channel: topic, params }))
    ws.on('error', e => this.events.emit('ws_error', e))
  }

  subscribeOrders() {
    const account = this.client.requireAccount()
    this.subscribeChannel('orders', { account }, msg => {
      // 统一透传，交由上层处理
      this.events.emit('orders', msg)
    })
  }

  subscribeBalance() {
    const account = this.client.requireAccount()
    this.subscribeChannel('balance', { account }, msg => {
      this.events.emit('balance', msg)
    })
  }

  subscribeTrades(symbol?: string) {
    const params: Record<string, any> = {}
    if (symbol) params.symbol = this.normalizeSymbol(symbol)
    this.subscribeChannel('trades', params, msg => {
      this.events.emit('trades', msg)
    })
  }

  subscribePrices() {
    this.subscribeChannel('prices', {}, msg => this.events.emit('prices', msg))
  }

  subscribeAccountInfo() {
    const account = this.client.requireAccount()
    this.subscribeChannel('account_info', { account }, msg => this.events.emit('account_info', msg))
  }

  subscribeAccountPositions() {
    const account = this.client.requireAccount()
    this.subscribeChannel('account_positions', { account }, msg => this.events.emit('account_positions', msg))
  }

  // --- Extra helpers (not in ExchangeAdapter) ---
  async createStopOrder(params: {
    symbol: string
    side: 'BUY' | 'SELL'
    quantity: string
    stopPrice: string
    limitPrice?: string
    reduceOnly?: boolean
  }) {
    const account = this.client.requireAccount()
    const side = params.side === 'BUY' ? 'bid' : 'ask'
    const payload: any = {
      account,
      symbol: params.symbol,
      amount: String(params.quantity),
      side,
      reduceOnly: !!params.reduceOnly,
      clientOrderId: undefined,
      stopLoss: {
        stopPrice: String(params.stopPrice),
        limitPrice: params.limitPrice ? String(params.limitPrice) : undefined,
      },
      agentWallet: this.client.cfg.agentWallet,
    }
    const res: any = await this.orders.createStopOrder(payload)
    return res && (res as any).data !== undefined ? (res as any).data : res
  }

  async setPositionTpSl(params: {
    symbol: string
    side: 'BUY' | 'SELL'
    takeProfit?: { stopPrice: string; limitPrice?: string }
    stopLoss?: { stopPrice: string; limitPrice?: string }
  }) {
    const account = this.client.requireAccount()
    const side = params.side === 'BUY' ? 'bid' : 'ask'
    const normSymbol = String(params.symbol).toUpperCase().includes('-')
      ? String(params.symbol).toUpperCase().split('-')[0]
      : String(params.symbol).toUpperCase().endsWith('USDT')
      ? String(params.symbol).toUpperCase().replace('USDT', '')
      : String(params.symbol).toUpperCase().endsWith('USD')
      ? String(params.symbol).toUpperCase().replace('USD', '')
      : String(params.symbol).toUpperCase()
    const data: any = {
      symbol: normSymbol,
      side,
      take_profit: params.takeProfit
        ? {
            stop_price: String(params.takeProfit.stopPrice),
            limit_price: params.takeProfit.limitPrice ? String(params.takeProfit.limitPrice) : undefined,
          }
        : undefined,
      stop_loss: params.stopLoss
        ? {
            stop_price: String(params.stopLoss.stopPrice),
            limit_price: params.stopLoss.limitPrice ? String(params.stopLoss.limitPrice) : undefined,
          }
        : undefined,
    }
    const { signature, timestamp, expiryWindow } = await this.client.signOperation('set_position_tpsl', data, 30000)
    const body = {
      account,
      ...data,
      agent_wallet: this.client.cfg.agentWallet,
      signature,
      timestamp,
      expiry_window: expiryWindow,
    }
    if (process.env.NODE_ENV === 'development' && process.env.PACIFICA_DEBUG === '1') {
      logger.debug('Pacifica设置止盈止损调试', {
        operation: 'set_position_tpsl',
        signDataLength: JSON.stringify(data).length,
        signaturePreview: `b58(${String(signature).slice(0, 8)}...)`,
      })
    }
    const res = await this.client.post(this.client.endpoints.positionTpSl as any, body, { skipHeaderSig: true })
    return (res as any)?.data ?? res
  }

  async setPositionTp(params: { symbol: string; side: 'BUY' | 'SELL'; stopPrice: string; limitPrice?: string }) {
    return await this.setPositionTpSl({
      symbol: params.symbol,
      side: params.side,
      takeProfit: { stopPrice: params.stopPrice, limitPrice: params.limitPrice },
    })
  }

  async setPositionSl(params: { symbol: string; side: 'BUY' | 'SELL'; stopPrice: string; limitPrice?: string }) {
    return await this.setPositionTpSl({
      symbol: params.symbol,
      side: params.side,
      stopLoss: { stopPrice: params.stopPrice, limitPrice: params.limitPrice },
    })
  }

  async batch(actions: Array<{ type: 'Create' | 'Cancel'; data: any }>) {
    return await (this.orders as any).batch(actions)
  }
}
