/**
 * ML Integration Service
 * Connects to the FastAPI ML Engine for trading signals and predictions
 */

import axios, { AxiosInstance, AxiosError } from 'axios';
import { EventEmitter } from 'events';

// ============================================================================
// Types
// ============================================================================

export type SignalType = 'buy' | 'sell' | 'hold';
export type TimeHorizon = 'scalp' | 'intraday' | 'swing';
export type AMDPhase = 'accumulation' | 'manipulation' | 'distribution' | 'unknown';

export interface MLSignal {
  id: string;
  symbol: string;
  timestamp: Date;
  signalType: SignalType;
  confidence: number;
  timeHorizon: TimeHorizon;
  amdPhase: AMDPhase;
  indicators: MLIndicators;
  prediction: MLPrediction;
  reasoning: string;
  expiresAt: Date;
}

export interface MLIndicators {
  rsi: number;
  macd: {
    value: number;
    signal: number;
    histogram: number;
  };
  atr: number;
  atrPercent: number;
  volumeRatio: number;
  ema20: number;
  ema50: number;
  ema200: number;
  bollingerBands: {
    upper: number;
    middle: number;
    lower: number;
    width: number;
    percentB: number;
  };
  supportLevels: number[];
  resistanceLevels: number[];
}

export interface MLPrediction {
  targetPrice: number;
  expectedHigh: number;
  expectedLow: number;
  stopLoss: number;
  takeProfit: number;
  riskRewardRatio: number;
  probabilityUp: number;
  probabilityDown: number;
  volatilityForecast: number;
}

export interface ModelHealth {
  status: 'healthy' | 'degraded' | 'unhealthy';
  lastTraining: Date;
  accuracy: number;
  precision: number;
  recall: number;
  f1Score: number;
  totalPredictions: number;
  correctPredictions: number;
  uptime: number;
}

export interface BacktestResult {
  symbol: string;
  startDate: Date;
  endDate: Date;
  initialCapital: number;
  finalCapital: number;
  totalReturn: number;
  annualizedReturn: number;
  maxDrawdown: number;
  sharpeRatio: number;
  winRate: number;
  totalTrades: number;
  profitFactor: number;
  trades: BacktestTrade[];
}

export interface BacktestTrade {
  entryTime: Date;
  exitTime: Date;
  side: 'long' | 'short';
  entryPrice: number;
  exitPrice: number;
  quantity: number;
  pnl: number;
  pnlPercent: number;
  exitReason: 'take_profit' | 'stop_loss' | 'signal' | 'expiry';
}

export interface MLEngineConfig {
  baseUrl: string;
  apiKey?: string;
  timeout: number;
  retryAttempts: number;
  retryDelay: number;
}

// ============================================================================
// Default Configuration
// ============================================================================

const DEFAULT_CONFIG: MLEngineConfig = {
  baseUrl: process.env.ML_ENGINE_URL || 'http://localhost:8000',
  apiKey: process.env.ML_ENGINE_API_KEY,
  timeout: 30000,
  retryAttempts: 3,
  retryDelay: 1000,
};

// ============================================================================
// ML Integration Service
// ============================================================================

class MLIntegrationService extends EventEmitter {
  private client: AxiosInstance;
  private config: MLEngineConfig;
  private isConnected: boolean = false;
  private reconnectInterval: NodeJS.Timeout | null = null;

  constructor(config: Partial<MLEngineConfig> = {}) {
    super();
    this.config = { ...DEFAULT_CONFIG, ...config };

    this.client = axios.create({
      baseURL: this.config.baseUrl,
      timeout: this.config.timeout,
      headers: {
        'Content-Type': 'application/json',
        ...(this.config.apiKey && { 'X-API-Key': this.config.apiKey }),
      },
    });

    // Add response interceptor for error handling
    this.client.interceptors.response.use(
      (response) => response,
      (error: AxiosError) => this.handleError(error)
    );
  }

  // ==========================================================================
  // Connection Management
  // ==========================================================================

  /**
   * Check ML Engine connection
   */
  async checkConnection(): Promise<boolean> {
    try {
      const response = await this.client.get('/health');
      this.isConnected = response.data.status === 'healthy';
      this.emit('connectionStatus', this.isConnected);
      return this.isConnected;
    } catch {
      this.isConnected = false;
      this.emit('connectionStatus', false);
      return false;
    }
  }

  /**
   * Get ML Engine health status
   */
  async getHealth(): Promise<ModelHealth> {
    const response = await this.client.get('/health');
    return {
      status: response.data.status,
      lastTraining: new Date(response.data.last_training),
      accuracy: response.data.accuracy,
      precision: response.data.precision,
      recall: response.data.recall,
      f1Score: response.data.f1_score,
      totalPredictions: response.data.total_predictions,
      correctPredictions: response.data.correct_predictions,
      uptime: response.data.uptime,
    };
  }

  /**
   * Start reconnection monitoring
   */
  startReconnectMonitor(intervalMs: number = 30000): void {
    if (this.reconnectInterval) return;

    this.reconnectInterval = setInterval(async () => {
      if (!this.isConnected) {
        console.log('[MLService] Attempting to reconnect to ML Engine...');
        await this.checkConnection();
      }
    }, intervalMs);
  }

  /**
   * Stop reconnection monitoring
   */
  stopReconnectMonitor(): void {
    if (this.reconnectInterval) {
      clearInterval(this.reconnectInterval);
      this.reconnectInterval = null;
    }
  }

  // ==========================================================================
  // Signal Generation
  // ==========================================================================

  /**
   * Get current trading signal for a symbol
   */
  async getSignal(symbol: string, timeHorizon: TimeHorizon = 'intraday'): Promise<MLSignal> {
    const response = await this.client.get(`/signals/${symbol}`, {
      params: { time_horizon: timeHorizon },
    });

    return this.transformSignal(response.data);
  }

  /**
   * Get signals for multiple symbols
   */
  async getSignals(
    symbols: string[],
    timeHorizon: TimeHorizon = 'intraday'
  ): Promise<MLSignal[]> {
    const response = await this.client.post('/signals/batch', {
      symbols,
      time_horizon: timeHorizon,
    });

    return response.data.signals.map(this.transformSignal);
  }

  /**
   * Get historical signals
   */
  async getHistoricalSignals(
    symbol: string,
    options: {
      startTime?: Date;
      endTime?: Date;
      limit?: number;
      signalType?: SignalType;
    } = {}
  ): Promise<MLSignal[]> {
    const response = await this.client.get(`/signals/${symbol}/history`, {
      params: {
        start_time: options.startTime?.toISOString(),
        end_time: options.endTime?.toISOString(),
        limit: options.limit || 100,
        signal_type: options.signalType,
      },
    });

    return response.data.signals.map(this.transformSignal);
  }

  // ==========================================================================
  // Predictions
  // ==========================================================================

  /**
   * Get price prediction for a symbol
   */
  async getPrediction(
    symbol: string,
    horizonMinutes: number = 90
  ): Promise<MLPrediction> {
    const response = await this.client.get(`/predictions/${symbol}`, {
      params: { horizon_minutes: horizonMinutes },
    });

    return {
      targetPrice: response.data.target_price,
      expectedHigh: response.data.expected_high,
      expectedLow: response.data.expected_low,
      stopLoss: response.data.stop_loss,
      takeProfit: response.data.take_profit,
      riskRewardRatio: response.data.risk_reward_ratio,
      probabilityUp: response.data.probability_up,
      probabilityDown: response.data.probability_down,
      volatilityForecast: response.data.volatility_forecast,
    };
  }

  /**
   * Get AMD phase prediction
   */
  async getAMDPhase(symbol: string): Promise<{
    phase: AMDPhase;
    confidence: number;
    expectedDuration: number;
    nextPhase: AMDPhase;
  }> {
    const response = await this.client.get(`/amd/${symbol}`);

    return {
      phase: response.data.phase,
      confidence: response.data.confidence,
      expectedDuration: response.data.expected_duration,
      nextPhase: response.data.next_phase,
    };
  }

  // ==========================================================================
  // Indicators
  // ==========================================================================

  /**
   * Get technical indicators for a symbol
   */
  async getIndicators(symbol: string): Promise<MLIndicators> {
    const response = await this.client.get(`/indicators/${symbol}`);

    return {
      rsi: response.data.rsi,
      macd: {
        value: response.data.macd.value,
        signal: response.data.macd.signal,
        histogram: response.data.macd.histogram,
      },
      atr: response.data.atr,
      atrPercent: response.data.atr_percent,
      volumeRatio: response.data.volume_ratio,
      ema20: response.data.ema_20,
      ema50: response.data.ema_50,
      ema200: response.data.ema_200,
      bollingerBands: {
        upper: response.data.bollinger.upper,
        middle: response.data.bollinger.middle,
        lower: response.data.bollinger.lower,
        width: response.data.bollinger.width,
        percentB: response.data.bollinger.percent_b,
      },
      supportLevels: response.data.support_levels,
      resistanceLevels: response.data.resistance_levels,
    };
  }

  // ==========================================================================
  // Backtesting
  // ==========================================================================

  /**
   * Run backtest for a strategy
   */
  async runBacktest(
    symbol: string,
    options: {
      startDate: Date;
      endDate: Date;
      initialCapital?: number;
      strategy?: string;
      params?: Record<string, unknown>;
    }
  ): Promise<BacktestResult> {
    const response = await this.client.post('/backtest', {
      symbol,
      start_date: options.startDate.toISOString(),
      end_date: options.endDate.toISOString(),
      initial_capital: options.initialCapital || 10000,
      strategy: options.strategy || 'default',
      params: options.params || {},
    });

    return {
      symbol: response.data.symbol,
      startDate: new Date(response.data.start_date),
      endDate: new Date(response.data.end_date),
      initialCapital: response.data.initial_capital,
      finalCapital: response.data.final_capital,
      totalReturn: response.data.total_return,
      annualizedReturn: response.data.annualized_return,
      maxDrawdown: response.data.max_drawdown,
      sharpeRatio: response.data.sharpe_ratio,
      winRate: response.data.win_rate,
      totalTrades: response.data.total_trades,
      profitFactor: response.data.profit_factor,
      trades: response.data.trades.map((t: Record<string, unknown>) => ({
        entryTime: new Date(t.entry_time as string),
        exitTime: new Date(t.exit_time as string),
        side: t.side,
        entryPrice: t.entry_price,
        exitPrice: t.exit_price,
        quantity: t.quantity,
        pnl: t.pnl,
        pnlPercent: t.pnl_percent,
        exitReason: t.exit_reason,
      })),
    };
  }

  // ==========================================================================
  // Model Management
  // ==========================================================================

  /**
   * Trigger model retraining
   */
  async triggerRetraining(symbol?: string): Promise<{ jobId: string }> {
    const response = await this.client.post('/models/retrain', {
      symbol,
    });

    return {
      jobId: response.data.job_id,
    };
  }

  /**
   * Get retraining job status
   */
  async getRetrainingStatus(jobId: string): Promise<{
    status: 'pending' | 'running' | 'completed' | 'failed';
    progress: number;
    message: string;
  }> {
    const response = await this.client.get(`/models/retrain/${jobId}`);

    return {
      status: response.data.status,
      progress: response.data.progress,
      message: response.data.message,
    };
  }

  /**
   * Get available models
   */
  async getModels(): Promise<
    {
      id: string;
      symbol: string;
      version: string;
      accuracy: number;
      createdAt: Date;
      isActive: boolean;
    }[]
  > {
    const response = await this.client.get('/models');

    return response.data.models.map((m: Record<string, unknown>) => ({
      id: m.id,
      symbol: m.symbol,
      version: m.version,
      accuracy: m.accuracy,
      createdAt: new Date(m.created_at as string),
      isActive: m.is_active,
    }));
  }

  // ==========================================================================
  // Private Methods
  // ==========================================================================

  private transformSignal(data: Record<string, unknown>): MLSignal {
    return {
      id: data.id as string,
      symbol: data.symbol as string,
      timestamp: new Date(data.timestamp as string),
      signalType: data.signal_type as SignalType,
      confidence: data.confidence as number,
      timeHorizon: data.time_horizon as TimeHorizon,
      amdPhase: data.amd_phase as AMDPhase,
      indicators: data.indicators as MLIndicators,
      prediction: data.prediction as MLPrediction,
      reasoning: data.reasoning as string,
      expiresAt: new Date(data.expires_at as string),
    };
  }

  private async handleError(error: AxiosError): Promise<never> {
    if (!error.response) {
      // Network error
      this.isConnected = false;
      this.emit('connectionStatus', false);
      throw new Error('ML Engine is not reachable');
    }

    const status = error.response.status;
    const message = (error.response.data as Record<string, unknown>)?.detail || error.message;

    if (status === 429) {
      throw new Error('Rate limit exceeded on ML Engine');
    }

    if (status === 503) {
      this.isConnected = false;
      this.emit('connectionStatus', false);
      throw new Error('ML Engine is temporarily unavailable');
    }

    throw new Error(`ML Engine error: ${message}`);
  }

  /**
   * Retry a request with exponential backoff
   */
  private async retryRequest<T>(
    request: () => Promise<T>,
    attempts: number = this.config.retryAttempts
  ): Promise<T> {
    let lastError: Error | undefined;

    for (let i = 0; i < attempts; i++) {
      try {
        return await request();
      } catch (error) {
        lastError = error as Error;
        if (i < attempts - 1) {
          await new Promise((resolve) =>
            setTimeout(resolve, this.config.retryDelay * Math.pow(2, i))
          );
        }
      }
    }

    throw lastError;
  }
}

// Export singleton instance
export const mlIntegrationService = new MLIntegrationService();
