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

const logger = getLogger('SignalManager');

export interface SignalRule {
  name: string;
  condition: (indicators: any) => boolean;
  weight: number;
  direction: 'buy' | 'sell' | 'both';
}

export interface SignalFilter {
  name: string;
  filter: (signal: TradingSignal, context: any) => boolean;
}

export interface TradingSignal {
  symbol: string;
  timestamp: number;
  direction: 'buy' | 'sell' | 'neutral';
  strength: number; // -1 to 1 (-1 = strong sell, 1 = strong buy)
  confidence: number; // 0 to 1
  rules: string[]; // Rules that triggered
  indicators: Record<string, number>;
  metadata?: any;
}

export interface SignalAggregation {
  method: 'weighted' | 'majority' | 'unanimous' | 'threshold';
  threshold?: number; // For threshold method
}

/**
 * Manages trading signals and rules
 */
export class SignalManager {
  private rules: SignalRule[] = [];
  private filters: SignalFilter[] = [];
  private signalHistory: TradingSignal[] = [];
  private maxHistorySize = 1000;
  
  constructor(
    private aggregation: SignalAggregation = { method: 'weighted' }
  ) {}
  
  /**
   * Add a signal rule
   */
  addRule(rule: SignalRule): void {
    this.rules.push(rule);
    logger.info(`Added signal rule: ${rule.name}`);
  }
  
  /**
   * Add multiple rules
   */
  addRules(rules: SignalRule[]): void {
    rules.forEach(rule => this.addRule(rule));
  }
  
  /**
   * Add a signal filter
   */
  addFilter(filter: SignalFilter): void {
    this.filters.push(filter);
    logger.info(`Added signal filter: ${filter.name}`);
  }
  
  /**
   * Remove a rule by name
   */
  removeRule(name: string): void {
    this.rules = this.rules.filter(r => r.name !== name);
  }
  
  /**
   * Generate signal based on indicators
   */
  generateSignal(
    symbol: string,
    timestamp: number,
    indicators: Record<string, number>,
    context: any = {}
  ): TradingSignal | null {
    const triggeredRules: { rule: SignalRule; triggered: boolean }[] = [];
    
    // Check each rule
    for (const rule of this.rules) {
      try {
        const triggered = rule.condition(indicators);
        if (triggered) {
          triggeredRules.push({ rule, triggered: true });
        }
      } catch (error) {
        logger.error(`Error evaluating rule ${rule.name}:`, error);
      }
    }
    
    if (triggeredRules.length === 0) {
      return null;
    }
    
    // Aggregate signals based on method
    const signal = this.aggregateSignals(symbol, timestamp, indicators, triggeredRules);
    
    if (!signal) return null;
    
    // Apply filters
    for (const filter of this.filters) {
      try {
        if (!filter.filter(signal, context)) {
          logger.debug(`Signal filtered by ${filter.name}`);
          return null;
        }
      } catch (error) {
        logger.error(`Error applying filter ${filter.name}:`, error);
      }
    }
    
    // Store in history
    this.addToHistory(signal);
    
    return signal;
  }
  
  /**
   * Aggregate multiple rule triggers into a single signal
   */
  private aggregateSignals(
    symbol: string,
    timestamp: number,
    indicators: Record<string, number>,
    triggeredRules: { rule: SignalRule; triggered: boolean }[]
  ): TradingSignal | null {
    let buyWeight = 0;
    let sellWeight = 0;
    let totalWeight = 0;
    const rules: string[] = [];
    
    for (const { rule } of triggeredRules) {
      rules.push(rule.name);
      totalWeight += Math.abs(rule.weight);
      
      if (rule.direction === 'buy' || rule.direction === 'both') {
        buyWeight += rule.weight;
      }
      if (rule.direction === 'sell' || rule.direction === 'both') {
        sellWeight += rule.weight;
      }
    }
    
    let direction: 'buy' | 'sell' | 'neutral' = 'neutral';
    let strength = 0;
    let confidence = 0;
    
    switch (this.aggregation.method) {
      case 'weighted':
        const netWeight = buyWeight - sellWeight;
        strength = totalWeight > 0 ? netWeight / totalWeight : 0;
        confidence = Math.min(triggeredRules.length / this.rules.length, 1);
        
        if (Math.abs(strength) > 0.1) {
          direction = strength > 0 ? 'buy' : 'sell';
        }
        break;
        
      case 'majority':
        const buyCount = triggeredRules.filter(t => 
          t.rule.direction === 'buy' || t.rule.direction === 'both'
        ).length;
        const sellCount = triggeredRules.filter(t => 
          t.rule.direction === 'sell' || t.rule.direction === 'both'
        ).length;
        
        if (buyCount > sellCount) {
          direction = 'buy';
          strength = buyCount / triggeredRules.length;
        } else if (sellCount > buyCount) {
          direction = 'sell';
          strength = -sellCount / triggeredRules.length;
        }
        confidence = triggeredRules.length / this.rules.length;
        break;
        
      case 'unanimous':
        const allBuy = triggeredRules.every(t => 
          t.rule.direction === 'buy' || t.rule.direction === 'both'
        );
        const allSell = triggeredRules.every(t => 
          t.rule.direction === 'sell' || t.rule.direction === 'both'
        );
        
        if (allBuy && triggeredRules.length >= 2) {
          direction = 'buy';
          strength = 1;
          confidence = 1;
        } else if (allSell && triggeredRules.length >= 2) {
          direction = 'sell';
          strength = -1;
          confidence = 1;
        }
        break;
        
      case 'threshold':
        const threshold = this.aggregation.threshold || 0.7;
        const avgWeight = totalWeight > 0 ? (buyWeight - sellWeight) / totalWeight : 0;
        
        if (avgWeight >= threshold) {
          direction = 'buy';
          strength = avgWeight;
          confidence = triggeredRules.length / this.rules.length;
        } else if (avgWeight <= -threshold) {
          direction = 'sell';
          strength = avgWeight;
          confidence = triggeredRules.length / this.rules.length;
        }
        break;
    }
    
    if (direction === 'neutral') {
      return null;
    }
    
    return {
      symbol,
      timestamp,
      direction,
      strength,
      confidence,
      rules,
      indicators
    };
  }
  
  /**
   * Get recent signals for a symbol
   */
  getRecentSignals(symbol: string, count = 10): TradingSignal[] {
    return this.signalHistory
      .filter(s => s.symbol === symbol)
      .slice(-count);
  }
  
  /**
   * Get signal statistics
   */
  getSignalStats(symbol?: string) {
    const signals = symbol 
      ? this.signalHistory.filter(s => s.symbol === symbol)
      : this.signalHistory;
    
    const buySignals = signals.filter(s => s.direction === 'buy');
    const sellSignals = signals.filter(s => s.direction === 'sell');
    
    const avgBuyStrength = buySignals.length > 0
      ? buySignals.reduce((sum, s) => sum + s.strength, 0) / buySignals.length
      : 0;
    
    const avgSellStrength = sellSignals.length > 0
      ? sellSignals.reduce((sum, s) => sum + Math.abs(s.strength), 0) / sellSignals.length
      : 0;
    
    const avgConfidence = signals.length > 0
      ? signals.reduce((sum, s) => sum + s.confidence, 0) / signals.length
      : 0;
    
    return {
      totalSignals: signals.length,
      buySignals: buySignals.length,
      sellSignals: sellSignals.length,
      avgBuyStrength,
      avgSellStrength,
      avgConfidence,
      ruleHitRate: this.calculateRuleHitRate()
    };
  }
  
  /**
   * Clear signal history
   */
  clearHistory(): void {
    this.signalHistory = [];
  }
  
  private addToHistory(signal: TradingSignal): void {
    this.signalHistory.push(signal);
    
    if (this.signalHistory.length > this.maxHistorySize) {
      this.signalHistory.shift();
    }
  }
  
  private calculateRuleHitRate(): Record<string, number> {
    const ruleHits: Record<string, number> = {};
    
    for (const signal of this.signalHistory) {
      for (const rule of signal.rules) {
        ruleHits[rule] = (ruleHits[rule] || 0) + 1;
      }
    }
    
    const hitRate: Record<string, number> = {};
    for (const [rule, hits] of Object.entries(ruleHits)) {
      hitRate[rule] = this.signalHistory.length > 0 
        ? hits / this.signalHistory.length 
        : 0;
    }
    
    return hitRate;
  }
}

/**
 * Common signal rules
 */
export const CommonRules = {
  // Moving Average Rules
  goldenCross: (fastMA: string, slowMA: string): SignalRule => ({
    name: `Golden Cross (${fastMA}/${slowMA})`,
    condition: (indicators) => {
      const fast = indicators[fastMA];
      const slow = indicators[slowMA];
      const prevFast = indicators[`${fastMA}_prev`];
      const prevSlow = indicators[`${slowMA}_prev`];
      return prevFast <= prevSlow && fast > slow;
    },
    weight: 1,
    direction: 'buy'
  }),
  
  deathCross: (fastMA: string, slowMA: string): SignalRule => ({
    name: `Death Cross (${fastMA}/${slowMA})`,
    condition: (indicators) => {
      const fast = indicators[fastMA];
      const slow = indicators[slowMA];
      const prevFast = indicators[`${fastMA}_prev`];
      const prevSlow = indicators[`${slowMA}_prev`];
      return prevFast >= prevSlow && fast < slow;
    },
    weight: 1,
    direction: 'sell'
  }),
  
  // RSI Rules
  rsiOversold: (threshold = 30): SignalRule => ({
    name: `RSI Oversold (<${threshold})`,
    condition: (indicators) => indicators.rsi < threshold,
    weight: 0.5,
    direction: 'buy'
  }),
  
  rsiOverbought: (threshold = 70): SignalRule => ({
    name: `RSI Overbought (>${threshold})`,
    condition: (indicators) => indicators.rsi > threshold,
    weight: 0.5,
    direction: 'sell'
  }),
  
  // MACD Rules
  macdBullishCross: (): SignalRule => ({
    name: 'MACD Bullish Cross',
    condition: (indicators) => {
      return indicators.macd_prev < indicators.macd_signal_prev && 
             indicators.macd > indicators.macd_signal;
    },
    weight: 0.8,
    direction: 'buy'
  }),
  
  macdBearishCross: (): SignalRule => ({
    name: 'MACD Bearish Cross',
    condition: (indicators) => {
      return indicators.macd_prev > indicators.macd_signal_prev && 
             indicators.macd < indicators.macd_signal;
    },
    weight: 0.8,
    direction: 'sell'
  }),
  
  // Bollinger Band Rules
  bollingerSqueeze: (threshold = 0.02): SignalRule => ({
    name: `Bollinger Squeeze (<${threshold})`,
    condition: (indicators) => {
      const bandwidth = (indicators.bb_upper - indicators.bb_lower) / indicators.bb_middle;
      return bandwidth < threshold;
    },
    weight: 0.3,
    direction: 'both'
  }),
  
  priceAtLowerBand: (): SignalRule => ({
    name: 'Price at Lower Bollinger Band',
    condition: (indicators) => {
      const bbPercent = (indicators.price - indicators.bb_lower) / 
                       (indicators.bb_upper - indicators.bb_lower);
      return bbPercent < 0.05;
    },
    weight: 0.6,
    direction: 'buy'
  }),
  
  priceAtUpperBand: (): SignalRule => ({
    name: 'Price at Upper Bollinger Band',
    condition: (indicators) => {
      const bbPercent = (indicators.price - indicators.bb_lower) / 
                       (indicators.bb_upper - indicators.bb_lower);
      return bbPercent > 0.95;
    },
    weight: 0.6,
    direction: 'sell'
  })
};

/**
 * Common signal filters
 */
export const CommonFilters = {
  // Minimum signal strength
  minStrength: (threshold = 0.5): SignalFilter => ({
    name: `Min Strength (${threshold})`,
    filter: (signal) => Math.abs(signal.strength) >= threshold
  }),
  
  // Minimum confidence
  minConfidence: (threshold = 0.3): SignalFilter => ({
    name: `Min Confidence (${threshold})`,
    filter: (signal) => signal.confidence >= threshold
  }),
  
  // Time of day filter
  tradingHours: (startHour = 9.5, endHour = 16): SignalFilter => ({
    name: `Trading Hours (${startHour}-${endHour})`,
    filter: (signal) => {
      const date = new Date(signal.timestamp);
      const hour = date.getUTCHours() + date.getUTCMinutes() / 60;
      return hour >= startHour && hour < endHour;
    }
  }),
  
  // Trend alignment
  trendAlignment: (trendIndicator = 'sma200'): SignalFilter => ({
    name: `Trend Alignment (${trendIndicator})`,
    filter: (signal) => {
      const trend = signal.indicators[trendIndicator];
      const price = signal.indicators.price;
      if (!trend || !price) return true;
      
      // Buy signals only above trend, sell signals only below
      if (signal.direction === 'buy') {
        return price > trend;
      } else if (signal.direction === 'sell') {
        return price < trend;
      }
      return true;
    }
  }),
  
  // Volume confirmation
  volumeConfirmation: (multiplier = 1.5): SignalFilter => ({
    name: `Volume Confirmation (${multiplier}x)`,
    filter: (signal) => {
      const volume = signal.indicators.volume;
      const avgVolume = signal.indicators.avg_volume;
      if (!volume || !avgVolume) return true;
      
      return volume >= avgVolume * multiplier;
    }
  })
};