// GRU Bond Pricing Service
// Base pricing, discounted acquisition, liquidity loop-linked yield

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';
import { gruIndexService } from './gru-index.service';


export type PricingModel = 'base' | 'discounted_acquisition' | 'liquidity_loop_linked';

export interface PricingRequest {
  bondId?: string;
  syntheticBondId?: string;
  pricingModel: PricingModel;
  discountRate?: string;
  indexCodes?: string[]; // For index adjustments (LiXAU, LiPMG, etc.)
}

export interface PricingResult {
  pricingId: string;
  basePrice: Decimal;
  indexAdjustment: Decimal;
  liquidityAdjustment: Decimal;
  riskAdjustment: Decimal;
  finalPrice: Decimal;
  yield?: Decimal;
}

export class BondPricingService {
  /**
   * Calculate bond price
   */
  async calculatePrice(request: PricingRequest): Promise<PricingResult> {
    logger.info('GRU: Calculating bond price', { request });

    let bond;
    if (request.bondId) {
      bond = await prisma.gru_bonds.findUnique({
        where: { bondId: request.bondId },
        include: { gru_units: true },
      });
      if (!bond) {
        throw new Error(`Bond not found: ${request.bondId}`);
      }
    } else if (request.syntheticBondId) {
      const syntheticBond = await prisma.synthetic_gru_bonds.findUnique({
        where: { syntheticBondId: request.syntheticBondId },
      });
      if (!syntheticBond) {
        throw new Error(`Synthetic bond not found: ${request.syntheticBondId}`);
      }
      // Use synthetic bond principal as base
      bond = { principalAmount: syntheticBond.principalAmount, couponRate: new Decimal(0.05) } as any;
    } else {
      throw new Error('Either bondId or syntheticBondId must be provided');
    }

    const pricingId = `PRICE-${uuidv4()}`;
    let basePrice: Decimal;
    let indexAdjustment = new Decimal(0);
    let liquidityAdjustment = new Decimal(0);
    let riskAdjustment = new Decimal(0);
    let bondYield: Decimal | undefined;

    switch (request.pricingModel) {
      case 'base':
        basePrice = await this.calculateBasePrice(bond);
        break;
      case 'discounted_acquisition':
        basePrice = await this.calculateDiscountedAcquisitionPrice(bond);
        break;
      case 'liquidity_loop_linked':
        const result = await this.calculateLiquidityLoopLinkedYield(bond);
        basePrice = result.price;
        bondYield = result.yield;
        break;
      default:
        throw new Error(`Unknown pricing model: ${request.pricingModel}`);
    }

    // Calculate index adjustments
    if (request.indexCodes && request.indexCodes.length > 0) {
      indexAdjustment = await this.calculateIndexAdjustment(bond, request.indexCodes);
    }

    // Calculate liquidity adjustment (simplified)
    liquidityAdjustment = await this.calculateLiquidityAdjustment(bond);

    // Calculate risk adjustment (simplified)
    riskAdjustment = await this.calculateRiskAdjustment(bond);

    const finalPrice = basePrice.plus(indexAdjustment).plus(liquidityAdjustment).minus(riskAdjustment);

    // Save pricing record
    await prisma.gru_bond_pricing.create({
      data: {
        id: uuidv4(),
        pricingId,
        bondId: request.bondId,
        syntheticBondId: request.syntheticBondId,
        pricingModel: request.pricingModel,
        basePrice,
        indexAdjustment,
        liquidityAdjustment,
        riskAdjustment,
        finalPrice,
        yield: bondYield,
        discountRate: request.discountRate ? new Decimal(request.discountRate) : null,
        calculationDetails: JSON.stringify({
          indexCodes: request.indexCodes,
          model: request.pricingModel,
        }),
        calculatedAt: new Date(),
        createdAt: new Date(),
        updatedAt: new Date(),
      },
    });

    logger.info('GRU: Bond price calculated', { pricingId, finalPrice: finalPrice.toString() });

    return {
      pricingId,
      basePrice,
      indexAdjustment,
      liquidityAdjustment,
      riskAdjustment,
      finalPrice,
      yield: bondYield,
    };
  }

  /**
   * Base pricing model: Price = PV(Coupons) + PV(Perpetual Component) + Index Adjustment
   */
  private async calculateBasePrice(bond: any): Promise<Decimal> {
    const principal = bond.principalAmount;
    const couponRate = bond.couponRate || new Decimal(0.05);
    const discountRate = new Decimal(0.05); // Default discount rate

    // PV of Coupons (for perpetual, use annual coupon / discount rate)
    const annualCoupon = principal.times(couponRate);
    const pvCoupons = annualCoupon.div(discountRate);

    // PV of Perpetual Component
    const pvPerpetual = principal.div(discountRate);

    // Base price
    const basePrice = pvCoupons.plus(pvPerpetual);

    return basePrice;
  }

  /**
   * Discounted acquisition model: Acquisition Price = Nominal / 0.15
   */
  private async calculateDiscountedAcquisitionPrice(bond: any): Promise<Decimal> {
    const nominal = bond.principalAmount;
    const acquisitionPrice = nominal.div(new Decimal(0.15));
    return acquisitionPrice;
  }

  /**
   * GRU liquidity loop-linked yield: Yield = f(7→10→9.55 cycles, Index Volatility, Sovereign Risk)
   */
  private async calculateLiquidityLoopLinkedYield(bond: any): Promise<{ price: Decimal; yield: Decimal }> {
    const baseYield = new Decimal(0.05); // Base yield 5%

    // Loop adjustment (simplified - would use actual loop data)
    const loopAdjustment = new Decimal(0.01); // +1% for loop efficiency

    // Index volatility (simplified)
    const volatilityAdjustment = new Decimal(0.005); // +0.5%

    // Sovereign risk (simplified - would use SRI)
    const riskPenalty = new Decimal(0.002); // -0.2%

    const calculatedYield = baseYield.plus(loopAdjustment).plus(volatilityAdjustment).minus(riskPenalty);

    // Calculate price from yield
    const principal = bond.principalAmount;
    const annualCoupon = principal.times(bond.couponRate || new Decimal(0.05));
    const price = annualCoupon.div(calculatedYield);

    return { price, yield: calculatedYield };
  }

  /**
   * Calculate index adjustment
   */
  private async calculateIndexAdjustment(bond: any, indexCodes: string[]): Promise<Decimal> {
    let totalAdjustment = new Decimal(0);

    for (const indexCode of indexCodes) {
      try {
        const index = await prisma.gru_indexes.findUnique({
          where: { indexCode },
        });

        if (index) {
          // Index adjustment = Principal × (IndexValue / BaseIndexValue - 1) × IndexWeight
          const indexWeight = new Decimal(0.1); // Default 10% weight
          const indexRatio = index.currentValue.div(index.baseValue);
          const adjustment = bond.principalAmount.times(indexRatio.minus(1)).times(indexWeight);
          totalAdjustment = totalAdjustment.plus(adjustment);
        }
      } catch (error) {
        logger.warn('GRU: Failed to get index for adjustment', { indexCode, error });
      }
    }

    return totalAdjustment;
  }

  /**
   * Calculate liquidity adjustment
   */
  private async calculateLiquidityAdjustment(bond: any): Promise<Decimal> {
    // Simplified liquidity adjustment
    // In production, would use actual liquidity metrics
    return new Decimal(0);
  }

  /**
   * Calculate risk adjustment
   */
  private async calculateRiskAdjustment(bond: any): Promise<Decimal> {
    // Simplified risk adjustment
    // In production, would use SRI and other risk metrics
    return new Decimal(0);
  }

  /**
   * Get pricing history
   */
  async getPricingHistory(bondId?: string, syntheticBondId?: string, limit: number = 100) {
    return await prisma.bond_pricing_histories.findMany({
      where: {
        bondId: bondId || undefined,
        syntheticBondId: syntheticBondId || undefined,
      },
      orderBy: { timestamp: 'desc' },
      take: limit,
    });
  }

  /**
   * Get latest pricing
   */
  async getLatestPricing(bondId?: string, syntheticBondId?: string) {
    return await prisma.gru_bond_pricing.findFirst({
      where: {
        bondId: bondId || undefined,
        syntheticBondId: syntheticBondId || undefined,
      },
      orderBy: { calculatedAt: 'desc' },
    });
  }
}

export const bondPricingService = new BondPricingService();

