import { ulcerIndex } from './risk-metrics';

/**
 * Performance Metrics and Analysis
 * Comprehensive performance measurement tools for trading strategies and portfolios
 */

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

// Define PortfolioMetrics locally until it's added to types library
export interface PortfolioMetrics {
  totalValue: number;
  totalReturn: number;
  totalReturnPercent: number;
  dailyReturn: number;
  dailyReturnPercent: number;
  maxDrawdown: number;
  sharpeRatio: number;
  beta: number;
  alpha: number;
  volatility: number;
}

export interface TradePerformance {
  totalTrades: number;
  winningTrades: number;
  losingTrades: number;
  winRate: number;
  averageWin: number;
  averageLoss: number;
  largestWin: number;
  largestLoss: number;
  profitFactor: number;
  expectancy: number;
  averageTradeReturn: number;
  consecutiveWins: number;
  consecutiveLosses: number;
}

export interface DrawdownAnalysis {
  maxDrawdown: number;
  maxDrawdownDuration: number;
  averageDrawdown: number;
  drawdownPeriods: Array<{
    start: Date;
    end: Date;
    duration: number;
    magnitude: number;
  }>;
}

export interface ReturnAnalysis {
  totalReturn: number;
  annualizedReturn: number;
  compoundAnnualGrowthRate: number;
  volatility: number;
  annualizedVolatility: number;
  skewness: number;
  kurtosis: number;
  bestMonth: number;
  worstMonth: number;
  positiveMonths: number;
  negativeMonths: number;
}

/**
 * Calculate comprehensive trade performance metrics
 */
export function analyzeTradePerformance(
  trades: Array<{ pnl: number; date: Date }>
): TradePerformance {
  if (trades.length === 0) {
    return {
      totalTrades: 0,
      winningTrades: 0,
      losingTrades: 0,
      winRate: 0,
      averageWin: 0,
      averageLoss: 0,
      largestWin: 0,
      largestLoss: 0,
      profitFactor: 0,
      expectancy: 0,
      averageTradeReturn: 0,
      consecutiveWins: 0,
      consecutiveLosses: 0,
    };
  }

  const winningTrades = trades.filter(trade => trade.pnl > 0);
  const losingTrades = trades.filter(trade => trade.pnl < 0);

  const totalWins = winningTrades.reduce((sum, trade) => sum + trade.pnl, 0);
  const totalLosses = Math.abs(losingTrades.reduce((sum, trade) => sum + trade.pnl, 0));

  const averageWin = winningTrades.length > 0 ? totalWins / winningTrades.length : 0;
  const averageLoss = losingTrades.length > 0 ? totalLosses / losingTrades.length : 0;

  const largestWin = winningTrades.length > 0 ? Math.max(...winningTrades.map(t => t.pnl)) : 0;
  const largestLoss = losingTrades.length > 0 ? Math.min(...losingTrades.map(t => t.pnl)) : 0;

  const profitFactor = totalLosses > 0 ? totalWins / totalLosses : totalWins > 0 ? Infinity : 0;
  const winRate = winningTrades.length / trades.length;
  const expectancy = winRate * averageWin - (1 - winRate) * averageLoss;

  const totalPnL = trades.reduce((sum, trade) => sum + trade.pnl, 0);
  const averageTradeReturn = totalPnL / trades.length;

  // Calculate consecutive wins/losses
  let consecutiveWins = 0;
  let consecutiveLosses = 0;
  let currentWinStreak = 0;
  let currentLossStreak = 0;

  for (const trade of trades) {
    if (trade.pnl > 0) {
      currentWinStreak++;
      currentLossStreak = 0;
      consecutiveWins = Math.max(consecutiveWins, currentWinStreak);
    } else if (trade.pnl < 0) {
      currentLossStreak++;
      currentWinStreak = 0;
      consecutiveLosses = Math.max(consecutiveLosses, currentLossStreak);
    }
  }

  return {
    totalTrades: trades.length,
    winningTrades: winningTrades.length,
    losingTrades: losingTrades.length,
    winRate,
    averageWin,
    averageLoss,
    largestWin,
    largestLoss,
    profitFactor,
    expectancy,
    averageTradeReturn,
    consecutiveWins,
    consecutiveLosses,
  };
}

/**
 * Analyze drawdown characteristics
 */
export function analyzeDrawdowns(
  equityCurve: Array<{ value: number; date: Date }>
): DrawdownAnalysis {
  if (equityCurve.length < 2) {
    return {
      maxDrawdown: 0,
      maxDrawdownDuration: 0,
      averageDrawdown: 0,
      drawdownPeriods: [],
    };
  }

  const first = equityCurve[0];
  if (!first) {
    return { maxDrawdown: 0, maxDrawdownDuration: 0, averageDrawdown: 0, drawdownPeriods: [] };
  }

  let peak = first.value;
  let peakDate = first.date;
  let maxDrawdown = 0;
  let maxDrawdownDuration = 0;

  const drawdownPeriods: Array<{
    start: Date;
    end: Date;
    duration: number;
    magnitude: number;
  }> = [];

  let currentDrawdownStart: Date | null = null;
  const drawdowns: number[] = [];

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

    if (current.value > peak) {
      // New peak - end any current drawdown
      if (currentDrawdownStart) {
        const prev = equityCurve[i - 1];
        if (!prev) {
          continue;
        }

        const drawdownMagnitude = (peak - prev.value) / peak;
        const duration = Math.floor(
          (prev.date.getTime() - currentDrawdownStart.getTime()) / (1000 * 60 * 60 * 24)
        );

        drawdownPeriods.push({
          start: currentDrawdownStart,
          end: prev.date,
          duration,
          magnitude: drawdownMagnitude,
        });

        drawdowns.push(drawdownMagnitude);
        maxDrawdownDuration = Math.max(maxDrawdownDuration, duration);
        currentDrawdownStart = null;
      }

      peak = current.value;
      peakDate = current.date;
    } else {
      // In drawdown
      if (!currentDrawdownStart) {
        currentDrawdownStart = peakDate;
      }

      const drawdown = (peak - current.value) / peak;
      maxDrawdown = Math.max(maxDrawdown, drawdown);
    }
  }

  // Handle ongoing drawdown
  if (currentDrawdownStart) {
    const lastPoint = equityCurve[equityCurve.length - 1];
    if (!lastPoint) {
      return { maxDrawdown, maxDrawdownDuration, averageDrawdown: 0, drawdownPeriods };
    }

    const drawdownMagnitude = (peak - lastPoint.value) / peak;
    const duration = Math.floor(
      (lastPoint.date.getTime() - currentDrawdownStart.getTime()) / (1000 * 60 * 60 * 24)
    );

    drawdownPeriods.push({
      start: currentDrawdownStart,
      end: lastPoint.date,
      duration,
      magnitude: drawdownMagnitude,
    });

    drawdowns.push(drawdownMagnitude);
    maxDrawdownDuration = Math.max(maxDrawdownDuration, duration);
  }

  const averageDrawdown =
    drawdowns.length > 0 ? drawdowns.reduce((sum, dd) => sum + dd, 0) / drawdowns.length : 0;

  return {
    maxDrawdown,
    maxDrawdownDuration,
    averageDrawdown,
    drawdownPeriods,
  };
}

/**
 * Analyze return characteristics
 */
export function analyzeReturns(
  returns: Array<{ return: number; date: Date }>,
  periodsPerYear: number = 252
): ReturnAnalysis {
  if (returns.length === 0) {
    return {
      totalReturn: 0,
      annualizedReturn: 0,
      compoundAnnualGrowthRate: 0,
      volatility: 0,
      annualizedVolatility: 0,
      skewness: 0,
      kurtosis: 0,
      bestMonth: 0,
      worstMonth: 0,
      positiveMonths: 0,
      negativeMonths: 0,
    };
  }

  const returnValues = returns.map(r => r.return);

  // Calculate basic statistics
  const totalReturn = returnValues.reduce((product, ret) => product * (1 + ret), 1) - 1;
  const averageReturn = returnValues.reduce((sum, ret) => sum + ret, 0) / returnValues.length;
  const annualizedReturn = Math.pow(1 + averageReturn, periodsPerYear) - 1;

  // Calculate CAGR
  const years = returns.length / periodsPerYear;
  const cagr = years > 0 ? Math.pow(1 + totalReturn, 1 / years) - 1 : 0;

  // Calculate volatility
  const variance =
    returnValues.reduce((sum, ret) => sum + Math.pow(ret - averageReturn, 2), 0) /
    (returnValues.length - 1);
  const volatility = Math.sqrt(variance);
  const annualizedVolatility = volatility * Math.sqrt(periodsPerYear);

  // Calculate skewness and kurtosis
  const skewness = calculateSkewness(returnValues);
  const kurtosis = calculateKurtosis(returnValues);

  // Monthly analysis
  const monthlyReturns = aggregateMonthlyReturns(returns);
  const bestMonth = monthlyReturns.length > 0 ? Math.max(...monthlyReturns) : 0;
  const worstMonth = monthlyReturns.length > 0 ? Math.min(...monthlyReturns) : 0;
  const positiveMonths = monthlyReturns.filter(ret => ret > 0).length;
  const negativeMonths = monthlyReturns.filter(ret => ret < 0).length;

  return {
    totalReturn,
    annualizedReturn,
    compoundAnnualGrowthRate: cagr,
    volatility,
    annualizedVolatility,
    skewness,
    kurtosis,
    bestMonth,
    worstMonth,
    positiveMonths,
    negativeMonths,
  };
}

/**
 * Calculate rolling performance metrics
 */
export function calculateRollingMetrics(
  returns: number[],
  windowSize: number,
  metricType: 'sharpe' | 'volatility' | 'return' = 'sharpe'
): number[] {
  if (returns.length < windowSize) {
    return [];
  }

  const rollingMetrics: number[] = [];

  for (let i = windowSize - 1; i < returns.length; i++) {
    const window = returns.slice(i - windowSize + 1, i + 1);

    switch (metricType) {
      case 'sharpe':
        rollingMetrics.push(calculateSharpeRatio(window));
        break;
      case 'volatility':
        rollingMetrics.push(calculateVolatility(window));
        break;
      case 'return': {
        const avgReturn = window.reduce((sum, ret) => sum + ret, 0) / window.length;
        rollingMetrics.push(avgReturn);
        break;
      }
    }
  }

  return rollingMetrics;
}

/**
 * Calculate performance attribution
 */
export function strategyPerformanceAttribution(
  portfolioReturns: number[],
  benchmarkReturns: number[],
  sectorWeights: number[],
  sectorReturns: number[]
): {
  allocationEffect: number;
  selectionEffect: number;
  interactionEffect: number;
  totalActiveReturn: number;
} {
  if (portfolioReturns.length !== benchmarkReturns.length) {
    throw new Error('Portfolio and benchmark returns must have same length');
  }

  const portfolioReturn =
    portfolioReturns.reduce((sum, ret) => sum + ret, 0) / portfolioReturns.length;
  const benchmarkReturn =
    benchmarkReturns.reduce((sum, ret) => sum + ret, 0) / benchmarkReturns.length;

  let allocationEffect = 0;
  let selectionEffect = 0;
  let interactionEffect = 0;

  for (let i = 0; i < sectorWeights.length; i++) {
    const portfolioWeight = sectorWeights[i];
    const sectorReturn = sectorReturns[i];
    if (portfolioWeight === undefined || sectorReturn === undefined) {
      continue;
    }

    const benchmarkWeight = 1 / sectorWeights.length; // Assuming equal benchmark weights

    // Allocation effect: (portfolio weight - benchmark weight) * (benchmark sector return - benchmark return)
    allocationEffect += (portfolioWeight - benchmarkWeight) * (sectorReturn - benchmarkReturn);

    // Selection effect: benchmark weight * (portfolio sector return - benchmark sector return)
    selectionEffect += benchmarkWeight * (sectorReturn - sectorReturn); // Simplified

    // Interaction effect: (portfolio weight - benchmark weight) * (portfolio sector return - benchmark sector return)
    interactionEffect += (portfolioWeight - benchmarkWeight) * (sectorReturn - sectorReturn); // Simplified
  }

  const totalActiveReturn = portfolioReturn - benchmarkReturn;

  return {
    allocationEffect,
    selectionEffect,
    interactionEffect,
    totalActiveReturn,
  };
}

/**
 * Calculate Omega ratio
 */
export function omegaRatio(returns: number[], threshold: number = 0): number {
  if (returns.length === 0) {
    return 0;
  }

  const gains = returns
    .filter(ret => ret > threshold)
    .reduce((sum, ret) => sum + (ret - threshold), 0);
  const losses = returns
    .filter(ret => ret < threshold)
    .reduce((sum, ret) => sum + Math.abs(ret - threshold), 0);

  return losses === 0 ? Infinity : gains / losses;
}

/**
 * Calculate gain-to-pain ratio
 */
export function gainToPainRatio(returns: number[]): number {
  if (returns.length === 0) {
    return 0;
  }

  const totalGain = returns.reduce((sum, ret) => sum + ret, 0);
  const totalPain = returns.filter(ret => ret < 0).reduce((sum, ret) => sum + Math.abs(ret), 0);

  return totalPain === 0 ? (totalGain > 0 ? Infinity : 0) : totalGain / totalPain;
}

/**
 * Calculate Martin ratio (modified Sharpe with downside deviation)
 */
export function martinRatio(returns: number[], riskFreeRate: number = 0): number {
  if (returns.length === 0) {
    return 0;
  }

  const averageReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
  const downsideReturns = returns.filter(ret => ret < riskFreeRate);

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

  const downsideDeviation = Math.sqrt(
    downsideReturns.reduce((sum, ret) => sum + Math.pow(ret - riskFreeRate, 2), 0) / returns.length
  );

  return downsideDeviation === 0 ? Infinity : (averageReturn - riskFreeRate) / downsideDeviation;
}

/**
 * Calculate comprehensive portfolio metrics
 */
export function calculateStrategyMetrics(
  equityCurve: Array<{ value: number; date: Date }>,
  benchmarkReturns?: number[],
  riskFreeRate: number = 0.02
): PortfolioMetrics {
  if (equityCurve.length < 2) {
    return {
      totalValue: 0,
      totalReturn: 0,
      totalReturnPercent: 0,
      dailyReturn: 0,
      dailyReturnPercent: 0,
      maxDrawdown: 0,
      sharpeRatio: 0,
      beta: 0,
      alpha: 0,
      volatility: 0,
    };
  }

  const returns = [];
  for (let i = 1; i < equityCurve.length; i++) {
    const current = equityCurve[i];
    const previous = equityCurve[i - 1];
    if (!current || !previous) {
      continue;
    }

    const ret = (current.value - previous.value) / previous.value;
    returns.push(ret);
  }

  const lastPoint = equityCurve[equityCurve.length - 1];
  const firstPoint = equityCurve[0];
  if (!lastPoint || !firstPoint) {
    return {
      totalValue: 0,
      totalReturn: 0,
      totalReturnPercent: 0,
      dailyReturn: 0,
      dailyReturnPercent: 0,
      maxDrawdown: 0,
      sharpeRatio: 0,
      beta: 0,
      alpha: 0,
      volatility: 0,
    };
  }

  const totalValue = lastPoint.value;
  const totalReturn = totalValue - firstPoint.value;
  const totalReturnPercent = (totalReturn / firstPoint.value) * 100;

  const dailyReturn = returns[returns.length - 1] || 0;
  const dailyReturnPercent = dailyReturn * 100;

  const maxDrawdown = analyzeDrawdowns(equityCurve).maxDrawdown;
  const sharpeRatio = calculateSharpeRatio(returns, riskFreeRate);
  const volatility = calculateVolatility(returns);

  let beta = 0;
  let alpha = 0;

  if (benchmarkReturns && benchmarkReturns.length === returns.length) {
    beta = calculateBeta(returns, benchmarkReturns);
    alpha = calculateAlpha(returns, benchmarkReturns, riskFreeRate);
  }

  return {
    totalValue,
    totalReturn,
    totalReturnPercent,
    dailyReturn,
    dailyReturnPercent,
    maxDrawdown,
    sharpeRatio,
    beta,
    alpha,
    volatility,
  };
}

/**
 * Calculate Calmar Ratio
 */
export function calmarRatio(
  returns: number[],
  equityCurve: Array<{ value: number; date: Date }>,
  riskFreeRate: number = 0
): number {
  const maxDrawdown = analyzeDrawdowns(equityCurve).maxDrawdown;
  const avgReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;

  return maxDrawdown === 0 ? 0 : (avgReturn - riskFreeRate) / maxDrawdown;
}

/**
 * Calculate Sterling Ratio
 */
export function sterlingRatio(
  returns: number[],
  equityCurve: Array<{ value: number; date: Date }>,
  riskFreeRate: number = 0
): number {
  const averageDrawdown = analyzeDrawdowns(equityCurve).averageDrawdown;
  const avgReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;

  return averageDrawdown === 0 ? 0 : (avgReturn - riskFreeRate) / averageDrawdown;
}

/**
 * Calculate Information Ratio
 */
export function informationRatio(portfolioReturns: number[], benchmarkReturns: number[]): number {
  if (portfolioReturns.length !== benchmarkReturns.length) {
    throw new Error('Portfolio and benchmark returns must have the same length.');
  }

  const excessReturns = portfolioReturns.map((portfolioReturn, index) => {
    const benchmark = benchmarkReturns[index];
    return benchmark !== undefined ? portfolioReturn - benchmark : 0;
  });
  const trackingError = calculateVolatility(excessReturns);
  const avgExcessReturn = excessReturns.reduce((sum, ret) => sum + ret, 0) / excessReturns.length;

  return trackingError === 0 ? 0 : avgExcessReturn / trackingError;
}

// Treynor Ratio is already exported from risk-metrics

/**
 * Calculate Jensen's Alpha (same as Alpha, but included for clarity)
 */
export function jensensAlpha(
  portfolioReturns: number[],
  marketReturns: number[],
  riskFreeRate: number
): number {
  return calculateAlpha(portfolioReturns, marketReturns, riskFreeRate);
}

/**
 * Calculate Capture Ratio (Up Capture and Down Capture)
 */
export function captureRatio(
  portfolioReturns: number[],
  benchmarkReturns: number[]
): { upCaptureRatio: number; downCaptureRatio: number } {
  let upCapture = 0;
  let downCapture = 0;
  let upMarketPeriods = 0;
  let downMarketPeriods = 0;

  for (let i = 0; i < portfolioReturns.length; i++) {
    const benchmarkReturn = benchmarkReturns[i];
    const portfolioReturn = portfolioReturns[i];
    if (benchmarkReturn === undefined || portfolioReturn === undefined) {
      continue;
    }

    if (benchmarkReturn > 0) {
      upCapture += portfolioReturn;
      upMarketPeriods++;
    } else if (benchmarkReturn < 0) {
      downCapture += portfolioReturn;
      downMarketPeriods++;
    }
  }

  const upCaptureRatio =
    upMarketPeriods > 0
      ? upCapture /
        upMarketPeriods /
        (benchmarkReturns.filter(r => r > 0).reduce((sum, r) => sum + r, 0) / upMarketPeriods)
      : 0;
  const downCaptureRatio =
    downMarketPeriods > 0
      ? downCapture /
        downMarketPeriods /
        (benchmarkReturns.filter(r => r < 0).reduce((sum, r) => sum + r, 0) / downMarketPeriods)
      : 0;

  return { upCaptureRatio, downCaptureRatio };
}

/**
 * Calculate Sortino Ratio
 */
export function sortinoRatio(returns: number[], riskFreeRate: number = 0): number {
  const avgReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
  const downsideReturns = returns.filter(ret => ret < riskFreeRate);
  const downsideDeviation = Math.sqrt(
    downsideReturns.reduce((sum, ret) => sum + Math.pow(ret - riskFreeRate, 2), 0) / returns.length
  );

  return downsideDeviation === 0 ? 0 : (avgReturn - riskFreeRate) / downsideDeviation;
}

/**
 * Calculate Tail Ratio
 */
export function tailRatio(returns: number[], tailPercent: number = 0.1): number {
  const numReturns = returns.length;
  const tailSize = Math.floor(numReturns * tailPercent);

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

  const sortedReturns = [...returns].sort((a, b) => a - b);
  const worstTail = sortedReturns.slice(0, tailSize);
  const bestTail = sortedReturns.slice(numReturns - tailSize);

  const avgWorst = worstTail.reduce((sum, ret) => sum + ret, 0) / tailSize;
  const avgBest = bestTail.reduce((sum, ret) => sum + ret, 0) / tailSize;

  return avgWorst === 0 ? 0 : avgBest / Math.abs(avgWorst);
}

/**
 * Calculate Rolling Beta
 */
export function calculateRollingBeta(
  portfolioReturns: number[],
  marketReturns: number[],
  windowSize: number
): number[] {
  if (portfolioReturns.length !== marketReturns.length || portfolioReturns.length < windowSize) {
    return [];
  }

  const rollingBetas: number[] = [];

  for (let i = windowSize; i <= portfolioReturns.length; i++) {
    const portfolioWindow = portfolioReturns.slice(i - windowSize, i);
    const marketWindow = marketReturns.slice(i - windowSize, i);
    rollingBetas.push(calculateBeta(portfolioWindow, marketWindow));
  }

  return rollingBetas;
}

/**
 * Calculate Ulcer Performance Index (UPI)
 */
export function ulcerPerformanceIndex(
  returns: number[],
  equityCurve: Array<{ value: number; date: Date }>,
  riskFreeRate: number = 0
): number {
  const ui = ulcerIndex(equityCurve);
  const avgReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;

  return ui === 0 ? 0 : (avgReturn - riskFreeRate) / ui;
}

/**
 * Calculate Rolling Alpha
 */
export function calculateRollingAlpha(
  portfolioReturns: number[],
  marketReturns: number[],
  riskFreeRate: number,
  windowSize: number
): number[] {
  if (portfolioReturns.length !== marketReturns.length || portfolioReturns.length < windowSize) {
    return [];
  }

  const rollingAlphas: number[] = [];

  for (let i = windowSize; i <= portfolioReturns.length; i++) {
    const portfolioWindow = portfolioReturns.slice(i - windowSize, i);
    const marketWindow = marketReturns.slice(i - windowSize, i);
    rollingAlphas.push(calculateAlpha(portfolioWindow, marketWindow, riskFreeRate));
  }

  return rollingAlphas;
}

/**
 * Calculate Time Weighted Rate of Return (TWRR)
 */
export function timeWeightedRateOfReturn(
  cashFlows: Array<{ amount: number; date: Date; value: number }>
): number {
  if (cashFlows.length < 2) {
    return 0;
  }

  const first = cashFlows[0];
  if (!first) {
    return 0;
  }

  let totalReturn = 1;
  let previousValue = first.value;

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

    const periodReturn =
      (current.value - previousValue - current.amount) / (previousValue + current.amount);
    totalReturn *= 1 + periodReturn;
    previousValue = current.value;
  }

  return totalReturn - 1;
}

/**
 * Calculate Money Weighted Rate of Return (MWRR) - Approximation using IRR
 */
export function moneyWeightedRateOfReturn(
  cashFlows: Array<{ amount: number; date: Date; value: number }>
): number {
  if (cashFlows.length === 0) {
    return 0;
  }

  const first = cashFlows[0];
  if (!first) {
    return 0;
  }

  // Approximate MWRR using Internal Rate of Return (IRR)
  // This requires a numerical method or library for accurate IRR calculation
  // This is a simplified example and may not be accurate for all cases

  let totalCashFlow = 0;
  let totalWeightedCashFlow = 0;
  const startDate = first.date.getTime();

  for (const cf of cashFlows) {
    const timeDiff = (cf.date.getTime() - startDate) / (1000 * 60 * 60 * 24 * 365); // Years
    totalCashFlow += cf.amount;
    totalWeightedCashFlow += cf.amount * timeDiff;
  }

  // Simplified approximation: MWRR ≈ totalCashFlow / totalWeightedCashFlow - 1
  return totalWeightedCashFlow === 0 ? 0 : totalCashFlow / totalWeightedCashFlow - 1;
}

// Helper functions

function calculateSharpeRatio(returns: number[], riskFreeRate: number = 0): number {
  if (returns.length < 2) {
    return 0;
  }

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

  return stdDev === 0 ? 0 : (avgReturn - riskFreeRate) / stdDev;
}

function calculateVolatility(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);
}

function calculateBeta(portfolioReturns: number[], marketReturns: number[]): number {
  if (portfolioReturns.length !== marketReturns.length || portfolioReturns.length < 2) {
    return 0;
  }

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

  let covariance = 0;
  let marketVariance = 0;

  for (let i = 0; i < portfolioReturns.length; 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;
  }

  covariance /= portfolioReturns.length - 1;
  marketVariance /= marketReturns.length - 1;

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

function calculateAlpha(
  portfolioReturns: number[],
  marketReturns: number[],
  riskFreeRate: number
): number {
  const portfolioMean =
    portfolioReturns.reduce((sum, ret) => sum + ret, 0) / portfolioReturns.length;
  const marketMean = marketReturns.reduce((sum, ret) => sum + ret, 0) / marketReturns.length;
  const beta = calculateBeta(portfolioReturns, marketReturns);

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

function calculateSkewness(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;
}

function calculateKurtosis(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
}

function aggregateMonthlyReturns(returns: Array<{ return: number; date: Date }>): number[] {
  const monthlyReturns: { [key: string]: number } = {};

  for (const ret of returns) {
    const monthKey = `${ret.date.getFullYear()}-${ret.date.getMonth()}`;
    if (!monthlyReturns[monthKey]) {
      monthlyReturns[monthKey] = 1;
    }
    monthlyReturns[monthKey] *= 1 + ret.return;
  }

  return Object.values(monthlyReturns).map(cumReturn => cumReturn - 1);
}
