// GRU Derivatives Pricing Service
// Pricing model: GRU_Deriv_Price = Index_BaseValue + Volatility_Surface(GRU) + Liquidity_Premium - Metal_Arbitrage_Spread

import prisma from '@/shared/database/prisma';
import { Decimal } from '@prisma/client/runtime/library';
import { logger } from '@/infrastructure/monitoring/logger';
import { gruIndexService, GruIndexCode } from './gru-index.service';


export interface DerivativePricingInput {
  indexCode: GruIndexCode;
  indexBaseValue: Decimal;
  volatility: Decimal;
  liquidityPremium: Decimal;
  metalArbitrageSpread: Decimal;
}

export interface DerivativePriceResult {
  baseValue: Decimal;
  volatilityComponent: Decimal;
  liquidityComponent: Decimal;
  arbitrageAdjustment: Decimal;
  finalPrice: Decimal;
}

export class GruDerivativesPricingService {
  /**
   * Calculate derivative price using the GRU pricing model
   * Formula: GRU_Deriv_Price = Index_BaseValue + Volatility_Surface(GRU) + Liquidity_Premium - Metal_Arbitrage_Spread
   */
  async calculateDerivativePrice(
    input: DerivativePricingInput
  ): Promise<DerivativePriceResult> {
    logger.info('GRU Derivatives: Calculating derivative price', { input });

    // Get index base value
    const indexBaseValue = input.indexBaseValue;

    // Calculate volatility surface component
    const volatilityComponent = this.calculateVolatilitySurface(
      indexBaseValue,
      input.volatility
    );

    // Add liquidity premium
    const liquidityComponent = input.liquidityPremium;

    // Subtract metal arbitrage spread
    const arbitrageAdjustment = input.metalArbitrageSpread.negated();

    // Final price calculation
    const finalPrice = indexBaseValue
      .plus(volatilityComponent)
      .plus(liquidityComponent)
      .plus(arbitrageAdjustment);

    logger.info('GRU Derivatives: Price calculated', {
      finalPrice: finalPrice.toString(),
    });

    return {
      baseValue: indexBaseValue,
      volatilityComponent,
      liquidityComponent,
      arbitrageAdjustment,
      finalPrice,
    };
  }

  /**
   * Calculate volatility surface component
   * Volatility surface adjusts for market volatility
   */
  private calculateVolatilitySurface(
    baseValue: Decimal,
    volatility: Decimal
  ): Decimal {
    // Volatility surface: baseValue * (volatility^2) / 2
    // This approximates Black-Scholes style volatility adjustment
    const volSquared = volatility.pow(2);
    const volSurface = baseValue.times(volSquared).dividedBy(2);
    return volSurface;
  }

  /**
   * Calculate default volatility for a GRU index
   */
  async getDefaultVolatility(indexCode: GruIndexCode): Promise<Decimal> {
    // Get historical volatility from price history
    const history = await gruIndexService.getIndexHistory(indexCode, 30);

    if (history.length < 2) {
      // Default volatility if no history
      return new Decimal(0.15); // 15% default
    }

    // Calculate volatility from price changes
    const returns: Decimal[] = [];
    for (let i = 1; i < history.length; i++) {
      const prev = history[i - 1].indexValue;
      const curr = history[i].indexValue;
      const change = curr.minus(prev).dividedBy(prev);
      returns.push(change);
    }

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

    // Calculate standard deviation of returns
    const mean = returns.reduce((sum, r) => sum.plus(r), new Decimal(0))
      .dividedBy(returns.length);

    const variance = returns
      .reduce((sum, r) => sum.plus(r.minus(mean).pow(2)), new Decimal(0))
      .dividedBy(returns.length);

    const stdDev = variance.sqrt();

    // Annualized volatility (assuming daily data)
    const annualizedVol = stdDev.times(Math.sqrt(252));

    return annualizedVol;
  }

  /**
   * Calculate liquidity premium
   * Based on market depth and trading volume
   */
  async calculateLiquidityPremium(
    indexCode: GruIndexCode,
    notionalAmount: Decimal
  ): Promise<Decimal> {
    // Get recent volume
    const history = await gruIndexService.getIndexHistory(indexCode, 10);
    const avgVolume = history.length > 0
      ? history.reduce((sum, h) => sum.plus(h.volume || 0), new Decimal(0))
          .dividedBy(history.length)
      : new Decimal(1000000);

    // Liquidity premium increases with trade size relative to average volume
    const sizeRatio = notionalAmount.dividedBy(avgVolume);
    const liquidityPremium = sizeRatio
      .times(0.001) // 0.1% base premium
      .min(new Decimal(0.05)); // Cap at 5%

    return liquidityPremium;
  }

  /**
   * Calculate metal arbitrage spread
   * Based on price differences between GRU and underlying metals
   */
  async calculateMetalArbitrageSpread(
    indexCode: GruIndexCode
  ): Promise<Decimal> {
    // Get current index value
    const index = await gruIndexService.getIndex(indexCode);
    if (!index) {
      return new Decimal(0);
    }

    // For LiXAU, arbitrage is minimal (direct XAU link)
    if (indexCode === 'LiXAU') {
      return new Decimal(0.001); // 0.1% minimal spread
    }

    // For composite indexes, calculate spread based on component price deviations
    // This is a simplified model - in production would use real-time metal prices
    const spread = new Decimal(0.002); // 0.2% default for composite indexes

    return spread;
  }

  /**
   * Calculate option Greeks (delta, gamma, theta, vega)
   */
  calculateOptionGreeks(
    spotPrice: Decimal,
    strikePrice: Decimal,
    timeToExpiration: number, // in years
    volatility: Decimal,
    riskFreeRate: Decimal,
    optionType: 'call' | 'put'
  ): {
    delta: Decimal;
    gamma: Decimal;
    theta: Decimal;
    vega: Decimal;
  } {
    // Black-Scholes Greeks calculation
    const S = spotPrice;
    const K = strikePrice;
    const T = Math.max(timeToExpiration / 365, 0.001); // Minimum 1 day
    const sigma = volatility;
    const r = riskFreeRate;

    // d1 and d2 for Black-Scholes
    const d1Numerator = S.dividedBy(K).ln()
      .plus(r.plus(sigma.pow(2).dividedBy(2)).times(T));
    const d1Denominator = sigma.times(Math.sqrt(T));
    const d1 = d1Numerator.dividedBy(d1Denominator);
    const d2 = d1.minus(sigma.times(Math.sqrt(T)));

    // Normal CDF approximation (simplified)
    const N_d1 = this.normalCDF(d1);
    const N_d2 = this.normalCDF(d2);
    const N_neg_d1 = new Decimal(1).minus(N_d1);

    // Delta
    const delta =
      optionType === 'call' ? N_d1 : N_neg_d1.negated();

    // Gamma (same for call and put)
    const gamma = this.normalPDF(d1).dividedBy(S.times(sigma.times(Math.sqrt(T))));

    // Theta (per day, converted to per year)
    const theta1 = S.times(this.normalPDF(d1).times(sigma))
      .dividedBy(new Decimal(2).times(Math.sqrt(T)));
    const theta2 = r.times(K).times(Math.exp(r.negated().times(T))).times(
      optionType === 'call' ? N_d2 : N_d2.negated()
    );
    const theta = theta1
      .plus(theta2)
      .negated()
      .dividedBy(365); // Per day

    // Vega (same for call and put)
    const vega = S.times(this.normalPDF(d1).times(Math.sqrt(T))).dividedBy(100); // Per 1% volatility change

    return {
      delta,
      gamma,
      theta,
      vega,
    };
  }

  /**
   * Normal CDF approximation
   */
  private normalCDF(x: Decimal): Decimal {
    // Abramowitz and Stegun approximation
    const t = new Decimal(1).dividedBy(new Decimal(1).plus(0.2316419.times(x.abs())));
    const d = new Decimal(0.3989423)
      .times(Math.exp(-x.pow(2).dividedBy(2).toNumber()));
    const p = d
      .times(t)
      .times(
        new Decimal(0.3193815)
          .minus(
            t.times(
              new Decimal(0.3565638)
                .minus(
                  t.times(
                    new Decimal(1.781478)
                      .minus(t.times(new Decimal(1.821256).minus(t.times(1.330274))))
                  )
                )
            )
          )
      );

    return x.isPositive() ? new Decimal(1).minus(p) : p;
  }

  /**
   * Normal PDF
   */
  private normalPDF(x: Decimal): Decimal {
    return new Decimal(0.3989423)
      .times(Math.exp(x.pow(2).negated().dividedBy(2).toNumber()));
  }
}

export const gruDerivativesPricingService = new GruDerivativesPricingService();

