/**
 * Backtest on historical data from Bybit + Binance bulk archives.
 * Sessions in data/historical/<YYYY-MM-DD>/ with trades.jsonl + orderbook.jsonl.
 *
 * Adaptations for historical data (only 2 exchanges vs 7 live):
 * - maxTopShare=1.0 — Binance aggTrades are 96% of notional due to aggregation
 * - Synthetic OBs auto-generated from trade data at 500ms in loadSession
 * - Tests both original rv[5,7] and wider rv[4,15] to find tradeable regimes
 */
import fs from 'node:fs'
import { STARTING_CAPITAL, PROFILES, round } from '../core/strategy.js'
import { replay, loadSession } from '../runners/replay.js'
import type { Profile } from '../core/strategy.js'

const HIST_DIR = 'data/historical'
const ORIG_DIR = 'data/sessions'

// ── Profiles ──
// Original thresholds but topShare disabled
const HIST_STRICT: Profile = {
  ...PROFILES.MAKER_OPT,
  name: 'STRICT_5_7',
  maxTopShare: 1.0,
}

// Wider rv gate to capture crash/recovery regimes
const HIST_WIDE: Profile = {
  ...PROFILES.MAKER_OPT,
  name: 'WIDE_4_15',
  maxTopShare: 1.0,
  minRealizedVolBps: 4,
  maxRealizedVolBps: 15,
}

// Taker (for comparison, no chase fill needed)
const HIST_TAKER: Profile = {
  ...PROFILES.TAKER,
  name: 'TAKER',
  maxTopShare: 1.0,
}

function runProfile(
  bars: [number, any][],
  obs: { ts: number; mid: number; bid: number; ask: number }[],
  profile: Profile
) {
  const st = replay(bars, obs, profile)
  const wins = st.closed.filter(t => t.net > 0)
  const losses = st.closed.filter(t => t.net <= 0)
  return {
    ret: round(st.capital - STARTING_CAPITAL),
    maxDdPct: round(st.maxDdPct * 100, 2),
    trades: st.closed.length,
    wins: wins.length,
    losses: losses.length,
    winRate: st.closed.length > 0 ? round(wins.length / st.closed.length * 100, 1) : 0,
    avgWin: wins.length > 0 ? round(wins.reduce((a, t) => a + t.net, 0) / wins.length) : 0,
    avgLoss: losses.length > 0 ? round(losses.reduce((a, t) => a + t.net, 0) / losses.length) : 0,
    fees: round(st.fees),
    sls: st.closed.filter(t => t.reason === 'sl').length,
    trails: st.closed.filter(t => t.reason === 'trail').length,
    times: st.closed.filter(t => t.reason === 'time' || t.reason === 'force_close').length,
    tp1s: st.closed.filter(t => t.reason === 'tp1').length,
    closed: st.closed,
  }
}

async function main() {
  const dirs = fs.readdirSync(HIST_DIR)
    .map(d => `${HIST_DIR}/${d}`)
    .filter(d => {
      try { return fs.statSync(`${d}/trades.jsonl`).size > 100_000 } catch { return false }
    })
    .sort()

  const profiles = [HIST_TAKER, HIST_STRICT, HIST_WIDE]
  const agg: Record<string, { ret: number; trades: number; wins: number; maxDd: number; fees: number; profitSessions: number; sessions: number }> = {}
  for (const p of profiles) agg[p.name] = { ret: 0, trades: 0, wins: 0, maxDd: 0, fees: 0, profitSessions: 0, sessions: 0 }

  console.log(`Historical backtest: ${dirs.length} sessions`)
  console.log(`Profiles: ${profiles.map(p => p.name).join(', ')}`)
  console.log()

  const header = ['', 'Return', 'MaxDD', 'Trades', 'WR', 'AvgW', 'AvgL', 'Fees', 'SL', 'Trail', 'Time', 'TP1'].map(h => h.padStart(10)).join('')

  for (const dir of dirs) {
    const date = dir.split('/').pop()!
    process.stderr.write(`Loading ${date}...`)
    const { bars, obs } = await loadSession(dir)
    process.stderr.write(` bars=${bars.length} obs=${obs.length}\n`)

    if (bars.length < 500 || obs.length < 100) {
      process.stderr.write(`  skipping (too small)\n`)
      continue
    }

    console.log(`=== ${date} (bars=${bars.length} obs=${obs.length}) ===`)
    console.log(header)

    for (const profile of profiles) {
      const r = runProfile(bars, obs, profile)
      const a = agg[profile.name]
      a.ret += r.ret; a.trades += r.trades; a.wins += r.wins
      a.maxDd = Math.max(a.maxDd, r.maxDdPct); a.fees += r.fees
      if (r.ret > 0) a.profitSessions++; a.sessions++

      const row = [
        profile.name.slice(0, 10).padStart(10),
        `$${r.ret}`, `${r.maxDdPct}%`, `${r.trades}`,
        `${r.winRate}%`, `$${r.avgWin}`, `$${r.avgLoss}`, `$${round(r.fees)}`,
        `${r.sls}`, `${r.trails}`, `${r.times}`, `${r.tp1s}`
      ].map(v => String(v).padStart(10)).join('')
      console.log(row)
    }
    console.log()
  }

  // ── Aggregate ──
  console.log('=== AGGREGATE (HISTORICAL) ===')
  for (const [name, a] of Object.entries(agg)) {
    const wr = a.trades > 0 ? round(a.wins / a.trades * 100, 1) : 0
    console.log(`  ${name.padEnd(12)} ret=$${round(a.ret)} maxDD=${round(a.maxDd, 2)}% trades=${a.trades} WR=${wr}% fees=$${round(a.fees)} profitable=${a.profitSessions}/${a.sessions}`)
  }

  // ── Original sessions for reference ──
  if (fs.existsSync(ORIG_DIR)) {
    console.log()
    console.log('=== ORIGINAL SESSIONS (reference) ===')
    const origDirs = fs.readdirSync(ORIG_DIR)
      .map(d => `${ORIG_DIR}/${d}`)
      .filter(d => { try { return fs.statSync(`${d}/trades.jsonl`).size > 1000 } catch { return false } })
      .sort()
    let origRet = 0, origTrades = 0, origWins = 0
    for (const d of origDirs) {
      const { bars, obs } = await loadSession(d)
      if (bars.length < 100 || obs.length < 100) continue
      const st = replay(bars, obs, PROFILES.MAKER_OPT)
      origRet += st.capital - STARTING_CAPITAL
      origTrades += st.closed.length
      origWins += st.closed.filter(t => t.net > 0).length
    }
    console.log(`  MAKER_OPT     ret=$${round(origRet)} trades=${origTrades} WR=${origTrades > 0 ? round(origWins / origTrades * 100, 1) : 0}%`)
  }
}

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