import { getLogger } from '@stock-bot/logger';

const logger = getLogger('PerformanceAnalyzer');
import * as stats from 'simple-statistics';

export interface Trade {
  entryTime: Date;
  exitTime: Date;
  symbol: string;
  side: 'long' | 'short';
  entryPrice: number;
  exitPrice: number;
  quantity: number;
  commission: number;
  pnl: number;
  returnPct: number;
  holdingPeriod: number; // in minutes
  mae: number; // Maximum Adverse Excursion
  mfe: number; // Maximum Favorable Excursion
}

export interface PerformanceMetrics {
  // Return metrics
  totalReturn: number;
  annualizedReturn: number;
  cagr: number; // Compound Annual Growth Rate
  
  // Risk metrics
  volatility: number;
  downVolatility: number;
  maxDrawdown: number;
  maxDrawdownDuration: number; // days
  var95: number; // Value at Risk 95%
  cvar95: number; // Conditional VaR 95%
  
  // Risk-adjusted returns
  sharpeRatio: number;
  sortinoRatio: number;
  calmarRatio: number;
  informationRatio: number;
  
  // Trade statistics
  totalTrades: number;
  winRate: number;
  avgWin: number;
  avgLoss: number;
  avgWinLoss: number;
  profitFactor: number;
  expectancy: number;
  payoffRatio: number;
  
  // Trade analysis
  avgHoldingPeriod: number;
  avgTradesPerDay: number;
  maxConsecutiveWins: number;
  maxConsecutiveLosses: number;
  largestWin: number;
  largestLoss: number;
  
  // Statistical measures
  skewness: number;
  kurtosis: number;
  tailRatio: number;
  
  // Kelly criterion
  kellyFraction: number;
  optimalLeverage: number;
}

export interface DrawdownAnalysis {
  maxDrawdown: number;
  maxDrawdownDuration: number;
  currentDrawdown: number;
  drawdownPeriods: Array<{
    start: Date;
    end: Date;
    depth: number;
    duration: number;
    recovery: number;
  }>;
  underwaterCurve: Array<{ date: Date; drawdown: number }>;
}

export interface FactorAttribution {
  alpha: number;
  beta: number;
  correlation: number;
  treynorRatio: number;
  trackingError: number;
  upCapture: number;
  downCapture: number;
}

export class PerformanceAnalyzer {
  private equityCurve: Array<{ date: Date; value: number }> = [];
  private trades: Trade[] = [];
  private dailyReturns: number[] = [];
  private benchmarkReturns?: number[];

  constructor(private initialCapital: number = 100000) {}

  addEquityPoint(date: Date, value: number): void {
    this.equityCurve.push({ date, value });
    // Don't calculate daily returns on every point - wait until analyze() is called
  }

  addTrade(trade: Trade): void {
    this.trades.push(trade);
  }

  setBenchmark(returns: number[]): void {
    this.benchmarkReturns = returns;
  }

  analyze(): PerformanceMetrics {
    if (this.equityCurve.length < 2) {
      return this.getEmptyMetrics();
    }

    // Calculate daily returns before analysis
    this.calculateDailyReturns();

    // Calculate returns
    const totalReturn = this.calculateTotalReturn();
    const annualizedReturn = this.calculateAnnualizedReturn();
    const cagr = this.calculateCAGR();

    // Risk metrics
    const volatility = this.calculateVolatility();
    const downVolatility = this.calculateDownsideVolatility();
    const drawdownAnalysis = this.analyzeDrawdowns();
    const { var95, cvar95 } = this.calculateVaR();

    // Risk-adjusted returns
    const sharpeRatio = this.calculateSharpeRatio(annualizedReturn, volatility);
    const sortinoRatio = this.calculateSortinoRatio(annualizedReturn, downVolatility);
    const calmarRatio = drawdownAnalysis.maxDrawdown > 0 ? annualizedReturn / (drawdownAnalysis.maxDrawdown * 100) : 0;
    const informationRatio = this.calculateInformationRatio();

    // Trade statistics
    const tradeStats = this.analyzeTradeStatistics();

    // Statistical measures
    const { skewness, kurtosis } = this.calculateDistributionMetrics();
    const tailRatio = this.calculateTailRatio();

    // Kelly criterion
    const { kellyFraction, optimalLeverage } = this.calculateKellyCriterion(tradeStats);

    return {
      totalReturn,
      annualizedReturn,
      cagr,
      volatility: volatility * 100, // Convert to percentage for display
      downVolatility: downVolatility * 100, // Convert to percentage for display
      maxDrawdown: drawdownAnalysis.maxDrawdown,
      maxDrawdownDuration: drawdownAnalysis.maxDrawdownDuration,
      var95,
      cvar95,
      sharpeRatio,
      sortinoRatio,
      calmarRatio,
      informationRatio,
      ...tradeStats,
      skewness,
      kurtosis,
      tailRatio,
      kellyFraction,
      optimalLeverage
    };
  }

  analyzeDrawdowns(): DrawdownAnalysis {
    const drawdowns: number[] = [];
    const underwaterCurve: Array<{ date: Date; drawdown: number }> = [];
    
    // Handle empty equity curve
    if (this.equityCurve.length === 0) {
      return {
        maxDrawdown: 0,
        averageDrawdown: 0,
        maxDrawdownDuration: 0,
        underwaterTime: 0,
        drawdownPeriods: [],
        currentDrawdown: 0
      };
    }
    
    let peak = this.equityCurve[0].value;
    let maxDrawdown = 0;
    let currentDrawdownStart: Date | null = null;
    let drawdownPeriods: DrawdownAnalysis['drawdownPeriods'] = [];

    for (let i = 0; i < this.equityCurve.length; i++) {
      const point = this.equityCurve[i];
      
      if (point.value > peak) {
        // New peak - end current drawdown if any
        if (currentDrawdownStart) {
          const period = {
            start: currentDrawdownStart,
            end: point.date,
            depth: maxDrawdown,
            duration: this.daysBetween(currentDrawdownStart, point.date),
            recovery: i
          };
          drawdownPeriods.push(period);
          currentDrawdownStart = null;
        }
        peak = point.value;
      }

      const drawdown = (point.value - peak) / peak;
      drawdowns.push(drawdown);
      underwaterCurve.push({ date: point.date, drawdown });

      if (drawdown < 0 && !currentDrawdownStart) {
        currentDrawdownStart = point.date;
      }

      if (drawdown < maxDrawdown) {
        maxDrawdown = drawdown;
      }
    }

    // Handle ongoing drawdown
    const currentDrawdown = drawdowns[drawdowns.length - 1];
    
    // Calculate max drawdown duration
    const maxDrawdownDuration = Math.max(
      ...drawdownPeriods.map(p => p.duration),
      currentDrawdownStart ? this.daysBetween(currentDrawdownStart, new Date()) : 0
    );

    return {
      maxDrawdown: Math.abs(maxDrawdown),
      maxDrawdownDuration,
      currentDrawdown: Math.abs(currentDrawdown),
      drawdownPeriods,
      underwaterCurve
    };
  }

  calculateFactorAttribution(benchmarkReturns: number[]): FactorAttribution {
    if (this.dailyReturns.length !== benchmarkReturns.length) {
      throw new Error('Returns and benchmark must have same length');
    }

    // Calculate beta using linear regression
    const regression = stats.linearRegression(
      this.dailyReturns.map((r, i) => [benchmarkReturns[i], r])
    );
    const beta = regression.m;
    const alpha = regression.b * 252; // Annualized

    // Correlation
    const correlation = stats.sampleCorrelation(this.dailyReturns, benchmarkReturns);

    // Treynor ratio
    const excessReturn = this.calculateAnnualizedReturn() - 0.02; // Assume 2% risk-free
    const treynorRatio = beta !== 0 ? excessReturn / beta : 0;

    // Tracking error
    const returnDiffs = this.dailyReturns.map((r, i) => r - benchmarkReturns[i]);
    const trackingError = stats.standardDeviation(returnDiffs) * Math.sqrt(252);

    // Up/down capture
    const upDays = benchmarkReturns
      .map((r, i) => r > 0 ? { bench: r, port: this.dailyReturns[i] } : null)
      .filter(d => d !== null) as Array<{ bench: number; port: number }>;
    
    const downDays = benchmarkReturns
      .map((r, i) => r < 0 ? { bench: r, port: this.dailyReturns[i] } : null)
      .filter(d => d !== null) as Array<{ bench: number; port: number }>;

    const upCapture = upDays.length > 0 ?
      stats.mean(upDays.map(d => d.port)) / stats.mean(upDays.map(d => d.bench)) : 0;
    
    const downCapture = downDays.length > 0 ?
      stats.mean(downDays.map(d => d.port)) / stats.mean(downDays.map(d => d.bench)) : 0;

    return {
      alpha,
      beta,
      correlation,
      treynorRatio,
      trackingError,
      upCapture,
      downCapture
    };
  }

  private calculateDailyReturns(): void {
    this.dailyReturns = [];
    
    // First, aggregate equity curve by day (taking last value of each day)
    const dailyEquity = new Map<string, { date: Date; value: number }>();
    
    for (const point of this.equityCurve) {
      const dateKey = point.date.toISOString().split('T')[0];
      dailyEquity.set(dateKey, point); // This will keep the last value of each day
    }
    
    // Convert to sorted array
    const dailyPoints = Array.from(dailyEquity.values())
      .sort((a, b) => a.date.getTime() - b.date.getTime());
    
    // Calculate returns from daily points
    for (let i = 1; i < dailyPoints.length; i++) {
      const prevValue = dailyPoints[i - 1].value;
      const currValue = dailyPoints[i].value;
      if (prevValue > 0) {
        this.dailyReturns.push((currValue - prevValue) / prevValue);
      }
    }
  }

  private calculateTotalReturn(): number {
    const finalValue = this.equityCurve[this.equityCurve.length - 1].value;
    return ((finalValue - this.initialCapital) / this.initialCapital) * 100;
  }

  private calculateAnnualizedReturn(): number {
    const totalReturn = this.calculateTotalReturn() / 100;
    const years = this.getYears();
    return (Math.pow(1 + totalReturn, 1 / years) - 1) * 100;
  }

  private calculateCAGR(): number {
    const finalValue = this.equityCurve[this.equityCurve.length - 1].value;
    const years = this.getYears();
    return (Math.pow(finalValue / this.initialCapital, 1 / years) - 1) * 100;
  }

  private calculateVolatility(): number {
    if (this.dailyReturns.length === 0) return 0;
    // Return volatility as a decimal percentage (not multiplied by 100)
    return stats.standardDeviation(this.dailyReturns) * Math.sqrt(252);
  }

  private calculateDownsideVolatility(): number {
    const negativeReturns = this.dailyReturns.filter(r => r < 0);
    if (negativeReturns.length === 0) return 0;
    // Return volatility as a decimal percentage (not multiplied by 100)
    return stats.standardDeviation(negativeReturns) * Math.sqrt(252);
  }

  private calculateVaR(): { var95: number; cvar95: number } {
    if (this.dailyReturns.length === 0) return { var95: 0, cvar95: 0 };
    
    const sortedReturns = [...this.dailyReturns].sort((a, b) => a - b);
    const index95 = Math.floor(sortedReturns.length * 0.05);
    
    // Handle edge case where we don't have enough data points
    if (index95 === 0 || sortedReturns.length < 20) {
      return { var95: 0, cvar95: 0 };
    }
    
    const var95 = Math.abs(sortedReturns[index95]) * 100;
    const tailReturns = sortedReturns.slice(0, index95);
    const cvar95 = tailReturns.length > 0 ? Math.abs(stats.mean(tailReturns)) * 100 : 0;
    
    return { var95, cvar95 };
  }

  private calculateSharpeRatio(annualReturn: number, volatility: number, riskFree: number = 2): number {
    if (volatility === 0) return 0;
    // Both annualReturn and volatility should be in percentage form
    const volatilityPct = volatility * 100;
    return (annualReturn - riskFree) / volatilityPct;
  }

  private calculateSortinoRatio(annualReturn: number, downVolatility: number, riskFree: number = 2): number {
    if (downVolatility === 0) return 0;
    // Both annualReturn and downVolatility should be in percentage form
    const downVolatilityPct = downVolatility * 100;
    return (annualReturn - riskFree) / downVolatilityPct;
  }

  private calculateInformationRatio(): number {
    if (!this.benchmarkReturns || this.benchmarkReturns.length !== this.dailyReturns.length) {
      return 0;
    }

    const excessReturns = this.dailyReturns.map((r, i) => r - this.benchmarkReturns![i]);
    const trackingError = stats.standardDeviation(excessReturns);
    
    if (trackingError === 0) return 0;
    
    const avgExcessReturn = stats.mean(excessReturns);
    return (avgExcessReturn * 252) / (trackingError * Math.sqrt(252));
  }

  private analyzeTradeStatistics(): Partial<PerformanceMetrics> {
    if (this.trades.length === 0) {
      return {
        totalTrades: 0,
        winRate: 0,
        avgWin: 0,
        avgLoss: 0,
        avgWinLoss: 0,
        profitFactor: 0,
        expectancy: 0,
        payoffRatio: 0,
        avgHoldingPeriod: 0,
        avgTradesPerDay: 0,
        maxConsecutiveWins: 0,
        maxConsecutiveLosses: 0,
        largestWin: 0,
        largestLoss: 0
      };
    }

    // Filter out trades with 0 or undefined P&L
    const validTrades = this.trades.filter(t => t.pnl !== undefined && t.pnl !== null);
    const wins = validTrades.filter(t => t.pnl > 0);
    const losses = validTrades.filter(t => t.pnl < 0);
    
    // Log trade analysis for debugging
    logger.debug(`Trade Analysis: Total=${this.trades.length}, Valid=${validTrades.length}, Wins=${wins.length}, Losses=${losses.length}`);

    const totalWins = wins.reduce((sum, t) => sum + t.pnl, 0);
    const totalLosses = Math.abs(losses.reduce((sum, t) => sum + t.pnl, 0));

    const avgWin = wins.length > 0 ? totalWins / wins.length : 0;
    const avgLoss = losses.length > 0 ? totalLosses / losses.length : 0;

    const winRate = validTrades.length > 0 ? (wins.length / validTrades.length) * 100 : 0;
    const profitFactor = totalLosses > 0 ? totalWins / totalLosses : totalWins > 0 ? Infinity : 0;
    const expectancy = (winRate / 100 * avgWin) - ((100 - winRate) / 100 * avgLoss);
    const payoffRatio = avgLoss > 0 ? avgWin / avgLoss : 0;

    // Holding period analysis
    const holdingPeriods = this.trades.map(t => t.holdingPeriod);
    const avgHoldingPeriod = stats.mean(holdingPeriods);

    // Trades per day
    const tradingDays = this.getTradingDays();
    const avgTradesPerDay = tradingDays > 0 ? this.trades.length / tradingDays : 0;

    // Consecutive wins/losses
    const { maxConsecutiveWins, maxConsecutiveLosses } = this.calculateConsecutiveStats();

    // Largest win/loss
    const largestWin = Math.max(...this.trades.map(t => t.pnl), 0);
    const largestLoss = Math.abs(Math.min(...this.trades.map(t => t.pnl), 0));

    return {
      totalTrades: this.trades.length,
      winRate,
      avgWin,
      avgLoss,
      avgWinLoss: avgWin - avgLoss,
      profitFactor,
      expectancy,
      payoffRatio,
      avgHoldingPeriod,
      avgTradesPerDay,
      maxConsecutiveWins,
      maxConsecutiveLosses,
      largestWin,
      largestLoss
    };
  }

  private calculateDistributionMetrics(): { skewness: number; kurtosis: number } {
    if (this.dailyReturns.length < 4) {
      return { skewness: 0, kurtosis: 0 };
    }

    const mean = stats.mean(this.dailyReturns);
    const std = stats.standardDeviation(this.dailyReturns);

    if (std === 0) {
      return { skewness: 0, kurtosis: 0 };
    }

    const n = this.dailyReturns.length;
    
    // Skewness
    const skewSum = this.dailyReturns.reduce((sum, r) => sum + Math.pow((r - mean) / std, 3), 0);
    const skewness = (n / ((n - 1) * (n - 2))) * skewSum;

    // Kurtosis
    const kurtSum = this.dailyReturns.reduce((sum, r) => sum + Math.pow((r - mean) / std, 4), 0);
    const kurtosis = (n * (n + 1) / ((n - 1) * (n - 2) * (n - 3))) * kurtSum - 
                    (3 * (n - 1) * (n - 1)) / ((n - 2) * (n - 3));

    return { skewness, kurtosis };
  }

  private calculateTailRatio(): number {
    if (this.dailyReturns.length < 20) return 0;

    const sorted = [...this.dailyReturns].sort((a, b) => b - a);
    const percentile95 = sorted[Math.floor(sorted.length * 0.05)];
    const percentile5 = sorted[Math.floor(sorted.length * 0.95)];

    return Math.abs(percentile5) > 0 ? percentile95 / Math.abs(percentile5) : 0;
  }

  private calculateKellyCriterion(tradeStats: Partial<PerformanceMetrics>): 
    { kellyFraction: number; optimalLeverage: number } {
    const winRate = (tradeStats.winRate || 0) / 100;
    const payoffRatio = tradeStats.payoffRatio || 0;

    if (payoffRatio === 0) {
      return { kellyFraction: 0, optimalLeverage: 1 };
    }

    // Kelly formula: f = p - q/b
    // where p = win probability, q = loss probability, b = payoff ratio
    const kellyFraction = winRate - (1 - winRate) / payoffRatio;

    // Conservative Kelly (25% of full Kelly)
    const conservativeKelly = Math.max(0, Math.min(0.25, kellyFraction * 0.25));

    // Optimal leverage based on Sharpe ratio
    const sharpe = this.calculateSharpeRatio(
      this.calculateAnnualizedReturn(),
      this.calculateVolatility()
    );
    const optimalLeverage = Math.max(1, Math.min(3, sharpe / 2));

    return { 
      kellyFraction: conservativeKelly, 
      optimalLeverage 
    };
  }

  private calculateConsecutiveStats(): { maxConsecutiveWins: number; maxConsecutiveLosses: number } {
    let maxWins = 0, maxLosses = 0;
    let currentWins = 0, currentLosses = 0;

    for (const trade of this.trades) {
      if (trade.pnl > 0) {
        currentWins++;
        currentLosses = 0;
        maxWins = Math.max(maxWins, currentWins);
      } else if (trade.pnl < 0) {
        currentLosses++;
        currentWins = 0;
        maxLosses = Math.max(maxLosses, currentLosses);
      }
    }

    return { maxConsecutiveWins: maxWins, maxConsecutiveLosses: maxLosses };
  }

  private getYears(): number {
    if (this.equityCurve.length < 2) return 1;
    const start = this.equityCurve[0].date;
    const end = this.equityCurve[this.equityCurve.length - 1].date;
    return this.daysBetween(start, end) / 365;
  }

  private getTradingDays(): number {
    if (this.equityCurve.length < 2) return 0;
    const start = this.equityCurve[0].date;
    const end = this.equityCurve[this.equityCurve.length - 1].date;
    return this.daysBetween(start, end) * (252 / 365); // Approximate trading days
  }

  private daysBetween(start: Date, end: Date): number {
    return (end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24);
  }

  private getEmptyMetrics(): PerformanceMetrics {
    return {
      totalReturn: 0,
      annualizedReturn: 0,
      cagr: 0,
      volatility: 0,
      downVolatility: 0,
      maxDrawdown: 0,
      maxDrawdownDuration: 0,
      var95: 0,
      cvar95: 0,
      sharpeRatio: 0,
      sortinoRatio: 0,
      calmarRatio: 0,
      informationRatio: 0,
      totalTrades: 0,
      winRate: 0,
      avgWin: 0,
      avgLoss: 0,
      avgWinLoss: 0,
      profitFactor: 0,
      expectancy: 0,
      payoffRatio: 0,
      avgHoldingPeriod: 0,
      avgTradesPerDay: 0,
      maxConsecutiveWins: 0,
      maxConsecutiveLosses: 0,
      largestWin: 0,
      largestLoss: 0,
      skewness: 0,
      kurtosis: 0,
      tailRatio: 0,
      kellyFraction: 0,
      optimalLeverage: 1
    };
  }

  exportReport(): string {
    const metrics = this.analyze();
    const drawdowns = this.analyzeDrawdowns();

    return `
# Performance Report

## Summary Statistics
- Total Return: ${metrics.totalReturn.toFixed(2)}%
- Annualized Return: ${metrics.annualizedReturn.toFixed(2)}%
- CAGR: ${metrics.cagr.toFixed(2)}%
- Volatility: ${metrics.volatility.toFixed(2)}%
- Max Drawdown: ${metrics.maxDrawdown.toFixed(2)}%
- Sharpe Ratio: ${metrics.sharpeRatio.toFixed(2)}
- Sortino Ratio: ${metrics.sortinoRatio.toFixed(2)}

## Trade Analysis
- Total Trades: ${metrics.totalTrades}
- Win Rate: ${metrics.winRate.toFixed(1)}%
- Profit Factor: ${metrics.profitFactor.toFixed(2)}
- Average Win: $${metrics.avgWin.toFixed(2)}
- Average Loss: $${metrics.avgLoss.toFixed(2)}
- Expectancy: $${metrics.expectancy.toFixed(2)}

## Risk Metrics
- VaR (95%): ${metrics.var95.toFixed(2)}%
- CVaR (95%): ${metrics.cvar95.toFixed(2)}%
- Downside Volatility: ${metrics.downVolatility.toFixed(2)}%
- Tail Ratio: ${metrics.tailRatio.toFixed(2)}
- Skewness: ${metrics.skewness.toFixed(2)}
- Kurtosis: ${metrics.kurtosis.toFixed(2)}

## Optimal Position Sizing
- Kelly Fraction: ${(metrics.kellyFraction * 100).toFixed(1)}%
- Optimal Leverage: ${metrics.optimalLeverage.toFixed(1)}x
`;
  }
}