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

const logger = getLogger('PositionManager');

export interface Position {
  symbol: string;
  quantity: number;
  avgPrice: number;
  currentPrice?: number;
  unrealizedPnl?: number;
  realizedPnl: number;
  openTime: Date;
  lastUpdateTime: Date;
}

export interface Trade {
  symbol: string;
  side: 'buy' | 'sell';
  quantity: number;
  price: number;
  commission: number;
  timestamp: Date;
  pnl?: number;
}

export interface PositionSizingParams {
  accountBalance: number;
  riskPerTrade: number; // As percentage (e.g., 0.02 for 2%)
  stopLossDistance?: number; // Price distance for stop loss
  maxPositionSize?: number; // Max % of account in one position
  volatilityAdjustment?: boolean;
  atr?: number; // For volatility-based sizing
}

/**
 * Manages positions and calculates position sizes
 */
export class PositionManager {
  private positions: Map<string, Position> = new Map();
  private trades: Trade[] = [];
  private accountBalance: number;
  private initialBalance: number;
  
  constructor(initialBalance: number = 100000) {
    this.initialBalance = initialBalance;
    this.accountBalance = initialBalance;
  }
  
  /**
   * Update position with a new trade
   */
  updatePosition(trade: Trade): Position {
    const { symbol, side, quantity, price, commission } = trade;
    let position = this.positions.get(symbol);
    
    if (!position) {
      // New position
      position = {
        symbol,
        quantity: side === 'buy' ? quantity : -quantity,
        avgPrice: price,
        realizedPnl: -commission,
        openTime: trade.timestamp,
        lastUpdateTime: trade.timestamp
      };
    } else {
      const oldQuantity = position.quantity;
      const newQuantity = side === 'buy' 
        ? oldQuantity + quantity 
        : oldQuantity - quantity;
      
      if (Math.sign(oldQuantity) !== Math.sign(newQuantity) && oldQuantity !== 0) {
        // Position flip or close
        const closedQuantity = Math.min(Math.abs(oldQuantity), quantity);
        const pnl = this.calculatePnl(
          position.avgPrice,
          price,
          closedQuantity,
          oldQuantity > 0 ? 'sell' : 'buy'
        );
        
        position.realizedPnl += pnl - commission;
        trade.pnl = pnl - commission;
        
        // Update average price if position continues
        if (Math.abs(newQuantity) > 0.0001) {
          position.avgPrice = price;
        }
      } else if (Math.sign(oldQuantity) === Math.sign(newQuantity) || oldQuantity === 0) {
        // Adding to position
        const totalCost = Math.abs(oldQuantity) * position.avgPrice + quantity * price;
        const totalQuantity = Math.abs(oldQuantity) + quantity;
        position.avgPrice = totalCost / totalQuantity;
        position.realizedPnl -= commission;
      }
      
      position.quantity = newQuantity;
      position.lastUpdateTime = trade.timestamp;
    }
    
    // Store or remove position
    if (Math.abs(position.quantity) < 0.0001) {
      this.positions.delete(symbol);
      logger.info(`Closed position for ${symbol}, realized P&L: $${position.realizedPnl.toFixed(2)}`);
    } else {
      this.positions.set(symbol, position);
    }
    
    // Record trade
    this.trades.push(trade);
    
    // Update account balance
    if (trade.pnl !== undefined) {
      this.accountBalance += trade.pnl;
    }
    
    return position;
  }
  
  /**
   * Get current position for a symbol
   */
  getPosition(symbol: string): Position | undefined {
    return this.positions.get(symbol);
  }
  
  /**
   * Get position quantity
   */
  getPositionQuantity(symbol: string): number {
    const position = this.positions.get(symbol);
    return position ? position.quantity : 0;
  }
  
  /**
   * Check if has position
   */
  hasPosition(symbol: string): boolean {
    const position = this.positions.get(symbol);
    return position !== undefined && Math.abs(position.quantity) > 0.0001;
  }
  
  /**
   * Get all open positions
   */
  getOpenPositions(): Position[] {
    return Array.from(this.positions.values());
  }
  
  /**
   * Update market prices for positions
   */
  updateMarketPrices(prices: Map<string, number>): void {
    for (const [symbol, position] of this.positions) {
      const currentPrice = prices.get(symbol);
      if (currentPrice) {
        position.currentPrice = currentPrice;
        position.unrealizedPnl = this.calculatePnl(
          position.avgPrice,
          currentPrice,
          Math.abs(position.quantity),
          position.quantity > 0 ? 'sell' : 'buy'
        );
      }
    }
  }
  
  /**
   * Calculate position size based on risk parameters
   */
  calculatePositionSize(params: PositionSizingParams, currentPrice: number): number {
    const { 
      accountBalance, 
      riskPerTrade, 
      stopLossDistance, 
      maxPositionSize = 0.25,
      volatilityAdjustment = false,
      atr
    } = params;
    
    let positionSize: number;
    
    if (stopLossDistance && stopLossDistance > 0) {
      // Risk-based position sizing
      const riskAmount = accountBalance * riskPerTrade;
      positionSize = Math.floor(riskAmount / stopLossDistance);
    } else if (volatilityAdjustment && atr) {
      // Volatility-based position sizing
      const riskAmount = accountBalance * riskPerTrade;
      const stopDistance = atr * 2; // 2 ATR stop
      positionSize = Math.floor(riskAmount / stopDistance);
    } else {
      // Fixed percentage position sizing
      const positionValue = accountBalance * riskPerTrade * 10; // Simplified
      positionSize = Math.floor(positionValue / currentPrice);
    }
    
    // Apply max position size limit
    const maxShares = Math.floor((accountBalance * maxPositionSize) / currentPrice);
    positionSize = Math.min(positionSize, maxShares);
    
    // Ensure minimum position size
    return Math.max(1, positionSize);
  }
  
  /**
   * Calculate Kelly Criterion position size
   */
  calculateKellySize(winRate: number, avgWin: number, avgLoss: number, currentPrice: number): number {
    if (avgLoss === 0) return 0;
    
    const b = avgWin / avgLoss;
    const p = winRate;
    const q = 1 - p;
    const kelly = (p * b - q) / b;
    
    // Apply Kelly fraction (usually 0.25 to be conservative)
    const kellyFraction = 0.25;
    const percentageOfCapital = Math.max(0, Math.min(0.25, kelly * kellyFraction));
    
    const positionValue = this.accountBalance * percentageOfCapital;
    return Math.max(1, Math.floor(positionValue / currentPrice));
  }
  
  /**
   * Get performance metrics
   */
  getPerformanceMetrics() {
    const totalTrades = this.trades.length;
    const winningTrades = this.trades.filter(t => (t.pnl || 0) > 0);
    const losingTrades = this.trades.filter(t => (t.pnl || 0) < 0);
    
    const totalPnl = this.trades.reduce((sum, t) => sum + (t.pnl || 0), 0);
    const unrealizedPnl = Array.from(this.positions.values())
      .reduce((sum, p) => sum + (p.unrealizedPnl || 0), 0);
    
    const winRate = totalTrades > 0 ? winningTrades.length / totalTrades : 0;
    const avgWin = winningTrades.length > 0 
      ? winningTrades.reduce((sum, t) => sum + (t.pnl || 0), 0) / winningTrades.length 
      : 0;
    const avgLoss = losingTrades.length > 0 
      ? Math.abs(losingTrades.reduce((sum, t) => sum + (t.pnl || 0), 0) / losingTrades.length)
      : 0;
    
    const profitFactor = avgLoss > 0 ? (avgWin * winningTrades.length) / (avgLoss * losingTrades.length) : 0;
    
    return {
      totalTrades,
      winningTrades: winningTrades.length,
      losingTrades: losingTrades.length,
      winRate: winRate * 100,
      totalPnl,
      unrealizedPnl,
      totalEquity: this.accountBalance + unrealizedPnl,
      avgWin,
      avgLoss,
      profitFactor,
      returnPct: ((this.accountBalance - this.initialBalance) / this.initialBalance) * 100
    };
  }
  
  /**
   * Get account balance
   */
  getAccountBalance(): number {
    return this.accountBalance;
  }
  
  /**
   * Get total equity (balance + unrealized P&L)
   */
  getTotalEquity(): number {
    const unrealizedPnl = Array.from(this.positions.values())
      .reduce((sum, p) => sum + (p.unrealizedPnl || 0), 0);
    return this.accountBalance + unrealizedPnl;
  }
  
  private calculatePnl(
    entryPrice: number, 
    exitPrice: number, 
    quantity: number, 
    side: 'buy' | 'sell'
  ): number {
    if (side === 'sell') {
      return (exitPrice - entryPrice) * quantity;
    } else {
      return (entryPrice - exitPrice) * quantity;
    }
  }
}