import WebSocket from 'ws'
import nacl from 'tweetnacl'
import bs58 from 'bs58'
import { httpClient } from '../../utils/httpClient.js'

export interface PacificaEndpoints {
  time: string // GET
  symbols: string // GET
  depth: string // GET  (expects ?symbol=&limit=)
  balances: string // GET
  positions: string // GET
  accountInfo: string // GET
  leverage: string // POST
  subaccountCreate: string // POST
  agentBind: string // POST
  orderCreateMarket: string // POST
  orderCreateLimit: string // POST
  orderCreateStop: string // POST
  orderCancel: string // POST
  orderCancelAll: string // POST
  orderGet: string // GET  (expects ?symbol=&orderId=)
  openOrders: string // GET  (expects ?symbol=&account=)
  orderBatch: string // POST
  positionTpSl: string // POST
}

export interface PacificaCfg {
  baseUrl: string
  wsUrl: string
  apiKey?: string
  privateKey?: string
  timeoutMs?: number
  account?: string
  agentWallet?: string
  agentPrivateKey?: string
}

export class PacificaClient {
  public endpoints: PacificaEndpoints
  public cfg: PacificaCfg
  private cachedKeys?: { secretKey: Uint8Array; publicKey: Uint8Array }
  private cachedAgentKeys?: { secretKey: Uint8Array; publicKey: Uint8Array }
  private defaultAccount?: string
  private agentWallet?: string
  constructor(cfg: PacificaCfg) {
    this.cfg = cfg
    if (!this.cfg.baseUrl || !this.cfg.wsUrl) {
      throw new Error('PacificaClient: baseUrl and wsUrl are required')
    }
    // 固定为官方文档结构，不再从环境或配置读取
    this.endpoints = {
      // Per docs: base is /api/v1; info and orderbook exist
      time: '/api/v1/info',
      symbols: '/api/v1/info',
      depth: '/api/v1/book',
      prices: '/api/v1/prices',
      // The rest may differ; keep placeholders under /api/v1 for now
      balances: '/api/v1/account/balance',
      positions: '/api/v1/account/positions',
      accountInfo: '/api/v1/account/info', // POST per docs
      leverage: '/api/v1/account/update-leverage',
      subaccountCreate: '/api/v1/subaccounts/create',
      agentBind: '/api/v1/agent/bind',
      orderCreateMarket: '/api/v1/orders/create_market',
      orderCreateLimit: '/api/v1/orders/create',
      orderCreateStop: '/api/v1/orders/stop/create',
      orderCancel: '/api/v1/orders/cancel',
      orderCancelAll: '/api/v1/orders/cancel_all',
      orderGet: '/api/v1/orders/history', // may be GET by id or requires POST; keep for now
      openOrders: '/api/v1/orders',
      orderBatch: '/api/v1/orders/batch',
      positionTpSl: '/api/v1/positions/tpsl',
    }
    this.defaultAccount = cfg.account
    this.agentWallet = cfg.agentWallet
  }

  private async sleep(ms: number): Promise<void> {
    return new Promise(r => setTimeout(r, ms))
  }

  private shouldRetry(status: number): boolean {
    if (status === 429) return true
    if (status >= 500 && status <= 599) return true
    return false
  }

  private parseSecretKey(): { secretKey?: Uint8Array; publicKey?: Uint8Array } {
    if (this.cachedKeys) return this.cachedKeys
    const pk = this.cfg.privateKey
    if (!pk) return {}
    try {
      let sk: Uint8Array | undefined
      if (pk.startsWith('[')) {
        sk = Uint8Array.from(JSON.parse(pk))
      } else {
        sk = bs58.decode(pk)
      }
      if (sk && (sk.length === 64 || sk.length === 32)) {
        const kp = sk.length === 64 ? nacl.sign.keyPair.fromSecretKey(sk) : nacl.sign.keyPair.fromSeed(sk)
        this.cachedKeys = { secretKey: kp.secretKey, publicKey: kp.publicKey }
        return this.cachedKeys
      }
    } catch {}
    return {}
  }

  private parseAgentSecretKey(): { secretKey?: Uint8Array; publicKey?: Uint8Array } {
    if (this.cachedAgentKeys) return this.cachedAgentKeys
    const pk = this.cfg.agentPrivateKey
    if (!pk) return {}
    try {
      let sk: Uint8Array | undefined
      if (pk.startsWith('[')) {
        sk = Uint8Array.from(JSON.parse(pk))
      } else {
        sk = bs58.decode(pk)
      }
      if (sk && (sk.length === 64 || sk.length === 32)) {
        const kp = sk.length === 64 ? nacl.sign.keyPair.fromSecretKey(sk) : nacl.sign.keyPair.fromSeed(sk)
        this.cachedAgentKeys = { secretKey: kp.secretKey, publicKey: kp.publicKey }
        return this.cachedAgentKeys
      }
    } catch {}
    return {}
  }

  requireAccount(account?: string): string {
    const finalAccount = account ?? this.defaultAccount
    if (!finalAccount) throw new Error('PacificaClient: account is required')
    return finalAccount
  }

  requireAgentWallet(wallet?: string): string {
    const final = wallet ?? this.agentWallet
    if (!final) throw new Error('PacificaClient: agent wallet is required')
    return final
  }

  private sortJson(value: any): any {
    if (Array.isArray(value)) return value.map(item => this.sortJson(item))
    if (value && typeof value === 'object') {
      const sorted: Record<string, any> = {}
      Object.keys(value)
        .sort()
        .forEach(key => {
          const val = value[key]
          if (val !== undefined && val !== null) sorted[key] = this.sortJson(val)
        })
      return sorted
    }
    return value
  }

  private canonicalJson(input: any): string {
    if (input == null) return ''
    const sorted = this.sortJson(input)
    return JSON.stringify(sorted, (key, value) => value, 0).replace(/\s+/g, '')
  }

  private signHeaders = async (path: string, body: any, ts: number): Promise<Record<string, string>> => {
    const headers: Record<string, string> = {}
    if (this.cfg.apiKey) headers['X-API-KEY'] = this.cfg.apiKey
    const { secretKey, publicKey } = this.parseSecretKey()
    if (!secretKey || !publicKey) return headers
    const payload = `${ts}|${path}|${this.canonicalJson(body)}`
    const msg = new TextEncoder().encode(payload)
    const sig = nacl.sign.detached(msg, secretKey)
    headers['X-PUBKEY'] = bs58.encode(publicKey)
    headers['X-TS'] = String(ts)
    headers['X-SIGNATURE'] = bs58.encode(sig)
    return headers
  }

  async buildSignedHeaders(path: string, body?: any): Promise<Record<string, string>> {
    const ts = Date.now()
    return await this.signHeaders(path, body, ts)
  }

  private async sign(type: string, data: Record<string, any>, expiryWindow: number, useAgent: boolean) {
    const keys = useAgent ? this.parseAgentSecretKey() : this.parseSecretKey()
    if (!keys.secretKey) throw new Error('PacificaClient: private key missing for signing')
    const timestamp = Date.now()
    const message = this.sortJson({ timestamp, expiry_window: expiryWindow, type, data })
    const json = JSON.stringify(message, (k, v) => v, 0).replace(/\s+/g, '')
    const sig = nacl.sign.detached(new TextEncoder().encode(json), keys.secretKey)
    return { timestamp, expiryWindow, signature: bs58.encode(sig) }
  }

  async signOperation(type: string, data: Record<string, any>, expiryWindow = 30000) {
    return this.sign(type, data, expiryWindow, false)
  }

  async signOperationWithAgent(type: string, data: Record<string, any>, expiryWindow = 30000) {
    return this.sign(type, data, expiryWindow, true)
  }

  async get<T>(path: string, query?: Record<string, string | number | undefined>): Promise<T> {
    const queryString = query
      ? `?${new URLSearchParams(Object.entries(query).filter(([, v]) => v !== undefined) as any).toString()}`
      : ''
    const url = `${this.cfg.baseUrl}${path}${queryString}`
    const pathForSig = path.split('?')[0]
    const maxRetries = 4
    const baseDelay = 250
    for (let attempt = 0; attempt <= maxRetries; attempt++) {
      try {
        const ts = Date.now()
        const headers = await this.signHeaders(pathForSig, undefined, ts)
        const res = await httpClient.get(url, {
          headers: headers as any,
          exchange: 'pacifica',
          timeout: this.cfg.timeoutMs || 30000,
          retries: 0, // 我们自己处理重试
        })

        if (!res.ok) {
          if (this.shouldRetry(res.status) && attempt < maxRetries) {
            // 对429错误使用更长的等待时间
            const isRateLimit = res.status === 429
            const delay = isRateLimit
              ? 5000 + attempt * 2000 // 429错误: 5s, 7s, 9s, 11s
              : baseDelay * Math.pow(2, attempt) // 其他错误: 指数退避
            const jitter = Math.floor(Math.random() * 1000)

            console.log(`HTTP ${res.status}错误，${delay + jitter}ms后重试 (${attempt + 1}/${maxRetries + 1})`)
            await this.sleep(delay + jitter)
            continue
          }
          const errorMsg = res.data ? (typeof res.data === 'string' ? res.data : JSON.stringify(res.data)) : ''
          throw new Error(`HTTP ${res.status} ${path}${errorMsg ? ` - ${errorMsg.slice(0, 500)}` : ''}`)
        }
        return res.data as T
      } catch (e: any) {
        const message = String(e?.message || '')
        if (message.startsWith('HTTP')) {
          throw e
        }
        if (attempt < maxRetries) {
          const jitter = Math.floor(Math.random() * 100)
          await this.sleep(baseDelay * Math.pow(2, attempt) + jitter)
          continue
        }
        throw e
      }
    }
    // Unreachable, to satisfy TS return type
    throw new Error('unreachable')
  }

  async getPublic<T>(path: string): Promise<T> {
    const url = `${this.cfg.baseUrl}${path}`
    const maxRetries = 4
    const baseDelay = 250
    for (let attempt = 0; attempt <= maxRetries; attempt++) {
      try {
        const res = await httpClient.get(url, {
          exchange: 'pacifica',
          timeout: this.cfg.timeoutMs || 30000,
          retries: 0, // 我们自己处理重试
        })

        if (!res.ok) {
          if (this.shouldRetry(res.status) && attempt < maxRetries) {
            // 对429错误使用更长的等待时间
            const isRateLimit = res.status === 429
            const delay = isRateLimit
              ? 5000 + attempt * 2000 // 429错误: 5s, 7s, 9s, 11s
              : baseDelay * Math.pow(2, attempt) // 其他错误: 指数退避
            const jitter = Math.floor(Math.random() * 1000)

            console.log(`HTTP ${res.status}错误，${delay + jitter}ms后重试 (${attempt + 1}/${maxRetries + 1})`)
            await this.sleep(delay + jitter)
            continue
          }
          const errorMsg = res.data ? (typeof res.data === 'string' ? res.data : JSON.stringify(res.data)) : ''
          throw new Error(`HTTP ${res.status} ${path}${errorMsg ? ` - ${errorMsg.slice(0, 500)}` : ''}`)
        }
        return res.data as T
      } catch (e: any) {
        const message = String(e?.message || '')
        if (message.startsWith('HTTP')) {
          throw e
        }
        if (attempt < maxRetries) {
          const jitter = Math.floor(Math.random() * 100)
          await this.sleep(baseDelay * Math.pow(2, attempt) + jitter)
          continue
        }
        throw e
      }
    }
    throw new Error('unreachable')
  }

  async post<T>(path: string, body: any, opts?: { skipHeaderSig?: boolean }): Promise<T> {
    const url = `${this.cfg.baseUrl}${path}`
    const maxRetries = 4
    const baseDelay = 250
    for (let attempt = 0; attempt <= maxRetries; attempt++) {
      try {
        const ts = Date.now()
        const headers = opts?.skipHeaderSig ? {} : await this.signHeaders(path, body, ts)
        const res = await httpClient.post(url, body, {
          headers: { 'Content-Type': 'application/json', ...headers } as any,
          exchange: 'pacifica',
          timeout: this.cfg.timeoutMs || 30000,
          retries: 0, // 我们自己处理重试
        })

        if (!res.ok) {
          if (this.shouldRetry(res.status) && attempt < maxRetries) {
            // 对429错误使用更长的等待时间
            const isRateLimit = res.status === 429
            const delay = isRateLimit
              ? 5000 + attempt * 2000 // 429错误: 5s, 7s, 9s, 11s
              : baseDelay * Math.pow(2, attempt) // 其他错误: 指数退避
            const jitter = Math.floor(Math.random() * 1000)

            console.log(`HTTP ${res.status}错误，${delay + jitter}ms后重试 (${attempt + 1}/${maxRetries + 1})`)
            await this.sleep(delay + jitter)
            continue
          }
          const errorMsg = res.data ? (typeof res.data === 'string' ? res.data : JSON.stringify(res.data)) : ''
          throw new Error(`HTTP ${res.status} ${path}${errorMsg ? ` - ${errorMsg.slice(0, 500)}` : ''}`)
        }
        return res.data as T
      } catch (e: any) {
        const message = String(e?.message || '')
        if (message.startsWith('HTTP')) {
          throw e
        }
        if (attempt < maxRetries) {
          const jitter = Math.floor(Math.random() * 100)
          await this.sleep(baseDelay * Math.pow(2, attempt) + jitter)
          continue
        }
        throw e
      }
    }
    throw new Error('unreachable')
  }

  wsConnect(_params: Record<string, any> = {}, _auth = true): WebSocket {
    // 官方主网：wss://ws.pacifica.fi/ws 测试网：wss://test-ws.pacifica.fi/ws
    // 若 cfg.wsUrl 已包含 /ws 则直接使用；否则补上 /ws
    const raw = this.cfg.wsUrl.replace(/\/$/, '')
    const url = /\/ws(\?|$)/.test(raw) ? raw : `${raw}/ws`
    return new WebSocket(url)
  }

  async wsLogin(_ws: WebSocket, _params: Record<string, any> = {}): Promise<void> {
    return
  }
}
