import fs from 'node:fs'
import readline from 'node:readline'

import { computeSignal, shouldEnter } from '../core/strategy.js'
import type { Bar10s } from '../core/strategy.js'

interface ResearchBar extends Bar10s {
  ts: number
  exBuyVol: Record<string, number>
  exSellVol: Record<string, number>
}

interface OB { ts: number; mid: number; bid: number; ask: number }

function sigMark(t: number): string {
  return Math.abs(t) > 2.58 ? 'SIG**' : Math.abs(t) > 1.96 ? 'SIG*' : ''
}

async function loadAllSessions(): Promise<{ bars: ResearchBar[]; obs: OB[] }> {
  const dirs = fs.readdirSync('data/sessions').map(d => 'data/sessions/' + d)
    .filter(d => { try { return fs.statSync(d + '/trades.jsonl').size > 10000 } catch { return false } }).sort()

  const barsMap = new Map<number, ResearchBar>()
  for (const dir of dirs) {
    process.stderr.write(`Loading ${dir}...\n`)
    const rl = readline.createInterface({ input: fs.createReadStream(dir + '/trades.jsonl') })
    for await (const line of rl) {
      const d = JSON.parse(line)
      const k = Math.floor(d.ts / 10000) // 10-second bars
      const p = d.data.price, ex = d.data.exchange, side = d.data.side, vol = d.data.notionalUsd
      const e = barsMap.get(k)
      if (!e) {
        barsMap.set(k, { ts: k * 10000, o: p, h: p, l: p, c: p, buyVol: side === 'buy' ? vol : 0, sellVol: side === 'sell' ? vol : 0, n: 1, exVol: { [ex]: vol }, exBuyVol: { [ex]: side === 'buy' ? vol : 0 }, exSellVol: { [ex]: side === 'sell' ? vol : 0 } })
      } else {
        e.h = Math.max(e.h, p); e.l = Math.min(e.l, p); e.c = p
        if (side === 'buy') e.buyVol += vol; else e.sellVol += vol
        e.n++; e.exVol[ex] = (e.exVol[ex] || 0) + vol
        e.exBuyVol[ex] = (e.exBuyVol[ex] || 0) + (side === 'buy' ? vol : 0)
        e.exSellVol[ex] = (e.exSellVol[ex] || 0) + (side === 'sell' ? vol : 0)
      }
    }
  }

  const obs: OB[] = []
  for (const dir of dirs) {
    const rl = readline.createInterface({ input: fs.createReadStream(dir + '/orderbook.jsonl') })
    for await (const line of rl) {
      const d = JSON.parse(line)
      if (d.data.midPrice) obs.push({ ts: d.ts, mid: d.data.midPrice, bid: d.data.bestBid, ask: d.data.bestAsk })
    }
  }

  const bars = [...barsMap.values()].sort((a, b) => a.ts - b.ts)
  obs.sort((a, b) => a.ts - b.ts)
  process.stderr.write(`Loaded: ${bars.length} bars, ${obs.length} OB snapshots\n\n`)
  return { bars, obs }
}

function returns(bars: ResearchBar[], lag: number): number[] {
  const r: number[] = []
  for (let i = lag; i < bars.length; i++) {
    if (bars[i - lag].c > 0) r.push((bars[i].c - bars[i - lag].c) / bars[i - lag].c * 10000)
  }
  return r
}

function autocorrelation(x: number[], lag: number): number {
  const n = x.length
  const mean = x.reduce((a, b) => a + b, 0) / n
  let num = 0, den = 0
  for (let i = 0; i < n; i++) den += (x[i] - mean) ** 2
  for (let i = lag; i < n; i++) num += (x[i] - mean) * (x[i - lag] - mean)
  return den > 0 ? num / den : 0
}

function tTest(values: number[]): { mean: number; se: number; t: number; n: number } {
  const n = values.length
  const mean = values.reduce((a, b) => a + b, 0) / n
  const variance = values.reduce((s, v) => s + (v - mean) ** 2, 0) / (n - 1)
  const se = Math.sqrt(variance / n)
  return { mean, se, t: se > 0 ? mean / se : 0, n }
}

async function main() {
  const { bars, obs } = await loadAllSessions()

  console.log('=== txocap edge search ===')
  console.log('Dataset: all recorded sessions, 10-second multi-venue bars, Bybit OB snapshots')
  console.log('Purpose: exploratory stats for alpha discovery and falsification; not part of runtime execution')
  console.log()

  // ============================
  // 1. SHORT-HORIZON RETURN AUTOCORRELATION
  // ============================
  console.log('=== 1. SHORT-HORIZON RETURN AUTOCORRELATION ===')
  console.log('Interpretation: positive = continuation, negative = mean-reversion.')
  console.log()
  for (const lag of [1, 2, 3, 6, 12, 30, 60]) {
    const r = returns(bars, 1)
    const ac = autocorrelation(r, lag)
    const horizon = lag * 10
    const significant = Math.abs(ac) > 2 / Math.sqrt(r.length) ? 'SIG*' : ''
    console.log(`  lag=${lag} (${horizon}s): AC=${ac.toFixed(4)} ${significant}`)
  }
  console.log(`  (critical value at 95%: ±${(2 / Math.sqrt(returns(bars, 1).length)).toFixed(4)})`)

  // ============================
  // 2. VOLUME-PRICE IMPACT ASYMMETRY
  // ============================
  console.log()
  console.log('=== 2. VOLUME-PRICE IMPACT ASYMMETRY ===')
  console.log('Question: does buy-side flow move price more than sell-side flow per dollar?')
  console.log()

  const buyImpact: number[] = [], sellImpact: number[] = []
  for (let i = 1; i < bars.length; i++) {
    const ret = (bars[i].c - bars[i - 1].c) / bars[i - 1].c * 10000
    const bv = bars[i].buyVol, sv = bars[i].sellVol
    if (bv > sv * 1.5 && bv > 50000) buyImpact.push(ret)
    if (sv > bv * 1.5 && sv > 50000) sellImpact.push(ret)
  }
  const bt = tTest(buyImpact), st = tTest(sellImpact)
  console.log(`  buy-dominated bars (${bt.n}): avg move=${bt.mean.toFixed(3)}bps t=${bt.t.toFixed(2)} ${sigMark(bt.t)}`)
  console.log(`  sell-dominated bars (${st.n}): avg move=${st.mean.toFixed(3)}bps t=${st.t.toFixed(2)} ${sigMark(st.t)}`)
  console.log(`  asymmetry: ${Math.abs(bt.mean) > Math.abs(st.mean) ? 'buys impact more' : 'sells impact more'}`)

  // ============================
  // 3. DELTA CONTINUATION DECAY
  // ============================
  console.log()
  console.log('=== 3. DELTA CONTINUATION DECAY ===')
  console.log('How long does strong one-bar delta predict future returns?')
  console.log()

  for (const fwd of [1, 2, 3, 6, 12, 30]) {
    const fwdReturns: number[] = []
    for (let i = 0; i < bars.length - fwd; i++) {
      const vol = bars[i].buyVol + bars[i].sellVol
      if (vol < 30000) continue
      const delta = (bars[i].buyVol - bars[i].sellVol) / vol
      if (Math.abs(delta) < 0.3) continue
      const futRet = (bars[i + fwd].c - bars[i].c) / bars[i].c * 10000
      fwdReturns.push(delta > 0 ? futRet : -futRet) // normalize: positive = delta-aligned
    }
    const tt = tTest(fwdReturns)
    console.log(`  fwd=${fwd} (${fwd * 10}s): mean=${tt.mean.toFixed(3)}bps t=${tt.t.toFixed(2)} n=${tt.n} ${sigMark(tt.t)}`)
  }

  // ============================
  // 4. CROSS-VENUE LEAD/LAG
  // ============================
  console.log()
  console.log('=== 4. CROSS-VENUE LEAD/LAG ===')
  console.log('Does one venue delta predict the next bar return of the combined market?')
  console.log()

  const exchanges = ['BINANCE', 'BYBIT', 'OKX', 'COINBASE']
  for (const leader of exchanges) {
    const predReturns: number[] = []
    for (let i = 0; i < bars.length - 1; i++) {
      const lBuy = bars[i].exBuyVol[leader] || 0
      const lSell = bars[i].exSellVol[leader] || 0
      const lVol = lBuy + lSell
      if (lVol < 20000) continue
      const lDelta = (lBuy - lSell) / lVol
      if (Math.abs(lDelta) < 0.4) continue
      // Next bar return from ALL exchanges combined
      const futRet = (bars[i + 1].c - bars[i].c) / bars[i].c * 10000
      predReturns.push(lDelta > 0 ? futRet : -futRet)
    }
    const tt = tTest(predReturns)
    console.log(`  ${leader.padEnd(12)} -> next bar: mean=${tt.mean.toFixed(3)}bps t=${tt.t.toFixed(2)} n=${tt.n} ${sigMark(tt.t)}`)
  }

  // ============================
  // 5. VOLATILITY CLUSTERING
  // ============================
  console.log()
  console.log('=== 5. VOLATILITY CLUSTERING ===')
  console.log('Does high volatility persist? This is the statistical basis for regime gating.')
  console.log()

  const absRets = returns(bars, 1).map(Math.abs)
  for (const lag of [1, 3, 6, 12]) {
    const ac = autocorrelation(absRets, lag)
    console.log(`  |ret| AC lag=${lag} (${lag * 10}s): ${ac.toFixed(4)} ${Math.abs(ac) > 2 / Math.sqrt(absRets.length) ? 'SIG*' : ''}`)
  }

  // ============================
  // 6. LARGE-BAR FOLLOW-THROUGH VS ABSORPTION
  // ============================
  console.log()
  console.log('=== 6. LARGE-BAR FOLLOW-THROUGH VS ABSORPTION ===')
  console.log('After a large volume bar, do delta/price alignment and divergence matter?')
  console.log()

  const avgVol = bars.reduce((s, b) => s + b.buyVol + b.sellVol, 0) / bars.length
  for (const fwd of [1, 3, 6, 12]) {
    const aligned: number[] = [], opposed: number[] = []
    for (let i = 20; i < bars.length - fwd; i++) {
      const vol = bars[i].buyVol + bars[i].sellVol
      if (vol < avgVol * 5) continue
      const delta = vol > 0 ? (bars[i].buyVol - bars[i].sellVol) / vol : 0
      if (Math.abs(delta) < 0.3) continue
      const move = (bars[i].c - bars[i].o) / bars[i].o * 10000
      const futRet = (bars[i + fwd].c - bars[i].c) / bars[i].c * 10000
      const deltaAligned = delta > 0 ? futRet : -futRet
      if (delta * move >= 0) aligned.push(deltaAligned)
      else opposed.push(deltaAligned)
    }
    const at = tTest(aligned), ot = tTest(opposed)
    console.log(`  fwd=${fwd} (${fwd * 10}s):`)
    console.log(`    momentum   mean=${at.mean.toFixed(3)}bps t=${at.t.toFixed(2)} n=${at.n} ${sigMark(at.t)}`)
    console.log(`    absorption mean=${ot.mean.toFixed(3)}bps t=${ot.t.toFixed(2)} n=${ot.n} ${sigMark(ot.t)}`)
  }

  // ============================
  // 7. CURRENT ENGINE SIGNAL EDGE (shouldEnter)
  // ============================
  console.log()
  console.log('=== 7. CURRENT ENGINE SIGNAL EDGE ===')
  console.log('This section evaluates the actual directional signal produced by computeSignal() + shouldEnter().')
  console.log()

  for (const fwd of [1, 3, 6, 12, 30]) {
    const engineReturns: number[] = []
    let absCount = 0, momCount = 0
    for (let i = 60; i < bars.length - fwd; i++) {
      const sig = computeSignal(bars, i, 1.2)
      if (!sig) continue
      const dir = shouldEnter(sig)
      if (!dir) continue
      if (sig.absorption) absCount++
      else momCount++
      const futRet = (bars[i + fwd].c - bars[i].c) / bars[i].c * 10000
      engineReturns.push(dir === 'long' ? futRet : -futRet)
    }
    const tt = tTest(engineReturns)
    console.log(`  fwd=${fwd} (${fwd * 10}s): mean=${tt.mean.toFixed(3)}bps t=${tt.t.toFixed(2)} n=${tt.n} ${sigMark(tt.t)} | abs=${absCount} mom=${momCount}`)
  }

  // ============================
  // 8. ORDERBOOK SKEW → FUTURE PRICE
  // ============================
  console.log()
  console.log('=== 8. ORDERBOOK SKEW → FUTURE PRICE ===')
  console.log('Exploratory only: simple microstructure skew test on recorded Bybit OB snapshots.')
  console.log()

  // Sample OB every 60s, check 60s later
  const step = 240 // ~60s at 250ms
  for (const fwdSteps of [240, 480, 1200]) {
    const skewReturns: number[] = []
    for (let i = 0; i + fwdSteps < obs.length; i += step) {
      const spread = obs[i].ask - obs[i].bid
      if (spread <= 0) continue
      const futMid = obs[i + fwdSteps].mid
      const ret = (futMid - obs[i].mid) / obs[i].mid * 10000
      const bidWeight = obs[i].ask - obs[i].mid
      const askWeight = obs[i].mid - obs[i].bid
      const imbalance = (bidWeight - askWeight) / spread // positive = closer to bid (sell pressure)
      if (Math.abs(imbalance) < 0.1) continue
      skewReturns.push(imbalance > 0 ? -ret : ret) // if closer to bid, expect down
    }
    const tt = tTest(skewReturns)
    const horizon = Math.round(fwdSteps * 0.25)
    console.log(`  fwd=${horizon}s: mean=${tt.mean.toFixed(3)}bps t=${tt.t.toFixed(2)} n=${tt.n} ${sigMark(tt.t)}`)
  }
}

main().catch(e => { console.error(e); process.exit(1) })
