// GRU Yield Curve Service
// Three yield curves: Sovereign, Synthetic, Bond-Implied

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 { gruBondsService } from './gru-bonds.service';


export type YieldCurveType = 'sovereign' | 'synthetic' | 'bond_implied';

export interface YieldCurvePoint {
  maturityMonths: number;
  yield: Decimal;
  discountFactor?: Decimal;
  forwardRate?: Decimal;
}

export interface YieldCurveData {
  curveId: string;
  curveType: string;
  curveName: string;
  effectiveDate: Date;
  maturityPoints: YieldCurvePoint[];
  interpolationMethod: string;
}

export class GruYieldCurveService {
  /**
   * Create or update yield curve
   */
  async createYieldCurve(
    curveType: YieldCurveType,
    maturityPoints: YieldCurvePoint[]
  ): Promise<string> {
    logger.info('GRU Yield Curve: Creating yield curve', { curveType, maturityPoints });

    // Deactivate existing curve of same type
    await prisma.gru_yield_curves.updateMany({
      where: {
        curveType,
        status: 'active',
      },
      data: {
        status: 'historical',
      },
    });

    // Calculate discount factors and forward rates
    const enrichedPoints = this.enrichMaturityPoints(maturityPoints);

    // Create curve
    const curveId = `GRU-CURVE-${uuidv4()}`;
    const curveName = this.getCurveName(curveType);

    const curve = await prisma.gru_yield_curves.create({
      data: {
        curveId,
        curveType,
        curveName,
        effectiveDate: new Date(),
        maturityPoints: maturityPoints.map((p) => ({
          maturityMonths: p.maturityMonths,
          yield: p.yield.toString(),
        })),
        interpolationMethod: 'linear',
        status: 'active',
        points: {
          create: enrichedPoints.map((p) => ({
            pointId: `GRU-POINT-${uuidv4()}`,
            maturityMonths: p.maturityMonths,
            yield: p.yield,
            discountFactor: p.discountFactor || null,
            forwardRate: p.forwardRate || null,
          })),
        },
      },
    });

    logger.info('GRU Yield Curve: Curve created', { curveId, curveType });

    return curveId;
  }

  /**
   * Get current yield curve
   */
  async getCurrentYieldCurve(curveType: YieldCurveType): Promise<YieldCurveData | null> {
    const curve = await prisma.gru_yield_curves.findFirst({
      where: {
        curveType,
        status: 'active',
      },
      include: {
        points: {
          orderBy: { maturityMonths: 'asc' },
        },
      },
      orderBy: { effectiveDate: 'desc' },
    });

    if (!curve) {
      return null;
    }

    return {
      curveId: curve.curveId,
      curveType: curve.curveType,
      curveName: curve.curveName,
      effectiveDate: curve.effectiveDate,
      maturityPoints: curve.points.map((p) => ({
        maturityMonths: p.maturityMonths,
        yield: p.yield,
        discountFactor: p.discountFactor || undefined,
        forwardRate: p.forwardRate || undefined,
      })),
      interpolationMethod: curve.interpolationMethod,
    };
  }

  /**
   * Calculate GRU Sovereign Yield Curve
   * Based on DBIS + SCB reserves
   */
  async calculateSovereignYieldCurve(): Promise<YieldCurvePoint[]> {
    // In production, this would fetch from DBIS reserves and SCB data
    // For now, use default sovereign curve
    const defaultCurve: YieldCurvePoint[] = [
      { maturityMonths: 1, yield: new Decimal(0.02) }, // 2%
      { maturityMonths: 3, yield: new Decimal(0.025) }, // 2.5%
      { maturityMonths: 6, yield: new Decimal(0.03) }, // 3%
      { maturityMonths: 12, yield: new Decimal(0.035) }, // 3.5%
      { maturityMonths: 24, yield: new Decimal(0.04) }, // 4%
      { maturityMonths: 36, yield: new Decimal(0.042) }, // 4.2%
      { maturityMonths: 60, yield: new Decimal(0.045) }, // 4.5%
      { maturityMonths: 120, yield: new Decimal(0.048) }, // 4.8%
      { maturityMonths: 240, yield: new Decimal(0.05) }, // 5%
      { maturityMonths: 360, yield: new Decimal(0.052) }, // 5.2%
    ];

    return defaultCurve;
  }

  /**
   * Calculate GRU Synthetic Yield Curve
   * Based on derivatives market prices
   */
  async calculateSyntheticYieldCurve(): Promise<YieldCurvePoint[]> {
    // In production, this would calculate from derivatives prices
    // For now, use synthetic curve (typically higher than sovereign)
    const syntheticCurve: YieldCurvePoint[] = [
      { maturityMonths: 1, yield: new Decimal(0.025) }, // 2.5%
      { maturityMonths: 3, yield: new Decimal(0.03) }, // 3%
      { maturityMonths: 6, yield: new Decimal(0.035) }, // 3.5%
      { maturityMonths: 12, yield: new Decimal(0.04) }, // 4%
      { maturityMonths: 24, yield: new Decimal(0.045) }, // 4.5%
      { maturityMonths: 36, yield: new Decimal(0.047) }, // 4.7%
      { maturityMonths: 60, yield: new Decimal(0.05) }, // 5%
      { maturityMonths: 120, yield: new Decimal(0.053) }, // 5.3%
      { maturityMonths: 240, yield: new Decimal(0.055) }, // 5.5%
      { maturityMonths: 360, yield: new Decimal(0.057) }, // 5.7%
    ];

    return syntheticCurve;
  }

  /**
   * Calculate GRU Bond-Implied Yield Curve
   * Based on Li99Pp instruments
   */
  async calculateBondImpliedYieldCurve(): Promise<YieldCurvePoint[]> {
    // In production, would fetch bond prices and calculate yields
    // For now, use bond-implied curve (typically between sovereign and synthetic)
    const bondImpliedCurve: YieldCurvePoint[] = [
      { maturityMonths: 1, yield: new Decimal(0.022) }, // 2.2%
      { maturityMonths: 3, yield: new Decimal(0.027) }, // 2.7%
      { maturityMonths: 6, yield: new Decimal(0.032) }, // 3.2%
      { maturityMonths: 12, yield: new Decimal(0.037) }, // 3.7%
      { maturityMonths: 24, yield: new Decimal(0.042) }, // 4.2%
      { maturityMonths: 36, yield: new Decimal(0.044) }, // 4.4%
      { maturityMonths: 60, yield: new Decimal(0.047) }, // 4.7%
      { maturityMonths: 120, yield: new Decimal(0.050) }, // 5%
      { maturityMonths: 240, yield: new Decimal(0.052) }, // 5.2%
      { maturityMonths: 360, yield: new Decimal(0.054) }, // 5.4%
    ];

    return bondImpliedCurve;
  }

  /**
   * Update all yield curves
   */
  async updateAllYieldCurves(): Promise<void> {
    logger.info('GRU Yield Curve: Updating all yield curves');

    // Calculate and update sovereign curve
    const sovereignPoints = await this.calculateSovereignYieldCurve();
    await this.createYieldCurve('sovereign', sovereignPoints);

    // Calculate and update synthetic curve
    const syntheticPoints = await this.calculateSyntheticYieldCurve();
    await this.createYieldCurve('synthetic', syntheticPoints);

    // Calculate and update bond-implied curve
    const bondImpliedPoints = await this.calculateBondImpliedYieldCurve();
    await this.createYieldCurve('bond_implied', bondImpliedPoints);

    logger.info('GRU Yield Curve: All curves updated');
  }

  /**
   * Get yield for specific maturity
   * Uses interpolation if exact maturity not found
   */
  async getYieldForMaturity(
    curveType: YieldCurveType,
    maturityMonths: number
  ): Promise<Decimal> {
    const curve = await this.getCurrentYieldCurve(curveType);
    if (!curve) {
      throw new Error(`Yield curve not found: ${curveType}`);
    }

    // Find exact match
    const exactMatch = curve.maturityPoints.find(
      (p) => p.maturityMonths === maturityMonths
    );
    if (exactMatch) {
      return exactMatch.yield;
    }

    // Interpolate
    return this.interpolateYield(curve.maturityPoints, maturityMonths);
  }

  /**
   * Interpolate yield for given maturity
   */
  private interpolateYield(
    points: YieldCurvePoint[],
    maturityMonths: number
  ): Decimal {
    if (points.length === 0) {
      throw new Error('No yield curve points available');
    }

    // Sort by maturity
    const sorted = [...points].sort((a, b) => a.maturityMonths - b.maturityMonths);

    // If before first point, use first yield
    if (maturityMonths <= sorted[0].maturityMonths) {
      return sorted[0].yield;
    }

    // If after last point, use last yield
    if (maturityMonths >= sorted[sorted.length - 1].maturityMonths) {
      return sorted[sorted.length - 1].yield;
    }

    // Linear interpolation
    for (let i = 0; i < sorted.length - 1; i++) {
      const p1 = sorted[i];
      const p2 = sorted[i + 1];

      if (maturityMonths >= p1.maturityMonths && maturityMonths <= p2.maturityMonths) {
        const ratio = new Decimal(maturityMonths - p1.maturityMonths).dividedBy(
          p2.maturityMonths - p1.maturityMonths
        );
        const interpolatedYield = p1.yield
          .plus(p2.yield.minus(p1.yield).times(ratio));
        return interpolatedYield;
      }
    }

    // Should not reach here
    return sorted[sorted.length - 1].yield;
  }

  /**
   * Enrich maturity points with discount factors and forward rates
   */
  private enrichMaturityPoints(
    points: YieldCurvePoint[]
  ): YieldCurvePoint[] {
    const sorted = [...points].sort((a, b) => a.maturityMonths - b.maturityMonths);
    const enriched: YieldCurvePoint[] = [];

    for (let i = 0; i < sorted.length; i++) {
      const point = sorted[i];
      const maturityYears = point.maturityMonths / 12;

      // Calculate discount factor: DF = e^(-yield * maturity)
      const discountFactor = Math.exp(-point.yield.toNumber() * maturityYears);
      const df = new Decimal(discountFactor);

      // Calculate forward rate (from previous point)
      let forwardRate: Decimal | undefined;
      if (i > 0) {
        const prevPoint = enriched[i - 1];
        const prevMaturityYears = prevPoint.maturityMonths / 12;
        const currMaturityYears = maturityYears;

        // Forward rate: (yield2 * T2 - yield1 * T1) / (T2 - T1)
        const forwardNumerator = point.yield
          .times(currMaturityYears)
          .minus(prevPoint.yield.times(prevMaturityYears));
        const forwardDenominator = currMaturityYears - prevMaturityYears;
        forwardRate = forwardNumerator.dividedBy(forwardDenominator);
      }

      enriched.push({
        maturityMonths: point.maturityMonths,
        yield: point.yield,
        discountFactor: df,
        forwardRate,
      });
    }

    return enriched;
  }

  /**
   * Get curve name
   */
  private getCurveName(curveType: YieldCurveType): string {
    const names: Record<YieldCurveType, string> = {
      sovereign: 'GRU Sovereign Yield Curve (DBIS + SCB Reserves)',
      synthetic: 'GRU Synthetic Yield Curve (Derivatives Market)',
      bond_implied: 'GRU Bond-Implied Yield Curve (Li99Pp Instruments)',
    };

    return names[curveType];
  }

  /**
   * Get all yield curves
   */
  async getAllYieldCurves(): Promise<YieldCurveData[]> {
    const curves = await prisma.gru_yield_curves.findMany({
      where: { status: 'active' },
      include: {
        points: {
          orderBy: { maturityMonths: 'asc' },
        },
      },
      orderBy: { curveType: 'asc' },
    });

    return curves.map((curve) => ({
      curveId: curve.curveId,
      curveType: curve.curveType,
      curveName: curve.curveName,
      effectiveDate: curve.effectiveDate,
      maturityPoints: curve.points.map((p) => ({
        maturityMonths: p.maturityMonths,
        yield: p.yield,
        discountFactor: p.discountFactor || undefined,
        forwardRate: p.forwardRate || undefined,
      })),
      interpolationMethod: curve.interpolationMethod,
    }));
  }
}

export const gruYieldCurveService = new GruYieldCurveService();

