// Synthetic GRU Bond Instruments Service
// sGRU-BND, sGRU-ETF, sGRU-FWD, sGRU-SWAP

import prisma from '@/shared/database/prisma';
import { Decimal } from '@prisma/client/runtime/library';
import { v4 as uuidv4 } from 'uuid';
import { logger } from '@/infrastructure/monitoring/logger';


export type SyntheticBondType = 'sGRU-BND' | 'sGRU-ETF' | 'sGRU-FWD' | 'sGRU-SWAP';

export interface SyntheticBondRequest {
  instrumentType: SyntheticBondType;
  bondId?: string; // Reference to underlying GRU bond
  underlyingBonds?: string[]; // Array of bond IDs for basket/ETF
  principalAmount: string;
  sovereignBankId: string;
  issuerId?: string;
  maturityDate?: Date;
  settlementDate?: Date; // For forward contracts
  forwardPrice?: string; // For FWD
  swapRate?: string; // For SWAP
}

export interface SyntheticBondResult {
  syntheticBondId: string;
  instrumentType: string;
  principalAmount: Decimal;
  currentPrice?: Decimal;
  nav?: Decimal;
  forwardPrice?: Decimal;
  swapRate?: Decimal;
  status: string;
}

export class SyntheticBondsService {
  /**
   * Issue synthetic GRU bond instrument
   */
  async issueSyntheticBond(request: SyntheticBondRequest): Promise<SyntheticBondResult> {
    logger.info('GRU: Issuing synthetic bond instrument', { request });

    const syntheticBondId = `SGRU-${request.instrumentType}-${uuidv4()}`;

    // Validate underlying bonds if provided
    if (request.underlyingBonds && request.underlyingBonds.length > 0) {
      for (const bondId of request.underlyingBonds) {
        const bond = await prisma.gru_bonds.findUnique({
          where: { bondId },
        });
        if (!bond) {
          throw new Error(`Underlying bond not found: ${bondId}`);
        }
      }
    }

    // Validate bond reference if provided
    if (request.bondId) {
      const bond = await prisma.gru_bonds.findUnique({
        where: { bondId: request.bondId },
      });
      if (!bond) {
        throw new Error(`Bond not found: ${request.bondId}`);
      }
    }

    // Calculate initial price/NAV based on instrument type
    let currentPrice: Decimal | undefined;
    let nav: Decimal | undefined;
    let forwardPrice: Decimal | undefined;
    let swapRate: Decimal | undefined;

    switch (request.instrumentType) {
      case 'sGRU-BND':
        // Basket bond: calculate weighted average
        currentPrice = await this.calculateBasketPrice(request.underlyingBonds || []);
        break;
      case 'sGRU-ETF':
        // ETF: calculate NAV
        nav = new Decimal(request.principalAmount);
        currentPrice = nav;
        break;
      case 'sGRU-FWD':
        // Forward: use provided forward price or calculate
        forwardPrice = request.forwardPrice
          ? new Decimal(request.forwardPrice)
          : await this.calculateForwardPrice(request.bondId, request.settlementDate);
        currentPrice = forwardPrice;
        break;
      case 'sGRU-SWAP':
        // Swap: use provided swap rate or default
        swapRate = request.swapRate ? new Decimal(request.swapRate) : new Decimal(0.05);
        break;
    }

    const syntheticBond = await prisma.synthetic_gru_bonds.create({
      data: {
        syntheticBondId,
        instrumentType: request.instrumentType,
        bondId: request.bondId,
        underlyingBonds: request.underlyingBonds ? JSON.stringify(request.underlyingBonds) : null,
        principalAmount: new Decimal(request.principalAmount),
        currentPrice,
        nav,
        forwardPrice,
        swapRate,
        sovereignBankId: request.sovereignBankId,
        issuerId: request.issuerId,
        maturityDate: request.maturityDate,
        settlementDate: request.settlementDate,
        status: 'active',
        issuedAt: new Date(),
      },
    });

    logger.info('GRU: Synthetic bond issued', { syntheticBondId, instrumentType: request.instrumentType });

    return {
      syntheticBondId,
      instrumentType: request.instrumentType,
      principalAmount: syntheticBond.principalAmount,
      currentPrice: syntheticBond.currentPrice,
      nav: syntheticBond.nav,
      forwardPrice: syntheticBond.forwardPrice,
      swapRate: syntheticBond.swapRate,
      status: syntheticBond.status,
    };
  }

  /**
   * Calculate basket bond price (weighted average)
   */
  private async calculateBasketPrice(bondIds: string[]): Promise<Decimal> {
    if (bondIds.length === 0) {
      return new Decimal(0);
    }

    const bonds = await prisma.gru_bonds.findMany({
      where: { bondId: { in: bondIds } },
    });

    if (bonds.length === 0) {
      return new Decimal(0);
    }

    // Calculate weighted average (equal weights for now)
    const weight = new Decimal(1).div(bonds.length);
    let totalPrice = new Decimal(0);

    for (const bond of bonds) {
      // Use principal amount as price proxy (in production, would use market price)
      totalPrice = totalPrice.plus(bond.principalAmount.times(weight));
    }

    return totalPrice;
  }

  /**
   * Calculate forward price
   */
  private async calculateForwardPrice(bondId: string | undefined, settlementDate: Date | undefined): Promise<Decimal> {
    if (!bondId || !settlementDate) {
      return new Decimal(0);
    }

    const bond = await prisma.gru_bonds.findUnique({
      where: { bondId },
    });

    if (!bond) {
      return new Decimal(0);
    }

    // Simple forward pricing: Spot * e^(r * t) - PV(Coupons)
    // For now, use principal as spot price
    const spotPrice = bond.principalAmount;
    const timeToSettlement = (settlementDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24 * 365.25);
    const rate = bond.interestRate;
    const forwardPrice = spotPrice.times(new Decimal(Math.exp(Number(rate) * timeToSettlement)));

    return forwardPrice;
  }

  /**
   * Get synthetic bond by ID
   */
  async getSyntheticBond(syntheticBondId: string) {
    return await prisma.synthetic_gru_bonds.findUnique({
      where: { syntheticBondId },
      include: {
        bond: true,
        settlements: true,
        pricingHistory: {
          take: 10,
          orderBy: { timestamp: 'desc' },
        },
        riskAssessments: {
          take: 1,
          orderBy: { assessedAt: 'desc' },
        },
      },
    });
  }

  /**
   * Get synthetic bonds for a bank
   */
  async getSyntheticBondsForBank(sovereignBankId: string) {
    return await prisma.synthetic_gru_bonds.findMany({
      where: { sovereignBankId },
      orderBy: { issuedAt: 'desc' },
      include: {
        bond: true,
      },
    });
  }

  /**
   * Update synthetic bond price/NAV
   */
  async updatePrice(syntheticBondId: string, price: string, nav?: string): Promise<void> {
    await prisma.synthetic_gru_bonds.update({
      where: { syntheticBondId },
      data: {
        currentPrice: new Decimal(price),
        nav: nav ? new Decimal(nav) : undefined,
        updatedAt: new Date(),
      },
    });

    // Record pricing history
    await prisma.bond_pricing_histories.create({
      data: {
        historyId: `BPH-${uuidv4()}`,
        syntheticBondId,
        price: new Decimal(price),
        timestamp: new Date(),
      },
    });

    logger.info('GRU: Synthetic bond price updated', { syntheticBondId, price });
  }

  /**
   * Settle synthetic bond (for forward contracts)
   */
  async settleSyntheticBond(syntheticBondId: string): Promise<string> {
    const syntheticBond = await prisma.synthetic_gru_bonds.findUnique({
      where: { syntheticBondId },
    });

    if (!syntheticBond || syntheticBond.status !== 'active') {
      throw new Error(`Synthetic bond not found or not active: ${syntheticBondId}`);
    }

    if (syntheticBond.instrumentType !== 'sGRU-FWD') {
      throw new Error('Only forward contracts can be settled');
    }

    if (!syntheticBond.settlementDate || new Date() < syntheticBond.settlementDate) {
      throw new Error('Settlement date not reached');
    }

    await prisma.synthetic_gru_bonds.update({
      where: { syntheticBondId },
      data: {
        status: 'settled',
        updatedAt: new Date(),
      },
    });

    logger.info('GRU: Synthetic bond settled', { syntheticBondId });

    return syntheticBondId;
  }

  /**
   * Close synthetic bond
   */
  async closeSyntheticBond(syntheticBondId: string): Promise<void> {
    await prisma.synthetic_gru_bonds.update({
      where: { syntheticBondId },
      data: {
        status: 'closed',
        updatedAt: new Date(),
      },
    });

    logger.info('GRU: Synthetic bond closed', { syntheticBondId });
  }
}

export const syntheticBondsService = new SyntheticBondsService();

