import { EventEmitter } from 'events';
import { IServiceContainer } from '@stock-bot/di';
import { OrderRequest, Position } from '../types';
import { StorageService } from '../services/StorageService';
import { MarketDataService } from '../services/MarketDataService';
import { ExecutionService } from '../services/ExecutionService';

interface VirtualAccount {
  balance: number;
  buyingPower: number;
  positions: Map<string, VirtualPosition>;
  orders: Map<string, VirtualOrder>;
  trades: VirtualTrade[];
  equity: number;
  marginUsed: number;
}

interface VirtualPosition {
  symbol: string;
  quantity: number;
  averagePrice: number;
  marketValue: number;
  unrealizedPnl: number;
  realizedPnl: number;
}

interface VirtualOrder {
  id: string;
  symbol: string;
  side: 'buy' | 'sell';
  quantity: number;
  orderType: string;
  limitPrice?: number;
  status: string;
  submittedAt: Date;
}

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

export class PaperTradingManager extends EventEmitter {
  private account: VirtualAccount;
  private marketPrices = new Map<string, { bid: number; ask: number }>();
  private readonly COMMISSION_RATE = 0.001; // 0.1%
  private readonly MARGIN_REQUIREMENT = 0.25; // 25% margin requirement
  private container: IServiceContainer;

  constructor(
    container: IServiceContainer,
    private storageService: StorageService,
    private marketDataService: MarketDataService,
    private executionService: ExecutionService,
    initialBalance: number = 100000
  ) {
    super();
    this.container = container;
    
    this.account = {
      balance: initialBalance,
      buyingPower: initialBalance * (1 / this.MARGIN_REQUIREMENT),
      positions: new Map(),
      orders: new Map(),
      trades: [],
      equity: initialBalance,
      marginUsed: 0
    };
    
    this.setupEventListeners();
  }

  private setupEventListeners(): void {
    // Listen for market data updates to track prices
    // In real implementation, would connect to market data service
  }

  updateMarketPrice(symbol: string, bid: number, ask: number): void {
    this.marketPrices.set(symbol, { bid, ask });
    
    // Update position values
    const position = this.account.positions.get(symbol);
    if (position) {
      const midPrice = (bid + ask) / 2;
      position.marketValue = position.quantity * midPrice;
      position.unrealizedPnl = position.quantity * (midPrice - position.averagePrice);
    }
    
    // Update account equity
    this.updateAccountEquity();
  }

  async executeOrder(order: OrderRequest): Promise<any> {
    // Validate order
    const validation = this.validateOrder(order);
    if (!validation.valid) {
      return {
        status: 'rejected',
        reason: validation.reason
      };
    }

    // Check buying power
    const requiredCapital = this.calculateRequiredCapital(order);
    if (requiredCapital > this.account.buyingPower) {
      return {
        status: 'rejected',
        reason: 'Insufficient buying power'
      };
    }

    // Create virtual order
    const virtualOrder: VirtualOrder = {
      id: `paper_${Date.now()}`,
      symbol: order.symbol,
      side: order.side,
      quantity: order.quantity,
      orderType: order.orderType,
      limitPrice: order.limitPrice,
      status: 'pending',
      submittedAt: new Date()
    };

    this.account.orders.set(virtualOrder.id, virtualOrder);

    // Simulate order execution based on type
    if (order.orderType === 'market') {
      await this.executeMarketOrder(virtualOrder);
    } else if (order.orderType === 'limit') {
      // Limit orders would be checked periodically
      virtualOrder.status = 'accepted';
    }

    return {
      orderId: virtualOrder.id,
      status: virtualOrder.status
    };
  }

  private async executeMarketOrder(order: VirtualOrder): Promise<void> {
    const marketPrice = this.marketPrices.get(order.symbol);
    if (!marketPrice) {
      order.status = 'rejected';
      this.emit('orderUpdate', {
        orderId: order.id,
        status: 'rejected',
        reason: 'No market data available'
      });
      return;
    }

    // Simulate realistic fill with slippage
    const fillPrice = order.side === 'buy' 
      ? marketPrice.ask * (1 + this.getSlippage(order.quantity))
      : marketPrice.bid * (1 - this.getSlippage(order.quantity));

    const commission = fillPrice * order.quantity * this.COMMISSION_RATE;

    // Create trade
    const trade: VirtualTrade = {
      orderId: order.id,
      symbol: order.symbol,
      side: order.side,
      quantity: order.quantity,
      price: fillPrice,
      commission,
      timestamp: new Date()
    };

    // Update position
    this.updatePosition(trade);

    // Update account
    const totalCost = (fillPrice * order.quantity) + commission;
    if (order.side === 'buy') {
      this.account.balance -= totalCost;
    } else {
      this.account.balance += (fillPrice * order.quantity) - commission;
    }

    // Record trade
    this.account.trades.push(trade);
    order.status = 'filled';

    // Update buying power and margin
    this.updateBuyingPower();

    // Emit events
    this.emit('fill', {
      orderId: order.id,
      symbol: order.symbol,
      side: order.side,
      quantity: order.quantity,
      price: fillPrice,
      commission,
      timestamp: new Date()
    });

    this.emit('orderUpdate', {
      orderId: order.id,
      status: 'filled'
    });
  }

  private updatePosition(trade: VirtualTrade): void {
    const position = this.account.positions.get(trade.symbol) || {
      symbol: trade.symbol,
      quantity: 0,
      averagePrice: 0,
      marketValue: 0,
      unrealizedPnl: 0,
      realizedPnl: 0
    };

    const oldQuantity = position.quantity;
    const oldAvgPrice = position.averagePrice;

    if (trade.side === 'buy') {
      // Adding to position
      const newQuantity = oldQuantity + trade.quantity;
      position.averagePrice = oldQuantity >= 0
        ? ((oldQuantity * oldAvgPrice) + (trade.quantity * trade.price)) / newQuantity
        : trade.price;
      position.quantity = newQuantity;
    } else {
      // Reducing position
      const newQuantity = oldQuantity - trade.quantity;
      
      if (oldQuantity > 0) {
        // Realize P&L on closed portion
        const realizedPnl = trade.quantity * (trade.price - oldAvgPrice) - trade.commission;
        position.realizedPnl += realizedPnl;
        trade.pnl = realizedPnl;
      }
      
      position.quantity = newQuantity;
      
      if (Math.abs(newQuantity) < 0.0001) {
        // Position closed
        this.account.positions.delete(trade.symbol);
        return;
      }
    }

    this.account.positions.set(trade.symbol, position);
  }

  private validateOrder(order: OrderRequest): { valid: boolean; reason?: string } {
    if (order.quantity <= 0) {
      return { valid: false, reason: 'Invalid quantity' };
    }

    if (order.orderType === 'limit' && !order.limitPrice) {
      return { valid: false, reason: 'Limit price required for limit orders' };
    }

    return { valid: true };
  }

  private calculateRequiredCapital(order: OrderRequest): number {
    const marketPrice = this.marketPrices.get(order.symbol);
    if (!marketPrice) return Infinity;

    const price = order.side === 'buy' ? marketPrice.ask : marketPrice.bid;
    const notional = price * order.quantity;
    const commission = notional * this.COMMISSION_RATE;
    const marginRequired = notional * this.MARGIN_REQUIREMENT;

    return order.side === 'buy' ? marginRequired + commission : commission;
  }

  private updateBuyingPower(): void {
    let totalMarginUsed = 0;
    
    for (const position of this.account.positions.values()) {
      totalMarginUsed += Math.abs(position.marketValue) * this.MARGIN_REQUIREMENT;
    }

    this.account.marginUsed = totalMarginUsed;
    this.account.buyingPower = (this.account.equity - totalMarginUsed) / this.MARGIN_REQUIREMENT;
  }

  private updateAccountEquity(): void {
    let totalUnrealizedPnl = 0;
    
    for (const position of this.account.positions.values()) {
      totalUnrealizedPnl += position.unrealizedPnl;
    }

    this.account.equity = this.account.balance + totalUnrealizedPnl;
  }

  private getSlippage(quantity: number): number {
    // Simple slippage model - increases with order size
    const baseSlippage = 0.0001; // 1 basis point
    const sizeImpact = quantity / 10000; // Impact increases with size
    return baseSlippage + (sizeImpact * 0.0001);
  }

  checkLimitOrders(): void {
    // Called periodically to check if limit orders can be filled
    for (const [orderId, order] of this.account.orders) {
      if (order.status !== 'accepted' || order.orderType !== 'limit') continue;

      const marketPrice = this.marketPrices.get(order.symbol);
      if (!marketPrice) continue;

      const canFill = order.side === 'buy'
        ? marketPrice.ask <= order.limitPrice!
        : marketPrice.bid >= order.limitPrice!;

      if (canFill) {
        this.executeMarketOrder(order);
      }
    }
  }

  getAccount(): VirtualAccount {
    return { ...this.account };
  }

  getPosition(symbol: string): VirtualPosition | undefined {
    return this.account.positions.get(symbol);
  }

  getAllPositions(): VirtualPosition[] {
    return Array.from(this.account.positions.values());
  }

  getPerformanceMetrics(): any {
    const totalTrades = this.account.trades.length;
    const winningTrades = this.account.trades.filter(t => t.pnl && t.pnl > 0);
    const losingTrades = this.account.trades.filter(t => t.pnl && t.pnl < 0);
    
    const totalPnl = this.account.trades.reduce((sum, t) => sum + (t.pnl || 0), 0);
    const totalCommission = this.account.trades.reduce((sum, t) => sum + t.commission, 0);
    
    return {
      totalTrades,
      winningTrades: winningTrades.length,
      losingTrades: losingTrades.length,
      winRate: totalTrades > 0 ? (winningTrades.length / totalTrades) * 100 : 0,
      totalPnl,
      totalCommission,
      netPnl: totalPnl - totalCommission,
      currentEquity: this.account.equity,
      currentPositions: this.account.positions.size
    };
  }

  reset(): void {
    const initialBalance = this.account.balance + 
      Array.from(this.account.positions.values())
        .reduce((sum, p) => sum + p.marketValue, 0);
    
    this.account = {
      balance: initialBalance,
      buyingPower: initialBalance * (1 / this.MARGIN_REQUIREMENT),
      positions: new Map(),
      orders: new Map(),
      trades: [],
      equity: initialBalance,
      marginUsed: 0
    };
    
    this.container.logger.info('Paper trading account reset');
  }
}