import { EventEmitter } from "node:events";

import type { Side } from "../../domain/src/types";

export interface GlobalOrderSnapshot {
  orderId: string;
  clientOrderId?: string;
  accountId: string;
  symbol: string;
  side: Side;
  price: number;
  timestamp: number;
}

export interface ValidationIntent {
  accountId: string;
  symbol: string;
  side: Side;
  price: number;
}

export interface GlobalCoordinatorOptions {
  stpToleranceBps?: number;
}

type SymbolMap = Map<string, Map<number, GlobalOrderSnapshot[]>>;

export class GlobalOrderCoordinator extends EventEmitter {
  private readonly stpToleranceBps: number;
  private readonly orderIndex = new Map<string, GlobalOrderSnapshot>();
  private readonly books: SymbolMap = new Map();
  private readonly clientIndex = new Map<string, string>();

  constructor(options: GlobalCoordinatorOptions = {}) {
    super();
    this.stpToleranceBps = options.stpToleranceBps ?? 0;
  }

  validate(intent: ValidationIntent): void {
    const conflicts = this.findConflicts(intent);
    if (conflicts.length > 0) {
      this.emit("conflict", { intent, conflicts });
      throw new Error("STP violation: conflicting order detected");
    }
  }

  register(snapshot: GlobalOrderSnapshot): void {
    this.orderIndex.set(snapshot.orderId, snapshot);
    if (snapshot.clientOrderId) {
      this.clientIndex.set(snapshot.clientOrderId, snapshot.orderId);
    }
    const book = getOrCreateMap(
      this.books,
      snapshot.symbol,
      () => new Map<number, GlobalOrderSnapshot[]>()
    );
    const level = getOrCreateArray(book, snapshot.price);
    level.push(snapshot);
    this.emit("registered", snapshot);
  }

  release(orderId: string): void {
    const snapshot = this.orderIndex.get(orderId);
    if (!snapshot) return;
    this.orderIndex.delete(orderId);
    if (snapshot.clientOrderId) {
      this.clientIndex.delete(snapshot.clientOrderId);
    }
    const book = this.books.get(snapshot.symbol);
    if (!book) return;
    const level = book.get(snapshot.price);
    if (!level) return;
    const idx = level.findIndex(item => item.orderId === orderId);
    if (idx >= 0) {
      level.splice(idx, 1);
      if (level.length === 0) {
        book.delete(snapshot.price);
      }
    }
    this.emit("released", snapshot);
  }

  releaseByClientId(clientOrderId: string): void {
    const orderId = this.clientIndex.get(clientOrderId);
    if (!orderId) return;
    this.release(orderId);
  }

  peekByClientId(clientOrderId: string): GlobalOrderSnapshot | undefined {
    const orderId = this.clientIndex.get(clientOrderId);
    if (!orderId) return undefined;
    return this.orderIndex.get(orderId);
  }

  peek(orderId: string): GlobalOrderSnapshot | undefined {
    return this.orderIndex.get(orderId);
  }

  list(): GlobalOrderSnapshot[] {
    return Array.from(this.orderIndex.values());
  }

  private findConflicts(intent: ValidationIntent): GlobalOrderSnapshot[] {
    const book = this.books.get(intent.symbol);
    if (!book) return [];
    const conflictSide: Side = intent.side === "buy" ? "sell" : "buy";
    const priceLevels = Array.from(book.keys());
    const tolerance = this.stpToleranceBps;
    const results: GlobalOrderSnapshot[] = [];

    for (const price of priceLevels) {
      const entries = book.get(price);
      if (!entries) continue;
      for (const entry of entries) {
        if (entry.accountId === intent.accountId) continue;
        if (entry.side !== conflictSide) continue;
        if (isCross(entry.price, intent.price, intent.side, tolerance)) {
          results.push(entry);
        }
      }
    }
    return results;
  }
}

function getOrCreateMap<K, V>(map: Map<K, V>, key: K, factory: () => V): V {
  const existing = map.get(key);
  if (existing) return existing;
  const value = factory();
  map.set(key, value);
  return value;
}

function getOrCreateArray<K, T>(map: Map<K, T[]>, key: K): T[] {
  const existing = map.get(key);
  if (existing) return existing;
  const arr: T[] = [];
  map.set(key, arr);
  return arr;
}

function isCross(levelPrice: number, intentPrice: number, intentSide: Side, toleranceBps: number): boolean {
  if (intentSide === "buy") {
    if (intentPrice < levelPrice) return false;
    return compareWithTolerance(intentPrice, levelPrice, toleranceBps) >= 0;
  }
  if (intentPrice > levelPrice) return false;
  return compareWithTolerance(levelPrice, intentPrice, toleranceBps) >= 0;
}

function compareWithTolerance(value: number, reference: number, toleranceBps: number): number {
  const diff = value - reference;
  if (toleranceBps <= 0) return diff;
  const allowed = (toleranceBps / 10_000) * reference;
  if (Math.abs(diff) <= allowed) return 0;
  return diff;
}
