import { TechnicalAnalysis, IncrementalIndicators } from '../../indicators/TechnicalAnalysis';
import { getLogger } from '@stock-bot/logger';

const logger = getLogger('IndicatorManager');

export interface IndicatorConfig {
  type: 'sma' | 'ema' | 'rsi' | 'macd' | 'bollinger' | 'stochastic' | 'atr';
  period?: number;
  fastPeriod?: number;
  slowPeriod?: number;
  signalPeriod?: number;
  stdDev?: number;
  kPeriod?: number;
  dPeriod?: number;
  smoothK?: number;
}

export interface PriceData {
  symbol: string;
  timestamp: number;
  open: number;
  high: number;
  low: number;
  close: number;
  volume: number;
}

/**
 * Manages technical indicators for a strategy
 * Handles both batch and incremental calculations
 */
export class IndicatorManager {
  private ta: TechnicalAnalysis;
  private incrementalIndicators: IncrementalIndicators;
  private priceHistory: Map<string, {
    open: number[];
    high: number[];
    low: number[];
    close: number[];
    volume: number[];
  }> = new Map();
  
  private indicatorCache: Map<string, Map<string, any>> = new Map();
  private maxHistoryLength: number;
  
  constructor(maxHistoryLength = 500) {
    this.ta = new TechnicalAnalysis();
    this.incrementalIndicators = new IncrementalIndicators();
    this.maxHistoryLength = maxHistoryLength;
  }
  
  /**
   * Update price history with new data
   */
  updatePrice(data: PriceData): void {
    const { symbol, open, high, low, close, volume } = data;
    
    if (!this.priceHistory.has(symbol)) {
      this.priceHistory.set(symbol, {
        open: [],
        high: [],
        low: [],
        close: [],
        volume: []
      });
    }
    
    const history = this.priceHistory.get(symbol)!;
    
    // Add new data
    history.open.push(open);
    history.high.push(high);
    history.low.push(low);
    history.close.push(close);
    history.volume.push(volume);
    
    // Trim to max length
    if (history.close.length > this.maxHistoryLength) {
      history.open.shift();
      history.high.shift();
      history.low.shift();
      history.close.shift();
      history.volume.shift();
    }
    
    // Clear cache for this symbol as data has changed
    this.indicatorCache.delete(symbol);
    
    // Update incremental indicators
    this.updateIncrementalIndicators(symbol, close);
  }
  
  /**
   * Get price history for a symbol
   */
  getPriceHistory(symbol: string) {
    return this.priceHistory.get(symbol);
  }
  
  /**
   * Get the number of price bars for a symbol
   */
  getHistoryLength(symbol: string): number {
    const history = this.priceHistory.get(symbol);
    return history ? history.close.length : 0;
  }
  
  /**
   * Calculate SMA
   */
  getSMA(symbol: string, period: number): number[] | null {
    const cacheKey = `sma_${period}`;
    return this.getCachedOrCalculate(symbol, cacheKey, () => {
      const history = this.priceHistory.get(symbol);
      if (!history || history.close.length < period) return null;
      return this.ta.sma(history.close, period);
    });
  }
  
  /**
   * Calculate EMA
   */
  getEMA(symbol: string, period: number): number[] | null {
    const cacheKey = `ema_${period}`;
    return this.getCachedOrCalculate(symbol, cacheKey, () => {
      const history = this.priceHistory.get(symbol);
      if (!history || history.close.length < period) return null;
      return this.ta.ema(history.close, period);
    });
  }
  
  /**
   * Calculate RSI
   */
  getRSI(symbol: string, period: number = 14): number[] | null {
    const cacheKey = `rsi_${period}`;
    return this.getCachedOrCalculate(symbol, cacheKey, () => {
      const history = this.priceHistory.get(symbol);
      if (!history || history.close.length < period + 1) return null;
      return this.ta.rsi(history.close, period);
    });
  }
  
  /**
   * Calculate MACD
   */
  getMACD(symbol: string, fastPeriod = 12, slowPeriod = 26, signalPeriod = 9) {
    const cacheKey = `macd_${fastPeriod}_${slowPeriod}_${signalPeriod}`;
    return this.getCachedOrCalculate(symbol, cacheKey, () => {
      const history = this.priceHistory.get(symbol);
      if (!history || history.close.length < slowPeriod + signalPeriod) return null;
      return this.ta.macd(history.close, fastPeriod, slowPeriod, signalPeriod);
    });
  }
  
  /**
   * Calculate Bollinger Bands
   */
  getBollingerBands(symbol: string, period = 20, stdDev = 2) {
    const cacheKey = `bb_${period}_${stdDev}`;
    return this.getCachedOrCalculate(symbol, cacheKey, () => {
      const history = this.priceHistory.get(symbol);
      if (!history || history.close.length < period) return null;
      return this.ta.bollingerBands(history.close, period, stdDev);
    });
  }
  
  /**
   * Calculate Stochastic
   */
  getStochastic(symbol: string, kPeriod = 14, dPeriod = 3, smoothK = 1) {
    const cacheKey = `stoch_${kPeriod}_${dPeriod}_${smoothK}`;
    return this.getCachedOrCalculate(symbol, cacheKey, () => {
      const history = this.priceHistory.get(symbol);
      if (!history || history.close.length < kPeriod) return null;
      return this.ta.stochastic(
        history.high,
        history.low,
        history.close,
        kPeriod,
        dPeriod,
        smoothK
      );
    });
  }
  
  /**
   * Calculate ATR
   */
  getATR(symbol: string, period = 14): number[] | null {
    const cacheKey = `atr_${period}`;
    return this.getCachedOrCalculate(symbol, cacheKey, () => {
      const history = this.priceHistory.get(symbol);
      if (!history || history.close.length < period + 1) return null;
      return this.ta.atr(history.high, history.low, history.close, period);
    });
  }
  
  /**
   * Get latest value from an indicator
   */
  getLatest(values: number[] | null): number | null {
    if (!values || values.length === 0) return null;
    return values[values.length - 1];
  }
  
  /**
   * Check for crossover
   */
  checkCrossover(series1: number[] | null, series2: number[] | null): boolean {
    if (!series1 || !series2) return false;
    return TechnicalAnalysis.crossover(series1, series2);
  }
  
  /**
   * Check for crossunder
   */
  checkCrossunder(series1: number[] | null, series2: number[] | null): boolean {
    if (!series1 || !series2) return false;
    return TechnicalAnalysis.crossunder(series1, series2);
  }
  
  /**
   * Setup incremental indicators
   */
  setupIncrementalIndicator(symbol: string, name: string, config: IndicatorConfig): void {
    const key = `${symbol}_${name}`;
    
    switch (config.type) {
      case 'sma':
        this.incrementalIndicators.createSMA(key, config.period!);
        break;
      case 'ema':
        this.incrementalIndicators.createEMA(key, config.period!);
        break;
      case 'rsi':
        this.incrementalIndicators.createRSI(key, config.period!);
        break;
      default:
        logger.warn(`Incremental indicator type ${config.type} not supported`);
    }
  }
  
  /**
   * Get incremental indicator value
   */
  getIncrementalValue(symbol: string, name: string): number | null {
    const key = `${symbol}_${name}`;
    return this.incrementalIndicators.current(key);
  }
  
  /**
   * Clear all data for a symbol
   */
  clearSymbol(symbol: string): void {
    this.priceHistory.delete(symbol);
    this.indicatorCache.delete(symbol);
    
    // Reset incremental indicators for this symbol
    const indicators = this.incrementalIndicators as any;
    for (const [key, indicator] of indicators.indicators) {
      if (key.startsWith(`${symbol}_`)) {
        if ('reset' in indicator) {
          indicator.reset();
        }
      }
    }
  }
  
  /**
   * Clear all data
   */
  clearAll(): void {
    this.priceHistory.clear();
    this.indicatorCache.clear();
    this.incrementalIndicators.resetAll();
  }
  
  private getCachedOrCalculate<T>(
    symbol: string,
    cacheKey: string,
    calculator: () => T | null
  ): T | null {
    if (!this.indicatorCache.has(symbol)) {
      this.indicatorCache.set(symbol, new Map());
    }
    
    const symbolCache = this.indicatorCache.get(symbol)!;
    
    if (symbolCache.has(cacheKey)) {
      return symbolCache.get(cacheKey);
    }
    
    const result = calculator();
    if (result !== null) {
      symbolCache.set(cacheKey, result);
    }
    
    return result;
  }
  
  private updateIncrementalIndicators(symbol: string, price: number): void {
    // Update all incremental indicators for this symbol
    const indicators = this.incrementalIndicators as any;
    for (const [key] of indicators.indicators) {
      if (key.startsWith(`${symbol}_`)) {
        try {
          this.incrementalIndicators.update(key, price);
        } catch (error) {
          logger.error(`Error updating incremental indicator ${key}:`, error);
        }
      }
    }
  }
}