/**
 * Position Sizing Calculations
 * Risk-based position sizing methods for trading strategies
 */

export interface PositionSizeParams {
  accountSize: number;
  riskPercentage: number;
  entryPrice: number;
  stopLoss: number;
  leverage?: number;
}

export interface KellyParams {
  winRate: number;
  averageWin: number;
  averageLoss: number;
}

export interface VolatilityParams {
  price: number;
  volatility: number;
  targetVolatility: number;
  lookbackDays: number;
}

/**
 * Calculate position size based on fixed risk percentage
 */
export function fixedRiskPositionSize(params: PositionSizeParams): number {
  const { accountSize, riskPercentage, entryPrice, stopLoss, leverage = 1 } = params;

  // Input validation
  if (accountSize <= 0 || riskPercentage <= 0 || entryPrice <= 0 || leverage <= 0) {
    return 0;
  }
  if (entryPrice === stopLoss) {
    return 0;
  }

  const riskAmount = accountSize * (riskPercentage / 100);
  const riskPerShare = Math.abs(entryPrice - stopLoss);
  const basePositionSize = riskAmount / riskPerShare;

  return Math.floor(basePositionSize * leverage);
}

/**
 * Calculate position size using Kelly Criterion
 */
export function kellyPositionSize(params: KellyParams, accountSize: number): number {
  const { winRate, averageWin, averageLoss } = params;

  // Validate inputs
  if (averageLoss === 0 || winRate <= 0 || winRate >= 1 || averageWin <= 0) {
    return 0;
  }

  const lossRate = 1 - winRate;
  const winLossRatio = averageWin / Math.abs(averageLoss);

  // Correct Kelly formula: f = (bp - q) / b
  // where: b = win/loss ratio, p = win rate, q = loss rate
  const kellyFraction = (winRate * winLossRatio - lossRate) / winLossRatio;

  // Cap Kelly fraction to prevent over-leveraging (max 25% of Kelly recommendation)
  const cappedKelly = Math.max(0, Math.min(kellyFraction * 0.25, 0.25));

  return accountSize * cappedKelly;
}

/**
 * Calculate fractional Kelly position size (more conservative)
 */
export function fractionalKellyPositionSize(
  params: KellyParams,
  accountSize: number,
  fraction: number = 0.25
): number {
  // Input validation
  if (fraction <= 0 || fraction > 1) {
    return 0;
  }

  const fullKelly = kellyPositionSize(params, accountSize);
  return fullKelly * fraction;
}

/**
 * Calculate position size based on volatility targeting
 */
export function volatilityTargetPositionSize(
  params: VolatilityParams,
  accountSize: number
): number {
  const { price, volatility, targetVolatility } = params;

  // Input validation
  if (volatility <= 0 || price <= 0 || targetVolatility <= 0 || accountSize <= 0) {
    return 0;
  }

  const volatilityRatio = targetVolatility / volatility;
  const basePositionValue = accountSize * Math.min(volatilityRatio, 2); // Cap at 2x leverage

  return Math.floor(basePositionValue / price);
}

/**
 * Calculate equal weight position size
 */
export function equalWeightPositionSize(
  accountSize: number,
  numberOfPositions: number,
  price: number
): number {
  // Input validation
  if (numberOfPositions <= 0 || price <= 0 || accountSize <= 0) {
    return 0;
  }

  const positionValue = accountSize / numberOfPositions;
  return Math.floor(positionValue / price);
}

/**
 * Calculate position size based on Average True Range (ATR)
 */
export function atrBasedPositionSize(
  accountSize: number,
  riskPercentage: number,
  atrValue: number,
  atrMultiplier: number = 2,
  price: number
): number {
  if (atrValue === 0 || price === 0) {
    return 0;
  }

  const riskAmount = accountSize * (riskPercentage / 100);
  const stopDistance = atrValue * atrMultiplier;
  const positionSize = riskAmount / stopDistance;

  // Return position size in shares, not dollars
  return Math.floor(positionSize);
}

/**
 * Calculate position size using Van Tharp's expectancy
 */
export function expectancyPositionSize(
  accountSize: number,
  winRate: number,
  averageWin: number,
  averageLoss: number,
  maxRiskPercentage: number = 2
): number {
  // Input validation
  if (accountSize <= 0 || winRate <= 0 || winRate >= 1 || averageWin <= 0 || averageLoss === 0) {
    return 0;
  }

  const expectancy = winRate * averageWin - (1 - winRate) * Math.abs(averageLoss);

  if (expectancy <= 0) {
    return 0;
  }

  // Scale position size based on expectancy relative to average loss
  // Higher expectancy relative to risk allows for larger position
  const expectancyRatio = expectancy / Math.abs(averageLoss);
  const riskPercentage = Math.min(expectancyRatio * 0.5, maxRiskPercentage);

  const positionValue = accountSize * (riskPercentage / 100);
  return positionValue;
}

/**
 * Calculate optimal position size using Monte Carlo simulation
 */
export function monteCarloPositionSize(
  accountSize: number,
  historicalReturns: number[],
  simulations: number = 1000,
  confidenceLevel: number = 0.95
): number {
  if (historicalReturns.length === 0) {
    return 0;
  }

  const outcomes: number[] = [];
  const mean = historicalReturns.reduce((sum, ret) => sum + ret, 0) / historicalReturns.length;
  const variance =
    historicalReturns.reduce((sum, ret) => sum + Math.pow(ret - mean, 2), 0) /
    historicalReturns.length;
  const stdDev = Math.sqrt(variance);

  // Test different position sizes (as fraction of account)
  const testFractions = [0.01, 0.025, 0.05, 0.075, 0.1, 0.15, 0.2, 0.25];
  let optimalFraction = 0;
  let bestSharpe = -Infinity;

  for (const fraction of testFractions) {
    const simOutcomes: number[] = [];

    for (let i = 0; i < simulations; i++) {
      let portfolioValue = accountSize;

      // Simulate trades over a period
      for (let j = 0; j < 50; j++) {
        // 50 trades
        const randomReturn =
          historicalReturns[Math.floor(Math.random() * historicalReturns.length)];
        const positionReturn = randomReturn * fraction;
        portfolioValue = portfolioValue * (1 + positionReturn);
      }

      simOutcomes.push(portfolioValue);
    }

    // Calculate Sharpe ratio for this fraction
    const avgOutcome = simOutcomes.reduce((sum, val) => sum + val, 0) / simOutcomes.length;
    const returns = simOutcomes.map(val => (val - accountSize) / accountSize);
    const avgReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
    const returnStdDev = Math.sqrt(
      returns.reduce((sum, ret) => sum + Math.pow(ret - avgReturn, 2), 0) / returns.length
    );

    const sharpe = returnStdDev > 0 ? avgReturn / returnStdDev : -Infinity;

    if (sharpe > bestSharpe) {
      bestSharpe = sharpe;
      optimalFraction = fraction;
    }
  }

  return accountSize * optimalFraction;
}

/**
 * Calculate position size based on Sharpe ratio optimization
 */
export function sharpeOptimizedPositionSize(
  accountSize: number,
  expectedReturn: number,
  volatility: number,
  riskFreeRate: number = 0.02,
  maxLeverage: number = 3
): number {
  // Input validation
  if (volatility <= 0 || accountSize <= 0 || expectedReturn <= riskFreeRate || maxLeverage <= 0) {
    return 0;
  }
  // Kelly criterion with Sharpe ratio optimization
  const excessReturn = expectedReturn - riskFreeRate;
  const kellyFraction = excessReturn / (volatility * volatility);

  // Apply maximum leverage constraint
  const constrainedFraction = Math.max(0, Math.min(kellyFraction, maxLeverage));

  return accountSize * constrainedFraction;
}

/**
 * Fixed fractional position sizing
 */
export function fixedFractionalPositionSize(
  accountSize: number,
  riskPercentage: number,
  stopLossPercentage: number,
  price: number
): number {
  // Input validation
  if (stopLossPercentage <= 0 || price <= 0 || riskPercentage <= 0 || accountSize <= 0) {
    return 0;
  }

  const riskAmount = accountSize * (riskPercentage / 100);
  const stopLossAmount = price * (stopLossPercentage / 100);

  return Math.floor(riskAmount / stopLossAmount);
}

/**
 * Volatility-adjusted position sizing
 */
export function volatilityAdjustedPositionSize(
  accountSize: number,
  targetVolatility: number,
  assetVolatility: number,
  price: number
): number {
  // Input validation
  if (assetVolatility <= 0 || price <= 0 || targetVolatility <= 0 || accountSize <= 0) {
    return 0;
  }

  const volatilityRatio = targetVolatility / assetVolatility;
  const cappedRatio = Math.min(volatilityRatio, 3); // Cap at 3x leverage
  const positionValue = accountSize * cappedRatio;

  return Math.floor(positionValue / price);
}

/**
 * Calculate position size with correlation adjustment
 */
export function correlationAdjustedPositionSize(
  basePositionSize: number,
  existingPositions: Array<{ size: number; correlation: number }>,
  maxCorrelationRisk: number = 0.3
): number {
  if (existingPositions.length === 0 || basePositionSize <= 0) {
    return basePositionSize;
  }

  // Calculate portfolio correlation risk
  // This should consider the correlation between the new position and existing ones
  const totalCorrelationRisk = existingPositions.reduce((total, position) => {
    // Weight correlation by position size relative to new position
    const relativeSize = position.size / (basePositionSize + position.size);
    return total + relativeSize * Math.abs(position.correlation);
  }, 0);

  // Adjust position size based on correlation risk
  const correlationAdjustment = Math.max(0.1, 1 - totalCorrelationRisk / maxCorrelationRisk);

  return Math.floor(basePositionSize * correlationAdjustment);
}

/**
 * Calculate portfolio heat (total risk across all positions)
 */
export function calculatePortfolioHeat(
  positions: Array<{ value: number; risk: number }>,
  accountSize: number
): number {
  // Input validation
  if (accountSize <= 0 || positions.length === 0) {
    return 0;
  }

  const totalRisk = positions.reduce((sum, position) => {
    // Ensure risk values are positive
    return sum + Math.max(0, position.risk);
  }, 0);

  return Math.min((totalRisk / accountSize) * 100, 100); // Cap at 100%
}

/**
 * Dynamic position sizing based on market conditions
 */
export function dynamicPositionSize(
  basePositionSize: number,
  marketVolatility: number,
  normalVolatility: number,
  drawdownLevel: number,
  maxDrawdownThreshold: number = 0.1
): number {
  // Input validation
  if (basePositionSize <= 0 || marketVolatility <= 0 || normalVolatility <= 0) {
    return 0;
  }
  if (drawdownLevel < 0 || maxDrawdownThreshold <= 0) {
    return basePositionSize;
  }

  // Volatility adjustment - reduce size when volatility is high
  const volatilityAdjustment = Math.min(normalVolatility / marketVolatility, 2); // Cap at 2x

  // Drawdown adjustment - reduce size as drawdown increases
  const normalizedDrawdown = Math.min(drawdownLevel / maxDrawdownThreshold, 1);
  const drawdownAdjustment = Math.max(0.1, 1 - normalizedDrawdown);

  const adjustedSize = basePositionSize * volatilityAdjustment * drawdownAdjustment;
  return Math.floor(Math.max(0, adjustedSize));
}

/**
 * Calculate maximum position size based on liquidity
 */
export function liquidityConstrainedPositionSize(
  desiredPositionSize: number,
  averageDailyVolume: number,
  maxVolumePercentage: number = 0.05,
  price: number
): number {
  if (averageDailyVolume === 0 || price === 0) {
    return 0;
  }

  const maxShares = averageDailyVolume * maxVolumePercentage;

  return Math.min(desiredPositionSize, maxShares);
}

/**
 * Multi-timeframe position sizing
 */
export function multiTimeframePositionSize(
  accountSize: number,
  shortTermSignal: number, // -1 to 1
  mediumTermSignal: number, // -1 to 1
  longTermSignal: number, // -1 to 1
  baseRiskPercentage: number = 1
): number {
  // Input validation
  if (accountSize <= 0 || baseRiskPercentage <= 0) {
    return 0;
  }

  // Clamp signals to valid range
  const clampedShort = Math.max(-1, Math.min(1, shortTermSignal));
  const clampedMedium = Math.max(-1, Math.min(1, mediumTermSignal));
  const clampedLong = Math.max(-1, Math.min(1, longTermSignal));

  // Weight the signals (long-term gets higher weight)
  const weightedSignal = clampedShort * 0.2 + clampedMedium * 0.3 + clampedLong * 0.5;

  // Adjust risk based on signal strength
  const adjustedRisk = baseRiskPercentage * Math.abs(weightedSignal);

  return accountSize * (adjustedRisk / 100);
}

/**
 * Risk parity position sizing
 */
export function riskParityPositionSize(
  assets: Array<{ volatility: number; price: number }>,
  targetRisk: number,
  accountSize: number
): number[] {
  if (assets.length === 0) {
    return [];
  }

  // Calculate inverse volatility weights
  const totalInverseVol = assets.reduce((sum, asset) => {
    if (asset.volatility === 0) {
      return sum;
    }
    return sum + 1 / asset.volatility;
  }, 0);

  if (totalInverseVol === 0) {
    return assets.map(() => 0);
  }

  return assets.map(asset => {
    if (asset.volatility === 0 || asset.price === 0) {
      return 0;
    }
    // Calculate weight based on inverse volatility
    const weight = 1 / asset.volatility / totalInverseVol;

    // The weight itself already accounts for risk parity
    // We just need to scale by target risk once
    const positionValue = accountSize * weight * targetRisk;
    return Math.floor(positionValue / asset.price);
  });
}

/**
 * Validate position size against risk limits
 */
export function validatePositionSize(
  positionSize: number,
  price: number,
  accountSize: number,
  maxPositionPercentage: number = 10,
  maxLeverage: number = 1
): { isValid: boolean; adjustedSize: number; violations: string[] } {
  const violations: string[] = [];
  let adjustedSize = positionSize;

  // Check maximum position percentage
  const positionValue = positionSize * price;
  const positionPercentage = (positionValue / accountSize) * 100;

  if (positionPercentage > maxPositionPercentage) {
    violations.push(`Position exceeds maximum ${maxPositionPercentage}% of account`);
    adjustedSize = (accountSize * maxPositionPercentage) / 100 / price;
  }

  // Check leverage limits
  const leverage = positionValue / accountSize;
  if (leverage > maxLeverage) {
    violations.push(`Position exceeds maximum leverage of ${maxLeverage}x`);
    adjustedSize = Math.min(adjustedSize, (accountSize * maxLeverage) / price);
  }

  // Check minimum position size
  if (adjustedSize < 1 && adjustedSize > 0) {
    violations.push('Position size too small (less than 1 share)');
    adjustedSize = 0;
  }

  return {
    isValid: violations.length === 0,
    adjustedSize: Math.max(0, adjustedSize),
    violations,
  };
}

/**
 * Optimal F position sizing (Ralph Vince's method)
 */
export function optimalFPositionSize(
  accountSize: number,
  historicalReturns: number[],
  maxIterations: number = 100
): number {
  if (historicalReturns.length === 0 || accountSize <= 0) {
    return 0;
  }

  // Convert returns to P&L per unit
  const pnlValues = historicalReturns.map(ret => ret * 1000); // Assuming $1000 per unit

  let bestF = 0;
  let bestTWR = 0; // Terminal Wealth Relative

  // Test different f values (0.01 to 1.00)
  for (let f = 0.01; f <= 1.0; f += 0.01) {
    let twr = 1.0;
    let valid = true;

    for (const pnl of pnlValues) {
      const hpr = 1 + (f * pnl) / 1000; // Holding Period Return

      if (hpr <= 0) {
        valid = false;
        break;
      }

      twr *= hpr;
    }

    if (valid && twr > bestTWR) {
      bestTWR = twr;
      bestF = f;
    }
  }

  // Apply safety factor
  const safeF = bestF * 0.75; // 75% of optimal f for safety

  return accountSize * safeF;
}

/**
 * Secure F position sizing (safer version of Optimal F)
 */
export function secureFPositionSize(
  accountSize: number,
  historicalReturns: number[],
  confidenceLevel: number = 0.95
): number {
  if (historicalReturns.length === 0 || accountSize <= 0) {
    return 0;
  }

  // Sort returns to find worst-case scenarios
  const sortedReturns = [...historicalReturns].sort((a, b) => a - b);
  const worstCaseIndex = Math.floor((1 - confidenceLevel) * sortedReturns.length);
  const worstCaseReturn = sortedReturns[worstCaseIndex];

  // Calculate maximum position size that won't bankrupt at confidence level
  const maxLoss = Math.abs(worstCaseReturn);
  const maxRiskPercentage = 0.02; // Never risk more than 2% on worst case

  if (maxLoss === 0) {
    return accountSize * 0.1;
  } // Default to 10% if no historical losses

  const secureF = Math.min(maxRiskPercentage / maxLoss, 0.25); // Cap at 25%

  return accountSize * secureF;
}
