import { EventEmitter } from "node:events";

import type { OrderBook, OrderBookDelta } from "../../domain/src/types";
import { ShadowBook } from "./shadowBook";

export interface MarketDataAdapterOptions {
  symbols: string[];
  shadowBook: ShadowBook;
  fetchSnapshot: (symbol: string) => Promise<OrderBook>;
  pollIntervalMs?: number;
}

type EventKey = "snapshot" | "delta" | "error";

type TimerMap = Map<string, NodeJS.Timeout>;

export class MarketDataAdapter extends EventEmitter {
  private readonly symbols: string[];
  private readonly shadowBook: ShadowBook;
  private readonly fetchSnapshot: (symbol: string) => Promise<OrderBook>;
  private readonly pollInterval: number;
  private readonly timers: TimerMap = new Map();
  private started = false;

  constructor(options: MarketDataAdapterOptions) {
    super();
    this.symbols = options.symbols;
    this.shadowBook = options.shadowBook;
    this.fetchSnapshot = options.fetchSnapshot;
    this.pollInterval = options.pollIntervalMs ?? 1_000;
  }

  override on<Event extends EventKey>(event: Event, listener: (payload: MarketDataEvents[Event]) => void): this {
    return super.on(event, listener);
  }

  override once<Event extends EventKey>(event: Event, listener: (payload: MarketDataEvents[Event]) => void): this {
    return super.once(event, listener);
  }

  async start(): Promise<void> {
    if (this.started) return;
    this.started = true;
    await Promise.all(this.symbols.map(symbol => this.refreshSymbol(symbol)));
    for (const symbol of this.symbols) {
      const timer = setInterval(() => {
        this.refreshSymbol(symbol).catch(err => {
          this.emit("error", { symbol, error: err });
        });
      }, this.pollInterval);
      this.timers.set(symbol, timer);
    }
  }

  stop(): void {
    if (!this.started) return;
    this.started = false;
    for (const timer of this.timers.values()) {
      clearInterval(timer);
    }
    this.timers.clear();
  }

  ingestSnapshot(symbol: string, snapshot: OrderBook, seq?: number): void {
    this.shadowBook.updateFromSnapshot(symbol, snapshot, seq);
    this.emit("snapshot", { symbol, snapshot });
  }

  ingestDelta(symbol: string, delta: OrderBookDelta): void {
    this.shadowBook.applyIncrement(symbol, delta);
    this.emit("delta", { symbol, delta });
  }

  private async refreshSymbol(symbol: string): Promise<void> {
    try {
      const snapshot = await this.fetchSnapshot(symbol);
      this.ingestSnapshot(symbol, snapshot);
    } catch (error) {
      this.emit("error", { symbol, error });
      throw error;
    }
  }
}

export type MarketDataAdapterEventPayloads = {
  snapshot: { symbol: string; snapshot: OrderBook };
  delta: { symbol: string; delta: OrderBookDelta };
  error: { symbol: string; error: unknown };
};

export interface MarketDataEvents {
  snapshot: MarketDataAdapterEventPayloads["snapshot"];
  delta: MarketDataAdapterEventPayloads["delta"];
  error: MarketDataAdapterEventPayloads["error"];
}
