/**
 * MomentumEngine — intraday trend-following strategy.
 *
 * Signal: 60-min price momentum > threshold → enter in direction, hold 30 min.
 * Edge: +9–14 bps/trade net of 4bps RT maker fees (t=2.2–3.0, 8-9/10 sessions).
 *
 * Designed for Bybit BTCUSDT perpetual, 100x leverage, maker limit execution.
 *
 * Key insight: BTC trends at 30min–4h horizons. Mean-reversion, absorption,
 * and OFI signals are dead after fees. Only momentum survives.
 */
function round(n: number, d = 2): number { return Math.round(n * 10 ** d) / 10 ** d }

// ── Config ──

export interface MomentumConfig {
  /** Bars to look back for momentum (360 = 60min at 10s bars) */
  lookbackBars: number
  /** Bars to hold position (180 = 30min) */
  holdBars: number
  /** Minimum |momentum| in bps to trigger entry */
  entryThreshBps: number
  /** Catastrophe stop loss in bps (0 = no stop) */
  stopBps: number
  /** One-way fee in bps (maker = 2) */
  entryFeeBps: number
  exitFeeBps: number
  /** Bars between position exit and next entry */
  cooldownBars: number
  /** Starting capital */
  startingCapital: number
  /** Leverage multiplier */
  leverage: number
  /** Risk per trade as fraction of equity */
  riskPct: number
}

export const DEFAULT_CONFIG: MomentumConfig = {
  lookbackBars: 360,       // 60 minutes
  holdBars: 180,           // 30 minutes
  entryThreshBps: 5,       // conservative: any 5bps move triggers
  stopBps: 100,            // wide catastrophe stop only
  entryFeeBps: 2,          // maker
  exitFeeBps: 2,           // maker (target)
  cooldownBars: 183,       // holdBars + 3
  startingCapital: 500,
  leverage: 100,
  riskPct: 0.02,           // 2% risk per trade
}

// ── Position ──

export interface MomentumPosition {
  id: number
  side: 'long' | 'short'
  entryPrice: number
  sizeBtc: number
  entryBar: number
  entryTs: number
  exitDeadlineBar: number
  stopPrice: number
  peakFavBps: number
  /** Pending Bybit order ID for entry fill tracking */
  bybitOrderId?: string
}

// ── Trade record ──

export interface MomentumTrade {
  id: number
  side: 'long' | 'short'
  entryPrice: number
  exitPrice: number
  sizeBtc: number
  grossBps: number
  netBps: number
  grossUsd: number
  netUsd: number
  feesUsd: number
  holdBars: number
  exitReason: 'time' | 'stop' | 'manual'
  entryTs: number
  exitTs: number
}

// ── Engine state ──

export interface MomentumState {
  equity: number
  peak: number
  maxDd: number
  maxDdPct: number
  totalFees: number
  totalPnl: number
  position: MomentumPosition | null
  closed: MomentumTrade[]
  lastExitBar: number
  barIndex: number
  lastMomentumBps: number
  lastSignal: 'long' | 'short' | null
}

// ── Events ──

export interface MomentumEntryRequest {
  type: 'entry'
  side: 'long' | 'short'
  refPrice: number
  sizeBtc: number
  stopPrice: number
  momentumBps: number
}

export interface MomentumExitRequest {
  type: 'exit'
  positionId: number
  side: 'long' | 'short'
  sizeBtc: number
  reason: 'time' | 'stop' | 'manual'
  targetPrice: number
}

export type MomentumEvent = MomentumEntryRequest | MomentumExitRequest

// ── Engine ──

export class MomentumEngine {
  private config: MomentumConfig
  private equity: number
  private peak: number
  private maxDd: number
  private maxDdPct: number
  private totalFees: number
  private totalPnl: number
  private position: MomentumPosition | null = null
  private closed: MomentumTrade[] = []
  private nextId = 1
  private lastExitBar = -999
  private barIdx = 0
  private bars: { c: number; h: number; l: number }[] = []
  private entryPending = false
  private lastMomBps = 0
  private lastSignal: 'long' | 'short' | null = null

  constructor(config: MomentumConfig = DEFAULT_CONFIG) {
    this.config = config
    this.equity = config.startingCapital
    this.peak = config.startingCapital
    this.maxDd = 0
    this.maxDdPct = 0
    this.totalFees = 0
    this.totalPnl = 0
  }

  /** Push a new 10s bar (for live/streaming mode) */
  pushBar(close: number, high: number, low: number): void {
    this.bars.push({ c: close, h: high, l: low })
    this.barIdx = this.bars.length - 1
  }

  /** Set preloaded bars (for replay mode) */
  setBars(bars: { c: number; h: number; l: number }[]): void {
    this.bars = bars
  }

  /** Set current bar index (for replay mode) */
  setBarIndex(idx: number): void {
    this.barIdx = idx
  }

  /** Main tick: check exits then entries. Returns events for caller to fill. */
  tick(mid: number, ts: number): MomentumEvent[] {
    const events: MomentumEvent[] = []
    const i = this.barIdx

    // 1. Check exit on existing position
    if (this.position && !this.entryPending) {
      const pos = this.position
      const pnlBps = pos.side === 'long'
        ? (mid - pos.entryPrice) / pos.entryPrice * 10000
        : (pos.entryPrice - mid) / pos.entryPrice * 10000

      // Track peak favorable
      if (pnlBps > pos.peakFavBps) pos.peakFavBps = pnlBps

      // Time exit
      if (i >= pos.exitDeadlineBar) {
        events.push({ type: 'exit', positionId: pos.id, side: pos.side, sizeBtc: pos.sizeBtc, reason: 'time', targetPrice: mid })
        return events
      }

      // Catastrophe stop
      if (this.config.stopBps > 0 && pnlBps <= -this.config.stopBps) {
        const stopPrice = pos.side === 'long'
          ? pos.entryPrice * (1 - this.config.stopBps / 10000)
          : pos.entryPrice * (1 + this.config.stopBps / 10000)
        events.push({ type: 'exit', positionId: pos.id, side: pos.side, sizeBtc: pos.sizeBtc, reason: 'stop', targetPrice: stopPrice })
        return events
      }

      return events // position open, no exit yet
    }

    // 2. Check for new entry (no position, not pending)
    if (this.position || this.entryPending) return events
    if (i - this.lastExitBar < this.config.cooldownBars) return events
    if (i < this.config.lookbackBars) return events

    // Compute momentum
    const refBar = this.bars[i - this.config.lookbackBars]
    if (!refBar || refBar.c <= 0) return events
    const curPrice = this.bars[i].c
    const momBps = (curPrice - refBar.c) / refBar.c * 10000
    this.lastMomBps = momBps

    if (Math.abs(momBps) < this.config.entryThreshBps) {
      this.lastSignal = null
      return events
    }

    const side: 'long' | 'short' = momBps > 0 ? 'long' : 'short'
    this.lastSignal = side

    // Position sizing
    const riskUsd = this.equity * this.config.riskPct
    const stopDist = (this.config.stopBps > 0 ? this.config.stopBps : 50) / 10000
    const posNotional = Math.min(riskUsd / stopDist, this.equity * this.config.leverage)
    const sizeBtc = round(posNotional / mid, 4)
    if (sizeBtc < 0.001) return events

    // Margin check
    const marginNeeded = mid * sizeBtc / this.config.leverage
    if (marginNeeded > this.equity * 0.95) return events

    // Stop price
    const stopPrice = side === 'long'
      ? round(mid * (1 - this.config.stopBps / 10000), 1)
      : round(mid * (1 + this.config.stopBps / 10000), 1)

    this.entryPending = true
    events.push({ type: 'entry', side, refPrice: mid, sizeBtc, stopPrice, momentumBps: momBps })
    return events
  }

  // ── Fill callbacks ──

  confirmEntry(evt: MomentumEntryRequest, fillPrice: number, fillQty: number, ts: number, bybitOrderId?: string): void {
    const fee = round(fillPrice * fillQty * (this.config.entryFeeBps / 10000))
    this.equity -= fee
    this.totalFees += fee

    const stopPrice = evt.side === 'long'
      ? round(fillPrice * (1 - this.config.stopBps / 10000), 1)
      : round(fillPrice * (1 + this.config.stopBps / 10000), 1)

    this.position = {
      id: this.nextId++,
      side: evt.side,
      entryPrice: fillPrice,
      sizeBtc: fillQty,
      entryBar: this.barIdx,
      entryTs: ts,
      exitDeadlineBar: this.barIdx + this.config.holdBars,
      stopPrice,
      peakFavBps: 0,
      bybitOrderId,
    }
    this.entryPending = false
  }

  rejectEntry(_evt: MomentumEntryRequest, _ts: number): void {
    this.entryPending = false
  }

  confirmExit(evt: MomentumExitRequest, fillPrice: number, wasTaker: boolean, ts: number): void {
    if (!this.position) return
    const pos = this.position
    const exitFeeBps = wasTaker ? 5.5 : this.config.exitFeeBps
    const exitFee = round(fillPrice * pos.sizeBtc * (exitFeeBps / 10000))

    const grossBps = pos.side === 'long'
      ? (fillPrice - pos.entryPrice) / pos.entryPrice * 10000
      : (pos.entryPrice - fillPrice) / pos.entryPrice * 10000

    const grossUsd = round(pos.sizeBtc * pos.entryPrice * (grossBps / 10000))
    const entryFee = round(pos.entryPrice * pos.sizeBtc * (this.config.entryFeeBps / 10000))
    const feesUsd = round(entryFee + exitFee)
    const netUsd = round(grossUsd - exitFee)  // entry fee already deducted
    const netBps = grossBps - (this.config.entryFeeBps + exitFeeBps)

    this.equity += grossUsd - exitFee
    this.totalFees += exitFee
    this.totalPnl += netUsd

    if (this.equity > this.peak) this.peak = this.equity
    const dd = this.peak - this.equity
    if (dd > this.maxDd) {
      this.maxDd = dd
      this.maxDdPct = this.peak > 0 ? dd / this.peak : 0
    }

    this.closed.push({
      id: pos.id,
      side: pos.side,
      entryPrice: pos.entryPrice,
      exitPrice: fillPrice,
      sizeBtc: pos.sizeBtc,
      grossBps: round(grossBps, 2),
      netBps: round(netBps, 2),
      grossUsd,
      netUsd,
      feesUsd,
      holdBars: this.barIdx - pos.entryBar,
      exitReason: evt.reason,
      entryTs: pos.entryTs,
      exitTs: ts,
    })

    this.position = null
    this.lastExitBar = this.barIdx
  }

  getState(): MomentumState {
    return {
      equity: round(this.equity),
      peak: round(this.peak),
      maxDd: round(this.maxDd),
      maxDdPct: round(this.maxDdPct * 100, 2),
      totalFees: round(this.totalFees),
      totalPnl: round(this.totalPnl),
      position: this.position ? { ...this.position } : null,
      closed: [...this.closed],
      lastExitBar: this.lastExitBar,
      barIndex: this.barIdx,
      lastMomentumBps: round(this.lastMomBps, 2),
      lastSignal: this.lastSignal,
    }
  }

  getConfig(): MomentumConfig { return { ...this.config } }
}

// ── Replay helper ──

export function replayMomentum(
  bars: { c: number; h: number; l: number; o: number }[],
  config: MomentumConfig = DEFAULT_CONFIG
): MomentumState {
  const engine = new MomentumEngine(config)
  engine.setBars(bars)

  for (let i = config.lookbackBars; i < bars.length; i++) {
    const ts = i * 10000 // synthetic timestamp
    engine.setBarIndex(i)
    const mid = bars[i].c
    const events = engine.tick(mid, ts)

    for (const evt of events) {
      if (evt.type === 'entry') {
        // Simple fill: assume fill at refPrice (historical replay)
        engine.confirmEntry(evt, evt.refPrice, evt.sizeBtc, ts)
      } else {
        engine.confirmExit(evt, evt.targetPrice, evt.reason === 'stop', ts)
      }
    }
  }

  // Close any remaining position at last price
  const st = engine.getState()
  if (st.position) {
    const lastPrice = bars[bars.length - 1].c
    engine.confirmExit(
      { type: 'exit', positionId: st.position.id, side: st.position.side, sizeBtc: st.position.sizeBtc, reason: 'time', targetPrice: lastPrice },
      lastPrice, false, (bars.length - 1) * 10000
    )
  }

  return engine.getState()
}
