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

const logger = getLogger('MarketSimulator');
import { MarketData, Quote, Trade, Bar, OrderBookSnapshot, PriceLevel } from '../types';
import { MarketMicrostructure } from '../types/MarketMicrostructure';

export interface SimulationConfig {
  useHistoricalSpreads: boolean;
  modelHiddenLiquidity: boolean;
  includeDarkPools: boolean;
  latencyMs: number;
  rebateRate: number;
  takeFeeRate: number;
}

export interface LiquidityProfile {
  visibleLiquidity: number;
  hiddenLiquidity: number;
  darkPoolLiquidity: number;
  totalLiquidity: number;
}

export class MarketSimulator {
  private orderBooks: Map<string, OrderBookSnapshot> = new Map();
  private microstructures: Map<string, MarketMicrostructure> = new Map();
  private liquidityProfiles: Map<string, LiquidityProfile> = new Map();
  private lastTrades: Map<string, Trade> = new Map();
  private config: SimulationConfig;

  constructor(config: Partial<SimulationConfig> = {}) {
    this.config = {
      useHistoricalSpreads: true,
      modelHiddenLiquidity: true,
      includeDarkPools: true,
      latencyMs: 1,
      rebateRate: -0.0002, // 2 bps rebate for providing liquidity
      takeFeeRate: 0.0003, // 3 bps fee for taking liquidity
      ...config
    };
  }

  setMicrostructure(symbol: string, microstructure: MarketMicrostructure): void {
    this.microstructures.set(symbol, microstructure);
    this.updateLiquidityProfile(symbol);
  }

  processMarketData(data: MarketData): OrderBookSnapshot | null {
    const { symbol } = this.getSymbolFromData(data);
    
    switch (data.type) {
      case 'quote':
        return this.updateFromQuote(symbol, data.data);
      case 'trade':
        return this.updateFromTrade(symbol, data.data);
      case 'bar':
        return this.reconstructFromBar(symbol, data.data);
      default:
        return null;
    }
  }

  private updateFromQuote(symbol: string, quote: Quote): OrderBookSnapshot {
    let orderbook = this.orderBooks.get(symbol);
    const microstructure = this.microstructures.get(symbol);
    
    if (!orderbook || !microstructure) {
      // Create new orderbook
      orderbook = this.createOrderBook(symbol, quote, microstructure);
    } else {
      // Update existing orderbook
      orderbook = this.updateOrderBook(orderbook, quote, microstructure);
    }
    
    this.orderBooks.set(symbol, orderbook);
    return orderbook;
  }

  private updateFromTrade(symbol: string, trade: Trade): OrderBookSnapshot | null {
    const orderbook = this.orderBooks.get(symbol);
    if (!orderbook) return null;
    
    // Update last trade
    this.lastTrades.set(symbol, trade);
    
    // Adjust orderbook based on trade
    // Large trades likely consumed liquidity
    const impactFactor = Math.min(trade.size / 1000, 0.1); // Max 10% impact
    
    if (trade.side === 'buy') {
      // Buy trade consumed ask liquidity
      orderbook.asks = orderbook.asks.map((level, i) => ({
        ...level,
        size: level.size * (1 - impactFactor * Math.exp(-i * 0.5))
      }));
    } else {
      // Sell trade consumed bid liquidity
      orderbook.bids = orderbook.bids.map((level, i) => ({
        ...level,
        size: level.size * (1 - impactFactor * Math.exp(-i * 0.5))
      }));
    }
    
    return orderbook;
  }

  private reconstructFromBar(symbol: string, bar: Bar): OrderBookSnapshot {
    const microstructure = this.microstructures.get(symbol) || this.createDefaultMicrostructure(symbol);
    
    // Estimate spread from high-low range
    const hlSpread = (bar.high - bar.low) / bar.close;
    const estimatedSpreadBps = Math.max(
      microstructure.avgSpreadBps,
      hlSpread * 10000 * 0.1 // 10% of HL range as spread estimate
    );
    
    // Create synthetic quote
    const midPrice = bar.vwap || (bar.high + bar.low + bar.close) / 3;
    const halfSpread = midPrice * estimatedSpreadBps / 20000;
    
    const quote: Quote = {
      bid: midPrice - halfSpread,
      ask: midPrice + halfSpread,
      bidSize: bar.volume / 100, // Rough estimate
      askSize: bar.volume / 100
    };
    
    return this.createOrderBook(symbol, quote, microstructure);
  }

  private createOrderBook(
    symbol: string,
    topQuote: Quote,
    microstructure?: MarketMicrostructure
  ): OrderBookSnapshot {
    const micro = microstructure || this.createDefaultMicrostructure(symbol);
    const levels = 10;
    
    const bids: PriceLevel[] = [];
    const asks: PriceLevel[] = [];
    
    // Model order book depth
    for (let i = 0; i < levels; i++) {
      const depthFactor = Math.exp(-i * 0.3); // Exponential decay
      const spreadMultiplier = 1 + i * 0.1; // Wider spread at deeper levels
      
      // Hidden liquidity modeling
      const hiddenRatio = this.config.modelHiddenLiquidity ? 
        1 + Math.random() * 2 : // 1-3x visible size hidden
        1;
      
      // Bid levels
      const bidPrice = topQuote.bid - (i * micro.tickSize);
      const bidSize = topQuote.bidSize * depthFactor * (0.8 + Math.random() * 0.4);
      bids.push({
        price: bidPrice,
        size: Math.round(bidSize / micro.lotSize) * micro.lotSize,
        orderCount: Math.max(1, Math.floor(bidSize / 100)),
        hiddenSize: this.config.modelHiddenLiquidity ? bidSize * (hiddenRatio - 1) : undefined
      });
      
      // Ask levels
      const askPrice = topQuote.ask + (i * micro.tickSize);
      const askSize = topQuote.askSize * depthFactor * (0.8 + Math.random() * 0.4);
      asks.push({
        price: askPrice,
        size: Math.round(askSize / micro.lotSize) * micro.lotSize,
        orderCount: Math.max(1, Math.floor(askSize / 100)),
        hiddenSize: this.config.modelHiddenLiquidity ? askSize * (hiddenRatio - 1) : undefined
      });
    }
    
    return {
      symbol,
      timestamp: new Date(),
      bids,
      asks,
      lastTrade: this.lastTrades.get(symbol)
    };
  }

  private updateOrderBook(
    current: OrderBookSnapshot,
    quote: Quote,
    microstructure?: MarketMicrostructure
  ): OrderBookSnapshot {
    const micro = microstructure || this.createDefaultMicrostructure(current.symbol);
    
    // Update top of book
    if (current.bids.length > 0) {
      current.bids[0].price = quote.bid;
      current.bids[0].size = quote.bidSize;
    }
    
    if (current.asks.length > 0) {
      current.asks[0].price = quote.ask;
      current.asks[0].size = quote.askSize;
    }
    
    // Adjust deeper levels based on spread changes
    const oldSpread = current.asks[0].price - current.bids[0].price;
    const newSpread = quote.ask - quote.bid;
    const spreadRatio = newSpread / oldSpread;
    
    // Update deeper levels
    for (let i = 1; i < current.bids.length; i++) {
      // Adjust sizes based on top of book changes
      const sizeRatio = quote.bidSize / (current.bids[0].size || quote.bidSize);
      current.bids[i].size *= sizeRatio * (0.9 + Math.random() * 0.2);
      
      // Adjust prices to maintain relative spacing
      const spacing = (current.bids[i-1].price - current.bids[i].price) * spreadRatio;
      current.bids[i].price = current.bids[i-1].price - spacing;
    }
    
    for (let i = 1; i < current.asks.length; i++) {
      const sizeRatio = quote.askSize / (current.asks[0].size || quote.askSize);
      current.asks[i].size *= sizeRatio * (0.9 + Math.random() * 0.2);
      
      const spacing = (current.asks[i].price - current.asks[i-1].price) * spreadRatio;
      current.asks[i].price = current.asks[i-1].price + spacing;
    }
    
    return current;
  }

  simulateMarketImpact(
    symbol: string,
    side: 'buy' | 'sell',
    orderSize: number,
    orderType: 'market' | 'limit',
    limitPrice?: number
  ): {
    fills: Array<{ price: number; size: number; venue: string }>;
    totalCost: number;
    avgPrice: number;
    marketImpact: number;
    fees: number;
  } {
    const orderbook = this.orderBooks.get(symbol);
    const microstructure = this.microstructures.get(symbol);
    const liquidityProfile = this.liquidityProfiles.get(symbol);
    
    if (!orderbook) {
      throw new Error(`No orderbook available for ${symbol}`);
    }
    
    const fills: Array<{ price: number; size: number; venue: string }> = [];
    let remainingSize = orderSize;
    let totalCost = 0;
    let fees = 0;
    
    // Get relevant price levels
    const levels = side === 'buy' ? orderbook.asks : orderbook.bids;
    const multiplier = side === 'buy' ? 1 : -1;
    
    // Simulate walking the book
    for (const level of levels) {
      if (remainingSize <= 0) break;
      
      // Check limit price constraint
      if (limitPrice !== undefined) {
        if (side === 'buy' && level.price > limitPrice) break;
        if (side === 'sell' && level.price < limitPrice) break;
      }
      
      // Calculate available liquidity including hidden
      let availableSize = level.size;
      if (level.hiddenSize && this.config.modelHiddenLiquidity) {
        availableSize += level.hiddenSize * Math.random(); // Hidden liquidity probabilistic
      }
      
      const fillSize = Math.min(remainingSize, availableSize);
      
      // Simulate latency - price might move
      if (this.config.latencyMs > 0 && orderType === 'market') {
        const priceMovement = microstructure ? 
          (Math.random() - 0.5) * microstructure.avgSpreadBps / 10000 * level.price :
          0;
        level.price += priceMovement * multiplier;
      }
      
      fills.push({
        price: level.price,
        size: fillSize,
        venue: 'primary'
      });
      
      totalCost += fillSize * level.price;
      remainingSize -= fillSize;
      
      // Calculate fees
      if (orderType === 'market') {
        fees += fillSize * level.price * this.config.takeFeeRate;
      } else {
        // Limit orders that provide liquidity get rebate
        fees += fillSize * level.price * this.config.rebateRate;
      }
    }
    
    // Dark pool execution for remaining size
    if (remainingSize > 0 && this.config.includeDarkPools && liquidityProfile) {
      const darkPoolPct = liquidityProfile.darkPoolLiquidity / liquidityProfile.totalLiquidity;
      const darkPoolSize = remainingSize * darkPoolPct * Math.random();
      
      if (darkPoolSize > 0) {
        const midPrice = (orderbook.bids[0].price + orderbook.asks[0].price) / 2;
        fills.push({
          price: midPrice,
          size: darkPoolSize,
          venue: 'dark'
        });
        
        totalCost += darkPoolSize * midPrice;
        remainingSize -= darkPoolSize;
        // Dark pools typically have lower fees
        fees += darkPoolSize * midPrice * 0.0001;
      }
    }
    
    // Calculate results
    const filledSize = orderSize - remainingSize;
    const avgPrice = filledSize > 0 ? totalCost / filledSize : 0;
    
    // Calculate market impact
    const initialMid = (orderbook.bids[0].price + orderbook.asks[0].price) / 2;
    const marketImpact = filledSize > 0 ? 
      Math.abs(avgPrice - initialMid) / initialMid * 10000 : // in bps
      0;
    
    return {
      fills,
      totalCost,
      avgPrice,
      marketImpact,
      fees
    };
  }

  private updateLiquidityProfile(symbol: string): void {
    const microstructure = this.microstructures.get(symbol);
    if (!microstructure) return;
    
    // Estimate liquidity distribution
    const visiblePct = 0.3; // 30% visible
    const hiddenPct = 0.5;  // 50% hidden
    const darkPct = 0.2;    // 20% dark
    
    const totalDailyLiquidity = microstructure.dailyVolume;
    
    this.liquidityProfiles.set(symbol, {
      visibleLiquidity: totalDailyLiquidity * visiblePct,
      hiddenLiquidity: totalDailyLiquidity * hiddenPct,
      darkPoolLiquidity: totalDailyLiquidity * darkPct,
      totalLiquidity: totalDailyLiquidity
    });
  }

  private createDefaultMicrostructure(symbol: string): MarketMicrostructure {
    return {
      symbol,
      avgSpreadBps: 5,
      dailyVolume: 1000000,
      avgTradeSize: 100,
      volatility: 0.02,
      tickSize: 0.01,
      lotSize: 1,
      intradayVolumeProfile: new Array(24).fill(1/24)
    };
  }

  private getSymbolFromData(data: MarketData): { symbol: string } {
    return { symbol: data.data.symbol };
  }

  getOrderBook(symbol: string): OrderBookSnapshot | undefined {
    return this.orderBooks.get(symbol);
  }

  getLiquidityProfile(symbol: string): LiquidityProfile | undefined {
    return this.liquidityProfiles.get(symbol);
  }

  reset(): void {
    this.orderBooks.clear();
    this.lastTrades.clear();
  }
}