/**
 * MEXC momentum strategy runner.
 *
 * Signal: combined scoring (DOW + hour + BUYP + RSI + US gap + 24h rev)
 * Execution: MEXC futures API, maker limit orders, 0% fee
 * Sizing: risk% × equity / stopDist
 */
import fs from 'node:fs'
import readline from 'node:readline'
import { MexcExecutor } from '../lib/mexc/executor.js'
import { MomentumEngine, DEFAULT_CONFIG } from '../core/momentum.js'
import type { MomentumEntryRequest, MomentumExitRequest } from '../core/momentum.js'

const DURATION_MS = Number(process.env.TXOCAP_BENCH_MS ?? 24 * 3_600_000)
const SYMBOL = 'BTC_USDT'
const CONTRACT_SIZE = 0.0001  // BTC per contract

// Load credentials from ./mexc file
function loadCreds(): { key: string; secret: string } {
  const path = './mexc'
  if (!fs.existsSync(path)) throw new Error('Missing ./mexc credentials file')
  const content = fs.readFileSync(path, 'utf8')
  const key = content.match(/AccessKey=(.+)/)?.[1]?.trim()
  const secret = content.match(/SecretKey=(.+)/)?.[1]?.trim()
  if (!key || !secret) throw new Error('Invalid ./mexc format — expected AccessKey=... SecretKey=...')
  return { key, secret }
}

function log(tag: string, msg: string) {
  process.stderr.write(`[${new Date().toISOString().slice(11, 19)}] [${tag}] ${msg}\n`)
}

// ── Bar aggregator (1-min kline equivalent from MEXC public WS or REST polling) ──
class BarAgg {
  private bars: { o: number; h: number; l: number; c: number }[] = []
  private curKey = -1
  private cur: { o: number; h: number; l: number; c: number } | null = null

  ingest(price: number, ts: number): boolean {
    const key = Math.floor(ts / 60000)  // 1-minute bars
    if (key !== this.curKey) {
      if (this.cur) this.bars.push({ ...this.cur })
      this.curKey = key
      this.cur = { o: price, h: price, l: price, c: price }
      return this.bars.length > 0
    }
    if (this.cur) {
      this.cur.h = Math.max(this.cur.h, price)
      this.cur.l = Math.min(this.cur.l, price)
      this.cur.c = price
    }
    return false
  }

  get() { return this.bars }
  count() { return this.bars.length }
}

async function main() {
  const { key, secret } = loadCreds()
  const mexc = new MexcExecutor(key, secret)
  const startedAt = Date.now()

  // ── Startup checks ──
  log('INIT', 'Connecting to MEXC...')
  const equity = await mexc.getEquity()
  const balance = await mexc.getBalance()
  if (balance < 10) throw new Error(`Insufficient USDT balance: $${balance.toFixed(2)} — deposit USDT to futures account first`)
  log('INIT', `Balance: $${balance.toFixed(2)}  Equity: $${equity.toFixed(2)}`)

  // Set leverage to 100x
  await mexc.setLeverage(SYMBOL, 100)
  log('INIT', 'Leverage set to 100x')

  // Cancel any existing orders
  await mexc.cancelAll(SYMBOL)

  // Check no open position
  const existing = await mexc.getPosition(SYMBOL)
  if (existing) throw new Error(`Open position exists: ${existing.side} ${existing.vol} contracts — close it first`)

  const startBal = balance

  // ── Strategy engine ──
  const barAgg = new BarAgg()
  const engine = new MomentumEngine({
    ...DEFAULT_CONFIG,
    startingCapital: startBal,
    entryFeeBps: 0,    // MEXC 0% maker
    exitFeeBps: 0,
    stopBps: 100,
  })

  let busy = false, stopping = false
  let lastBal = startBal, lastBalAt = 0
  const stats = { entries: 0, entryFills: 0, exits: 0, exitFills: 0, exitMarket: 0 }

  async function fetchBal(): Promise<number> {
    const now = Date.now()
    if (now - lastBalAt < 5000) return lastBal
    lastBalAt = now
    try { lastBal = await mexc.getBalance() } catch {}
    return lastBal
  }

  // Price is needed for limit order placement
  let lastPrice = 0

  // ── Entry ──
  async function executeEntry(evt: MomentumEntryRequest): Promise<void> {
    if (busy || stopping) return
    busy = true; stats.entries++
    const side = evt.side === 'long' ? 'BUY' : 'SELL'
    // Convert USDT notional to contracts
    const price = lastPrice || evt.refPrice
    const notional = Math.min(engine.getConfig().startingCapital * engine.getConfig().riskPct / 0.01, engine.getConfig().startingCapital * 100)
    const vol = Math.max(1, Math.round(notional / (price * CONTRACT_SIZE)))

    log('SIGNAL', `${evt.side.toUpperCase()} mom=${evt.momentumBps.toFixed(1)}bps qty=${vol} contracts (~$${(vol * price * CONTRACT_SIZE).toFixed(0)}) @~${price.toFixed(1)}`)

    const result = await mexc.chaseLimitOrder(SYMBOL, side, vol, () => lastPrice || price, 3, 4000)

    if (result.filled && result.avgPrice) {
      stats.entryFills++
      engine.confirmEntry(evt, result.avgPrice, result.qty * CONTRACT_SIZE, Date.now())
      log('FILL', `entry ${evt.side.toUpperCase()} ${result.qty}ct @${result.avgPrice.toFixed(1)} (${result.attempts}att, ${(result.elapsedMs/1000).toFixed(1)}s)`)
    } else {
      engine.rejectEntry(evt, Date.now())
      log('MISS', `entry not filled after ${result.attempts} attempts`)
    }
    busy = false
  }

  // ── Exit ──
  async function executeExit(evt: MomentumExitRequest): Promise<void> {
    if (busy || stopping) return
    busy = true; stats.exits++

    const pos = await mexc.getPosition(SYMBOL)
    if (!pos) { engine.rejectEntry(evt as any, Date.now()); busy = false; return }

    const side = evt.side === 'long' ? 'SELL' : 'BUY'
    const vol = pos.vol
    const price = lastPrice || evt.targetPrice

    const result = await mexc.chaseLimitOrder(
      SYMBOL, side, vol, () => lastPrice || price,
      evt.reason === 'stop' ? 2 : 6, 3000
    )

    if (result.filled && result.avgPrice) {
      stats.exitFills++
      engine.confirmExit(evt, result.avgPrice, false, Date.now())
    } else {
      // Fallback to market
      stats.exitMarket++
      log('MKTFB', `${evt.reason} exit maker failed — using market order`)
      await mexc.placeMarketOrder(SYMBOL, side, vol, true)
      await new Promise(r => setTimeout(r, 2000))
      engine.confirmExit(evt, lastPrice || evt.targetPrice, true, Date.now())
    }

    const t = engine.getState().closed.at(-1)
    const bal = await fetchBal()
    if (t) log('EXIT', `${t.exitReason.toUpperCase()} ${t.side.toUpperCase()} net=${t.netBps.toFixed(1)}bps $${t.netUsd.toFixed(2)} bal=$${bal.toFixed(2)}`)
    busy = false
  }

  // ── Price polling (MEXC REST, 1s interval) ──
  // MEXC doesn't require WebSocket for this strategy (1-min bars)
  const pricePoller = setInterval(async () => {
    try {
      const price = await mexc.getMarkPrice(SYMBOL)
      if (!price) return
      lastPrice = price

      const newBar = barAgg.ingest(price, Date.now())
      if (!newBar || busy) return

      const bars = barAgg.get()
      engine.setBars(bars)
      engine.setBarIndex(bars.length - 1)

      const events = engine.tick(price, Date.now())
      for (const evt of events) {
        if (evt.type === 'entry') void executeEntry(evt)
        else if (evt.type === 'exit') void executeExit(evt)
      }
    } catch {}
  }, 1000)

  // ── Status ──
  const statusTimer = setInterval(async () => {
    const st = engine.getState()
    const bal = await fetchBal()
    const elapsed = Math.round((Date.now() - startedAt) / 60000)
    const pos = st.position
    const posStr = pos ? `${pos.side.toUpperCase()} ${pos.sizeBtc.toFixed(4)}btc @${pos.entryPrice.toFixed(1)} peak=${pos.peakFavBps.toFixed(1)}bps` : 'flat'
    log('STATUS', `${elapsed}min bars=${barAgg.count()} bal=$${bal.toFixed(2)} pnl=${(bal-startBal>=0?'+':'')}$${(bal-startBal).toFixed(2)} trades=${st.closed.length} pos=${posStr}`)
  }, 60_000)

  const endTimer = setTimeout(() => void shutdown('timer'), DURATION_MS)

  async function shutdown(reason: string) {
    if (stopping) return; stopping = true
    log('STOP', reason)
    clearInterval(pricePoller); clearInterval(statusTimer); clearTimeout(endTimer)
    try { await mexc.cancelAll(SYMBOL) } catch {}
    const openPos = await mexc.getPosition(SYMBOL).catch(() => null)
    if (openPos && openPos.vol > 0) {
      const side = openPos.side === 'LONG' ? 'SELL' : 'BUY'
      await mexc.placeMarketOrder(SYMBOL, side, openPos.vol, true)
      log('FLAT', `market flatten: ${openPos.side} ${openPos.vol}ct`)
    }
    const endBal = await mexc.getBalance().catch(() => NaN)
    const st = engine.getState()
    const wins = st.closed.filter(t => t.netUsd > 0)
    process.stderr.write('\n' + '═'.repeat(60) + '\n')
    process.stderr.write('MEXC MOMENTUM RUN REPORT\n')
    process.stderr.write('═'.repeat(60) + '\n')
    process.stderr.write(`Duration:   ${Math.round((Date.now()-startedAt)/60000)}min\n`)
    process.stderr.write(`Balance:    $${startBal.toFixed(2)} → $${Number.isFinite(endBal)?endBal.toFixed(2):'n/a'}\n`)
    process.stderr.write(`PnL:        ${Number.isFinite(endBal)?(endBal-startBal>=0?'+':'')+'$'+(endBal-startBal).toFixed(2):'n/a'}\n`)
    process.stderr.write(`Trades:     ${st.closed.length} (${wins.length}W/${st.closed.length-wins.length}L WR=${st.closed.length?(wins.length/st.closed.length*100).toFixed(0):0}%)\n`)
    process.stderr.write(`Fees:       $${st.totalFees.toFixed(2)} (MEXC maker = 0%)\n`)
    process.stderr.write(`MaxDD:      ${st.maxDdPct}%\n`)
    process.stderr.write(`Fills:      entry ${stats.entryFills}/${stats.entries}  exit ${stats.exitFills}/${stats.exits}  mkt=${stats.exitMarket}\n`)
    if (st.closed.length) {
      process.stderr.write('\nTrades:\n')
      for (const t of st.closed) {
        process.stderr.write(`  ${t.exitReason.toUpperCase().padEnd(5)} ${t.side.toUpperCase()} ${t.entryPrice.toFixed(1)}→${t.exitPrice.toFixed(1)} ${t.sizeBtc.toFixed(4)}btc net=${t.netBps.toFixed(1)}bps $${t.netUsd.toFixed(2)} ${(t.holdBars/6).toFixed(0)}h\n`)
      }
    }
    process.stderr.write('═'.repeat(60) + '\n')
    process.exit(0)
  }

  process.on('SIGINT', () => void shutdown('sigint'))
  process.on('SIGTERM', () => void shutdown('sigterm'))
  log('RUN', `Started. Running for ${Math.round(DURATION_MS/60000)}min. Signal: combined score>=10, hold 1-4h, stop 100bps.`)
}

main().catch(e => {
  process.stderr.write(`[FATAL] ${e instanceof Error ? e.stack ?? e.message : String(e)}\n`)
  process.exit(1)
})
