// IBIN Matching Service
// Price-time priority matching engine

import prisma from '@/shared/database/prisma';
import { Decimal } from '@prisma/client/runtime/library';
import { v4 as uuidv4 } from 'uuid';
import { sriCalculatorService } from '@/core/risk/sri/sri-calculator.service';


export interface BondOrderRequest {
  bondId: string;
  orderType: string; // buy, sell
  price: string;
  quantity: string;
  participantBankId: string;
}

export interface MatchingResult {
  tradeId?: string;
  matched: boolean;
  remainingQuantity?: string;
}

export class IbinMatchingService {
  /**
   * Place order
   */
  async placeOrder(request: BondOrderRequest): Promise<{
    orderId: string;
    priority: number;
  }> {
    const orderId = `ORDER-${uuidv4()}`;

    // Calculate priority (price-time priority)
    const priority = await this.calculatePriority(
      request.bondId,
      request.price,
      request.orderType
    );

    // Create order
    const order = await prisma.bondOrderBook.create({
      data: {
        orderId,
        bondId: request.bondId,
        orderType: request.orderType,
        price: new Decimal(request.price),
        quantity: new Decimal(request.quantity),
        participantBankId: request.participantBankId,
        priority,
        status: 'pending',
      },
    });

    // Try to match immediately
    const matchResult = await this.tryMatch(orderId);

    return {
      orderId: order.orderId,
      priority: order.priority,
    };
  }

  /**
   * Calculate priority (price-time priority)
   * Higher priority = better price, earlier time
   */
  private async calculatePriority(
    bondId: string,
    price: string,
    orderType: string
  ): Promise<number> {
    // Get current timestamp as base
    const timestamp = Date.now();

    // For buy orders: higher price = higher priority
    // For sell orders: lower price = higher priority
    const priceDecimal = new Decimal(price);
    const priceFactor = orderType === 'buy' 
      ? priceDecimal.toNumber() 
      : new Decimal(1000000).minus(priceDecimal).toNumber(); // Invert for sell

    // Combine price and time (price weighted more heavily)
    const priority = priceFactor * 1000000 + (999999999 - timestamp % 1000000);

    return Math.floor(priority);
  }

  /**
   * Try to match order
   */
  async tryMatch(orderId: string): Promise<MatchingResult> {
    const order = await prisma.bondOrderBook.findUnique({
      where: { orderId },
    });

    if (!order) {
      throw new Error(`Order not found: ${orderId}`);
    }

    if (order.status !== 'pending') {
      return { matched: false };
    }

    // Find matching orders (opposite type, compatible price)
    const oppositeType = order.orderType === 'buy' ? 'sell' : 'buy';
    
    const matchingOrders = await prisma.bondOrderBook.findMany({
      where: {
        bondId: order.bondId,
        orderType: oppositeType,
        status: 'pending',
        NOT: { orderId: order.orderId },
      },
      orderBy: { priority: 'desc' },
    });

    let remainingQuantity = order.quantity;
    let matched = false;

    for (const matchOrder of matchingOrders) {
      // Check if prices are compatible
      const priceCompatible = order.orderType === 'buy'
        ? order.price.gte(matchOrder.price) // Buy order: must be >= sell price
        : order.price.lte(matchOrder.price); // Sell order: must be <= buy price

      if (!priceCompatible) {
        continue;
      }

      // Calculate trade quantity
      const tradeQuantity = remainingQuantity.lessThan(matchOrder.quantity)
        ? remainingQuantity
        : matchOrder.quantity;

      // Create trade
      const tradeId = await this.createTrade(
        order.bondId,
        order.orderType === 'buy' ? order.participantBankId : matchOrder.participantBankId,
        order.orderType === 'sell' ? order.participantBankId : matchOrder.participantBankId,
        tradeQuantity,
        matchOrder.price // Use the matched order's price
      );

      // Update quantities
      remainingQuantity = remainingQuantity.minus(tradeQuantity);
      const matchRemaining = matchOrder.quantity.minus(tradeQuantity);

      // Update orders
      if (remainingQuantity.isZero()) {
        await prisma.bondOrderBook.update({
          where: { orderId: order.orderId },
          data: { status: 'matched' },
        });
      } else {
        await prisma.bondOrderBook.update({
          where: { orderId: order.orderId },
          data: { quantity: remainingQuantity },
        });
      }

      if (matchRemaining.isZero()) {
        await prisma.bondOrderBook.update({
          where: { orderId: matchOrder.orderId },
          data: { status: 'matched' },
        });
      } else {
        await prisma.bondOrderBook.update({
          where: { orderId: matchOrder.orderId },
          data: { quantity: matchRemaining },
        });
      }

      matched = true;

      if (remainingQuantity.isZero()) {
        break;
      }
    }

    return {
      tradeId: matched ? 'TRADE-' + uuidv4() : undefined,
      matched,
      remainingQuantity: remainingQuantity.isZero() ? undefined : remainingQuantity.toString(),
    };
  }

  /**
   * Create trade
   */
  private async createTrade(
    bondId: string,
    buyerBankId: string,
    sellerBankId: string,
    quantity: Decimal,
    price: Decimal
  ): Promise<string> {
    const tradeId = `TRADE-${uuidv4()}`;
    const tradeAmount = quantity.mul(price);

    await prisma.bondTrade.create({
      data: {
        tradeId,
        bondId,
        buyerBankId,
        sellerBankId,
        quantity,
        price,
        tradeAmount,
        status: 'pending',
      },
    });

    return tradeId;
  }

  /**
   * Get order book for bond
   */
  async getOrderBook(bondId: string): Promise<{
    buyOrders: any[];
    sellOrders: any[];
  }> {
    const orders = await prisma.bondOrderBook.findMany({
      where: {
        bondId,
        status: 'pending',
      },
      orderBy: { priority: 'desc' },
    });

    const buyOrders = orders.filter(o => o.orderType === 'buy');
    const sellOrders = orders.filter(o => o.orderType === 'sell');

    return { buyOrders, sellOrders };
  }

  /**
   * Cancel order
   */
  async cancelOrder(orderId: string): Promise<void> {
    await prisma.bondOrderBook.update({
      where: { orderId },
      data: { status: 'cancelled' },
    });
  }
}

export const ibinMatchingService = new IbinMatchingService();

