import { EventEmitter } from 'events'
import axios from 'axios'

export interface OrderBookLevel {
  price: number
  quantity: number
}
export interface OrderBookSnapshot {
  symbol: string
  bids: OrderBookLevel[]
  asks: OrderBookLevel[]
  lastUpdateId: number
  timestamp: number
}
export interface DepthUpdate {
  symbol: string
  firstUpdateId: number
  finalUpdateId: number
  previousFinalUpdateId: number
  bids: [string, string][]
  asks: [string, string][]
  timestamp: number
}

export class AsterOrderBook extends EventEmitter {
  private symbol: string
  private bids: Map<number, number> = new Map()
  private asks: Map<number, number> = new Map()
  private lastUpdateId: number = 0
  private lastFinalUpdateId: number = 0
  private isInitialized: boolean = false
  private pendingUpdates: DepthUpdate[] = []
  private httpBase: string

  constructor(symbol: string, httpBase: string = 'https://fapi.asterdex.com') {
    super()
    this.symbol = symbol.toUpperCase()
    this.httpBase = httpBase
  }

  async initialize(): Promise<void> {
    const snapshot = await this.fetchSnapshot()
    this.bids.clear()
    this.asks.clear()
    this.pendingUpdates = this.pendingUpdates.filter(update => update.finalUpdateId >= snapshot.lastUpdateId)
    this.applySnapshot(snapshot)
    this.processPendingUpdates()
    this.isInitialized = true
  }

  private async fetchSnapshot(): Promise<OrderBookSnapshot> {
    const url = `${this.httpBase}/fapi/v3/depth`
    const response = await axios.get(url, { params: { symbol: this.symbol, limit: 1000 } })
    const data = response.data
    return {
      symbol: this.symbol,
      bids: data.bids.map(([p, q]: [string, string]) => ({ price: parseFloat(p), quantity: parseFloat(q) })),
      asks: data.asks.map(([p, q]: [string, string]) => ({ price: parseFloat(p), quantity: parseFloat(q) })),
      lastUpdateId: data.lastUpdateId,
      timestamp: Date.now(),
    }
  }

  private applySnapshot(snapshot: OrderBookSnapshot): void {
    this.bids.clear()
    this.asks.clear()
    for (const level of snapshot.bids) if (level.quantity > 0) this.bids.set(level.price, level.quantity)
    for (const level of snapshot.asks) if (level.quantity > 0) this.asks.set(level.price, level.quantity)
    this.lastUpdateId = snapshot.lastUpdateId
    this.lastFinalUpdateId = snapshot.lastUpdateId
  }

  updateDepth(update: DepthUpdate): void {
    if (!this.isInitialized) {
      this.pendingUpdates.push(update)
      return
    }
    if (this.lastFinalUpdateId !== 0 && update.previousFinalUpdateId !== this.lastFinalUpdateId) {
      this.handlePacketLoss()
      return
    }
    if (update.finalUpdateId <= this.lastUpdateId) return
    this.applyDepthUpdate(update)
    this.lastFinalUpdateId = update.finalUpdateId
    this.emitUpdate()
  }

  private applyDepthUpdate(update: DepthUpdate): void {
    for (const [priceStr, quantityStr] of update.bids) {
      const price = parseFloat(priceStr)
      const quantity = parseFloat(quantityStr)
      if (quantity === 0) this.bids.delete(price)
      else this.bids.set(price, quantity)
    }
    for (const [priceStr, quantityStr] of update.asks) {
      const price = parseFloat(priceStr)
      const quantity = parseFloat(quantityStr)
      if (quantity === 0) this.asks.delete(price)
      else this.asks.set(price, quantity)
    }
  }

  private async handlePacketLoss(): Promise<void> {
    this.isInitialized = false
    this.pendingUpdates = []
    await this.initialize()
    this.emit('reconnect')
  }
  private processPendingUpdates(): void {
    this.pendingUpdates.sort((a, b) => a.timestamp - b.timestamp)
    for (const u of this.pendingUpdates)
      if (u.finalUpdateId > this.lastUpdateId) {
        this.applyDepthUpdate(u)
        this.lastFinalUpdateId = u.finalUpdateId
      }
    this.pendingUpdates = []
  }
  private emitUpdate(): void {
    const ob: OrderBookSnapshot = {
      symbol: this.symbol,
      bids: Array.from(this.bids.entries())
        .map(([p, q]) => ({ price: p, quantity: q }))
        .sort((a, b) => b.price - a.price),
      asks: Array.from(this.asks.entries())
        .map(([p, q]) => ({ price: p, quantity: q }))
        .sort((a, b) => a.price - b.price),
      lastUpdateId: this.lastFinalUpdateId,
      timestamp: Date.now(),
    }
    this.emit('update', ob)
  }
  getSnapshot(): OrderBookSnapshot {
    return {
      symbol: this.symbol,
      bids: Array.from(this.bids.entries())
        .map(([p, q]) => ({ price: p, quantity: q }))
        .sort((a, b) => b.price - a.price),
      asks: Array.from(this.asks.entries())
        .map(([p, q]) => ({ price: p, quantity: q }))
        .sort((a, b) => a.price - b.price),
      lastUpdateId: this.lastFinalUpdateId,
      timestamp: Date.now(),
    }
  }
  getBestBid(): number | null {
    if (this.bids.size === 0) return null
    return Math.max(...this.bids.keys())
  }
  getBestAsk(): number | null {
    if (this.asks.size === 0) return null
    return Math.min(...this.asks.keys())
  }
  getSpread(): number | null {
    const bb = this.getBestBid()
    const ba = this.getBestAsk()
    if (bb === null || ba === null) return null
    return ba - bb
  }
  getMidPrice(): number | null {
    const bb = this.getBestBid()
    const ba = this.getBestAsk()
    if (bb === null || ba === null) return null
    return (bb + ba) / 2
  }
  getQuantity(price: number, side: 'bid' | 'ask'): number {
    const map = side === 'bid' ? this.bids : this.asks
    return map.get(price) || 0
  }
  getTotalQuantity(depth: number, side: 'bid' | 'ask'): number {
    const map = side === 'bid' ? this.bids : this.asks
    const prices = Array.from(map.keys()).sort(side === 'bid' ? (a, b) => b - a : (a, b) => a - b)
    let total = 0
    for (let i = 0; i < Math.min(depth, prices.length); i++) total += map.get(prices[i]) || 0
    return total
  }
}
