/**
 * Risk Metrics and Analysis
 * Comprehensive risk measurement tools for portfolio and trading analysis
 */

import type { RiskMetrics } from '@stock-bot/types';

/**
 * Calculate Value at Risk (VaR) using historical simulation
 */
export function valueAtRisk(returns: number[], confidenceLevel: number = 0.95): number {
  if (returns.length === 0) {
    return 0;
  }

  const sortedReturns = [...returns].sort((a, b) => a - b);
  const index = Math.floor((1 - confidenceLevel) * sortedReturns.length);

  return sortedReturns[index] || 0;
}

/**
 * Calculate Conditional Value at Risk (CVaR/Expected Shortfall)
 */
export function conditionalValueAtRisk(returns: number[], confidenceLevel: number = 0.95): number {
  if (returns.length === 0) {
    return 0;
  }

  const sortedReturns = [...returns].sort((a, b) => a - b);
  const cutoffIndex = Math.floor((1 - confidenceLevel) * sortedReturns.length);

  if (cutoffIndex === 0) {
    return sortedReturns[0] || 0;
  }

  const tailReturns = sortedReturns.slice(0, cutoffIndex);
  return tailReturns.reduce((sum, ret) => sum + ret, 0) / tailReturns.length;
}

/**
 * Calculate parametric VaR using normal distribution
 */
export function parametricVaR(
  returns: number[],
  confidenceLevel: number = 0.95,
  portfolioValue: number = 1
): number {
  if (returns.length === 0) {
    return 0;
  }

  const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
  const variance =
    returns.reduce((sum, ret) => sum + Math.pow(ret - mean, 2), 0) / (returns.length - 1);
  const stdDev = Math.sqrt(variance);

  // Z-score for confidence level (normal distribution)
  const zScore = getZScore(confidenceLevel);

  return portfolioValue * (mean - zScore * stdDev);
}

/**
 * Calculate maximum drawdown
 */
export function maxDrawdown(equityCurve: number[]): number {
  if (equityCurve.length < 2) {
    return 0;
  }

  let maxDD = 0;
  const first = equityCurve[0];
  if (first === undefined) {
    return 0;
  }

  let peak = first;

  for (let i = 1; i < equityCurve.length; i++) {
    const current = equityCurve[i];
    if (current === undefined) {
      continue;
    }

    if (current > peak) {
      peak = current;
    } else {
      const drawdown = (peak - current) / peak;
      maxDD = Math.max(maxDD, drawdown);
    }
  }

  return maxDD;
}

/**
 * Calculate downside deviation
 */
export function downsideDeviation(returns: number[], targetReturn: number = 0): number {
  if (returns.length === 0) {
    return 0;
  }

  const downsideReturns = returns.filter(ret => ret < targetReturn);

  if (downsideReturns.length === 0) {
    return 0;
  }

  const sumSquaredDownside = downsideReturns.reduce(
    (sum, ret) => sum + Math.pow(ret - targetReturn, 2),
    0
  );

  return Math.sqrt(sumSquaredDownside / returns.length);
}

/**
 * Calculate Sharpe ratio
 */
export function sharpeRatio(returns: number[], riskFreeRate: number = 0): number {
  if (returns.length < 2) {
    return 0;
  }

  const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
  const variance =
    returns.reduce((sum, ret) => sum + Math.pow(ret - mean, 2), 0) / (returns.length - 1);
  const stdDev = Math.sqrt(variance);

  if (stdDev === 0) {
    return 0;
  }

  return (mean - riskFreeRate) / stdDev;
}

/**
 * Calculate beta coefficient
 */
export function beta(portfolioReturns: number[], marketReturns: number[]): number {
  if (portfolioReturns.length !== marketReturns.length || portfolioReturns.length < 2) {
    return 0;
  }

  const n = portfolioReturns.length;
  const portfolioMean = portfolioReturns.reduce((sum, ret) => sum + ret, 0) / n;
  const marketMean = marketReturns.reduce((sum, ret) => sum + ret, 0) / n;

  let covariance = 0;
  let marketVariance = 0;

  for (let i = 0; i < n; i++) {
    const portfolioReturn = portfolioReturns[i];
    const marketReturn = marketReturns[i];
    if (portfolioReturn === undefined || marketReturn === undefined) {
      continue;
    }

    const portfolioDiff = portfolioReturn - portfolioMean;
    const marketDiff = marketReturn - marketMean;

    covariance += portfolioDiff * marketDiff;
    marketVariance += marketDiff * marketDiff;
  }

  return marketVariance === 0 ? 0 : covariance / marketVariance;
}

/**
 * Calculate alpha
 */
export function alpha(
  portfolioReturns: number[],
  marketReturns: number[],
  riskFreeRate: number = 0
): number {
  const portfolioMean =
    portfolioReturns.reduce((sum, ret) => sum + ret, 0) / portfolioReturns.length;
  const marketMean = marketReturns.reduce((sum, ret) => sum + ret, 0) / marketReturns.length;
  const portfolioBeta = beta(portfolioReturns, marketReturns);

  return portfolioMean - (riskFreeRate + portfolioBeta * (marketMean - riskFreeRate));
}

/**
 * Calculate Treynor ratio
 */
export function treynorRatio(
  portfolioReturns: number[],
  marketReturns: number[],
  riskFreeRate: number = 0
): number {
  const portfolioBeta = beta(portfolioReturns, marketReturns);

  if (portfolioBeta === 0) {
    return 0;
  }

  const portfolioMean =
    portfolioReturns.reduce((sum, ret) => sum + ret, 0) / portfolioReturns.length;
  return (portfolioMean - riskFreeRate) / portfolioBeta;
}

/**
 * Calculate tracking error
 */
export function trackingError(portfolioReturns: number[], benchmarkReturns: number[]): number {
  if (portfolioReturns.length !== benchmarkReturns.length || portfolioReturns.length === 0) {
    return 0;
  }

  const activeReturns = portfolioReturns.map((ret, i) => {
    const benchmark = benchmarkReturns[i];
    return benchmark !== undefined ? ret - benchmark : 0;
  });
  const mean = activeReturns.reduce((sum, ret) => sum + ret, 0) / activeReturns.length;

  const variance =
    activeReturns.reduce((sum, ret) => sum + Math.pow(ret - mean, 2), 0) /
    (activeReturns.length - 1);

  return Math.sqrt(variance);
}

/**
 * Calculate volatility (standard deviation of returns)
 */
export function volatility(returns: number[]): number {
  if (returns.length < 2) {
    return 0;
  }

  const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
  const variance =
    returns.reduce((sum, ret) => sum + Math.pow(ret - mean, 2), 0) / (returns.length - 1);

  return Math.sqrt(variance);
}

/**
 * Calculate annualized volatility
 */
export function annualizedVolatility(returns: number[], periodsPerYear: number = 252): number {
  return volatility(returns) * Math.sqrt(periodsPerYear);
}

/**
 * Calculate skewness (measure of asymmetry)
 */
export function skewness(returns: number[]): number {
  if (returns.length < 3) {
    return 0;
  }

  const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
  const variance = returns.reduce((sum, ret) => sum + Math.pow(ret - mean, 2), 0) / returns.length;
  const stdDev = Math.sqrt(variance);

  if (stdDev === 0) {
    return 0;
  }

  const skew =
    returns.reduce((sum, ret) => sum + Math.pow((ret - mean) / stdDev, 3), 0) / returns.length;

  return skew;
}

/**
 * Calculate kurtosis (measure of tail heaviness)
 */
export function kurtosis(returns: number[]): number {
  if (returns.length < 4) {
    return 0;
  }

  const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
  const variance = returns.reduce((sum, ret) => sum + Math.pow(ret - mean, 2), 0) / returns.length;
  const stdDev = Math.sqrt(variance);

  if (stdDev === 0) {
    return 0;
  }

  const kurt =
    returns.reduce((sum, ret) => sum + Math.pow((ret - mean) / stdDev, 4), 0) / returns.length;

  return kurt - 3; // Excess kurtosis (subtract 3 for normal distribution baseline)
}

/**
 * Calculate comprehensive risk metrics
 */
export function calculateRiskMetrics(
  returns: number[],
  equityCurve: number[],
  marketReturns?: number[],
  riskFreeRate: number = 0
): RiskMetrics {
  if (returns.length === 0) {
    return {
      var95: 0,
      var99: 0,
      cvar95: 0,
      maxDrawdown: 0,
      volatility: 0,
      downside_deviation: 0,
      calmar_ratio: 0,
      sortino_ratio: 0,
      beta: 0,
      alpha: 0,
      sharpeRatio: 0,
      treynorRatio: 0,
      trackingError: 0,
      informationRatio: 0,
    };
  }

  const portfolioVolatility = volatility(returns);
  const portfolioMean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
  // Calculate VaR
  const var95Value = valueAtRisk(returns, 0.95);
  const var99Value = valueAtRisk(returns, 0.99);
  const cvar95Value = conditionalValueAtRisk(returns, 0.95);

  // Calculate max drawdown
  const maxDD = maxDrawdown(equityCurve);

  // Calculate downside deviation
  const downsideDeviationValue = downsideDeviation(returns);
  // Calculate ratios
  const calmarRatio = maxDD > 0 ? portfolioMean / maxDD : 0;
  const sortinoRatio =
    downsideDeviationValue > 0 ? (portfolioMean - riskFreeRate) / downsideDeviationValue : 0;
  const sharpeRatio =
    portfolioVolatility > 0 ? (portfolioMean - riskFreeRate) / portfolioVolatility : 0;

  let portfolioBeta = 0;
  let portfolioAlpha = 0;
  let portfolioTreynorRatio = 0;
  let portfolioTrackingError = 0;
  let informationRatio = 0;

  if (marketReturns && marketReturns.length === returns.length) {
    portfolioBeta = beta(returns, marketReturns);
    portfolioAlpha = alpha(returns, marketReturns, riskFreeRate);
    portfolioTreynorRatio = treynorRatio(returns, marketReturns, riskFreeRate);
    portfolioTrackingError = trackingError(returns, marketReturns);
    informationRatio = portfolioTrackingError > 0 ? portfolioAlpha / portfolioTrackingError : 0;
  }
  return {
    var95: var95Value,
    var99: var99Value,
    cvar95: cvar95Value,
    maxDrawdown: maxDD,
    volatility: portfolioVolatility,
    downside_deviation: downsideDeviationValue,
    calmar_ratio: calmarRatio,
    sortino_ratio: sortinoRatio,
    beta: portfolioBeta,
    alpha: portfolioAlpha,
    sharpeRatio,
    treynorRatio: portfolioTreynorRatio,
    trackingError: portfolioTrackingError,
    informationRatio,
  };
}

/**
 * Helper function to get Z-score for confidence level
 * This implementation handles arbitrary confidence levels
 */
function getZScore(confidenceLevel: number): number {
  // First check our lookup table for common values (more precise)
  const zScores: { [key: string]: number } = {
    '0.90': 1.282,
    '0.95': 1.645,
    '0.975': 1.96,
    '0.99': 2.326,
    '0.995': 2.576,
  };

  const key = confidenceLevel.toString();
  if (zScores[key]) {
    return zScores[key];
  }

  // For arbitrary confidence levels, use approximation
  if (confidenceLevel < 0.5) {
    return -getZScore(1 - confidenceLevel);
  }

  if (confidenceLevel >= 0.999) {
    return 3.09;
  } // Cap at 99.9% for numerical stability

  // Approximation of inverse normal CDF
  const y = Math.sqrt(-2.0 * Math.log(1.0 - confidenceLevel));
  return (
    y -
    (2.515517 + 0.802853 * y + 0.010328 * y * y) /
      (1.0 + 1.432788 * y + 0.189269 * y * y + 0.001308 * y * y * y)
  );
}

/**
 * Calculate portfolio risk contribution
 */
export function riskContribution(
  weights: number[],
  covarianceMatrix: number[][],
  portfolioVolatility: number
): number[] {
  const n = weights.length;
  const contributions: number[] = [];

  for (let i = 0; i < n; i++) {
    let marginalContribution = 0;
    const row = covarianceMatrix[i];
    if (!row) {
      continue;
    }

    for (let j = 0; j < n; j++) {
      const weight = weights[j];
      const covariance = row[j];
      if (weight !== undefined && covariance !== undefined) {
        marginalContribution += weight * covariance;
      }
    }

    const weight = weights[i];
    if (weight !== undefined && portfolioVolatility !== 0) {
      const contribution = (weight * marginalContribution) / Math.pow(portfolioVolatility, 2);
      contributions.push(contribution);
    }
  }

  return contributions;
}

/**
 * Calculate Ulcer Index
 */
export function ulcerIndex(equityCurve: Array<{ value: number; date: Date }>): number {
  if (equityCurve.length === 0) {
    return 0;
  }

  let sumSquaredDrawdown = 0;
  const first = equityCurve[0];
  if (!first) {
    return 0;
  }

  let peak = first.value;

  for (const point of equityCurve) {
    peak = Math.max(peak, point.value);
    const drawdownPercent = ((peak - point.value) / peak) * 100;
    sumSquaredDrawdown += drawdownPercent * drawdownPercent;
  }

  return Math.sqrt(sumSquaredDrawdown / equityCurve.length);
}

/**
 * Calculate risk-adjusted return (RAR)
 */
export function riskAdjustedReturn(
  portfolioReturn: number,
  portfolioRisk: number,
  riskFreeRate: number = 0
): number {
  if (portfolioRisk === 0) {
    return 0;
  }
  return (portfolioReturn - riskFreeRate) / portfolioRisk;
}
