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

const logger = getLogger('RiskManager');

export interface RiskLimits {
  maxPositions?: number;
  maxPositionSizePct?: number; // Max % of account per position
  maxTotalExposurePct?: number; // Max % of account in all positions
  maxDailyLossPct?: number; // Max daily loss as % of account
  maxDrawdownPct?: number; // Max drawdown allowed
  maxConsecutiveLosses?: number;
  minWinRate?: number; // Minimum win rate to continue trading
}

export interface RiskMetrics {
  currentExposure: number;
  currentExposurePct: number;
  dailyPnl: number;
  dailyPnlPct: number;
  currentDrawdown: number;
  currentDrawdownPct: number;
  maxDrawdown: number;
  maxDrawdownPct: number;
  consecutiveLosses: number;
  volatility: number;
  sharpeRatio: number;
  var95: number; // Value at Risk 95%
}

export interface RiskCheckResult {
  allowed: boolean;
  reason?: string;
  adjustedSize?: number;
}

/**
 * Manages risk limits and calculates risk metrics
 */
export class RiskManager {
  private limits: Required<RiskLimits>;
  private dailyPnl = 0;
  private dailyStartBalance: number;
  private peakBalance: number;
  private consecutiveLosses = 0;
  private dailyReturns: number[] = [];
  private readonly lookbackDays = 30;
  
  constructor(
    private accountBalance: number,
    limits: RiskLimits = {}
  ) {
    this.limits = {
      maxPositions: 10,
      maxPositionSizePct: 0.1,
      maxTotalExposurePct: 0.6,
      maxDailyLossPct: 0.05,
      maxDrawdownPct: 0.2,
      maxConsecutiveLosses: 5,
      minWinRate: 0.3,
      ...limits
    };
    
    this.dailyStartBalance = accountBalance;
    this.peakBalance = accountBalance;
  }
  
  /**
   * Check if a new position is allowed
   */
  checkNewPosition(
    symbol: string,
    proposedSize: number,
    price: number,
    currentPositions: Map<string, { quantity: number; value: number }>
  ): RiskCheckResult {
    const proposedValue = Math.abs(proposedSize * price);
    const proposedPct = proposedValue / this.accountBalance;
    
    // Check max position size
    if (proposedPct > this.limits.maxPositionSizePct) {
      const maxValue = this.accountBalance * this.limits.maxPositionSizePct;
      const adjustedSize = Math.floor(maxValue / price);
      
      return {
        allowed: true,
        reason: `Position size reduced from ${proposedSize} to ${adjustedSize} (max ${(this.limits.maxPositionSizePct * 100).toFixed(1)}% per position)`,
        adjustedSize
      };
    }
    
    // Check max positions
    if (currentPositions.size >= this.limits.maxPositions && !currentPositions.has(symbol)) {
      return {
        allowed: false,
        reason: `Maximum number of positions (${this.limits.maxPositions}) reached`
      };
    }
    
    // Check total exposure
    let totalExposure = proposedValue;
    for (const [sym, pos] of currentPositions) {
      if (sym !== symbol) {
        totalExposure += Math.abs(pos.value);
      }
    }
    
    const totalExposurePct = totalExposure / this.accountBalance;
    if (totalExposurePct > this.limits.maxTotalExposurePct) {
      return {
        allowed: false,
        reason: `Total exposure would be ${(totalExposurePct * 100).toFixed(1)}% (max ${(this.limits.maxTotalExposurePct * 100).toFixed(1)}%)`
      };
    }
    
    // Check daily loss limit
    const dailyLossPct = Math.abs(this.dailyPnl) / this.dailyStartBalance;
    if (this.dailyPnl < 0 && dailyLossPct >= this.limits.maxDailyLossPct) {
      return {
        allowed: false,
        reason: `Daily loss limit reached (${(dailyLossPct * 100).toFixed(1)}%)`
      };
    }
    
    // Check consecutive losses
    if (this.consecutiveLosses >= this.limits.maxConsecutiveLosses) {
      return {
        allowed: false,
        reason: `Maximum consecutive losses (${this.limits.maxConsecutiveLosses}) reached`
      };
    }
    
    // Check drawdown
    const currentDrawdownPct = (this.peakBalance - this.accountBalance) / this.peakBalance;
    if (currentDrawdownPct >= this.limits.maxDrawdownPct) {
      return {
        allowed: false,
        reason: `Maximum drawdown reached (${(currentDrawdownPct * 100).toFixed(1)}%)`
      };
    }
    
    return { allowed: true };
  }
  
  /**
   * Update metrics after a trade
   */
  updateAfterTrade(pnl: number): void {
    this.dailyPnl += pnl;
    this.accountBalance += pnl;
    
    if (pnl < 0) {
      this.consecutiveLosses++;
    } else if (pnl > 0) {
      this.consecutiveLosses = 0;
    }
    
    if (this.accountBalance > this.peakBalance) {
      this.peakBalance = this.accountBalance;
    }
  }
  
  /**
   * Reset daily metrics
   */
  resetDaily(): void {
    // Record daily return
    const dailyReturn = (this.accountBalance - this.dailyStartBalance) / this.dailyStartBalance;
    this.dailyReturns.push(dailyReturn);
    
    // Keep only recent returns
    if (this.dailyReturns.length > this.lookbackDays) {
      this.dailyReturns.shift();
    }
    
    this.dailyPnl = 0;
    this.dailyStartBalance = this.accountBalance;
    
    logger.info(`Daily reset - Balance: $${this.accountBalance.toFixed(2)}, Daily return: ${(dailyReturn * 100).toFixed(2)}%`);
  }
  
  /**
   * Calculate current risk metrics
   */
  getMetrics(currentPositions: Map<string, { quantity: number; value: number }>): RiskMetrics {
    let currentExposure = 0;
    for (const pos of currentPositions.values()) {
      currentExposure += Math.abs(pos.value);
    }
    
    const currentDrawdown = this.peakBalance - this.accountBalance;
    const currentDrawdownPct = this.peakBalance > 0 ? currentDrawdown / this.peakBalance : 0;
    
    return {
      currentExposure,
      currentExposurePct: currentExposure / this.accountBalance,
      dailyPnl: this.dailyPnl,
      dailyPnlPct: this.dailyPnl / this.dailyStartBalance,
      currentDrawdown,
      currentDrawdownPct,
      maxDrawdown: Math.max(currentDrawdown, 0),
      maxDrawdownPct: Math.max(currentDrawdownPct, 0),
      consecutiveLosses: this.consecutiveLosses,
      volatility: this.calculateVolatility(),
      sharpeRatio: this.calculateSharpeRatio(),
      var95: this.calculateVaR(0.95)
    };
  }
  
  /**
   * Update risk limits
   */
  updateLimits(newLimits: Partial<RiskLimits>): void {
    this.limits = { ...this.limits, ...newLimits };
    logger.info('Risk limits updated:', this.limits);
  }
  
  /**
   * Get current limits
   */
  getLimits(): Required<RiskLimits> {
    return { ...this.limits };
  }
  
  /**
   * Calculate portfolio volatility
   */
  private calculateVolatility(): number {
    if (this.dailyReturns.length < 2) return 0;
    
    const mean = this.dailyReturns.reduce((sum, r) => sum + r, 0) / this.dailyReturns.length;
    const variance = this.dailyReturns.reduce((sum, r) => sum + Math.pow(r - mean, 2), 0) / (this.dailyReturns.length - 1);
    
    return Math.sqrt(variance) * Math.sqrt(252); // Annualized
  }
  
  /**
   * Calculate Sharpe ratio
   */
  private calculateSharpeRatio(riskFreeRate = 0.02): number {
    if (this.dailyReturns.length < 2) return 0;
    
    const avgReturn = this.dailyReturns.reduce((sum, r) => sum + r, 0) / this.dailyReturns.length;
    const annualizedReturn = avgReturn * 252;
    const volatility = this.calculateVolatility();
    
    if (volatility === 0) return 0;
    
    return (annualizedReturn - riskFreeRate) / volatility;
  }
  
  /**
   * Calculate Value at Risk
   */
  private calculateVaR(confidence: number): number {
    if (this.dailyReturns.length < 5) return 0;
    
    const sortedReturns = [...this.dailyReturns].sort((a, b) => a - b);
    const index = Math.floor((1 - confidence) * sortedReturns.length);
    
    return Math.abs(sortedReturns[index] * this.accountBalance);
  }
}