import { io, Socket } from 'socket.io-client';
import { EventEmitter } from 'events';
import { IServiceContainer } from '@stock-bot/di';
import { ModeConfig, MarketData, QuoteSchema, TradeSchema, BarSchema } from '../types';
import { QuestDBClient } from '@stock-bot/questdb';

export class MarketDataService extends EventEmitter {
  private mode: 'backtest' | 'paper' | 'live' = 'paper';
  private dataIngestionSocket: Socket | null = null;
  private questdbClient: QuestDBClient | null = null;
  private subscriptions = new Set<string>();
  private batchBuffer: MarketData[] = [];
  private batchTimer: NodeJS.Timeout | null = null;
  private readonly BATCH_SIZE = 100;
  private readonly BATCH_INTERVAL_MS = 50;
  private container: IServiceContainer;

  constructor(container: IServiceContainer) {
    super();
    this.container = container;
  }

  async initialize(config: ModeConfig): Promise<void> {
    this.mode = config.mode;
    
    if (config.mode === 'backtest') {
      // Initialize QuestDB client for historical data
      this.questdbClient = new QuestDBClient({
        host: process.env.QUESTDB_HOST || 'localhost',
        port: parseInt(process.env.QUESTDB_PORT || '9000'),
        database: process.env.QUESTDB_DATABASE || 'trading'
      });
    } else {
      // Connect to data-ingestion service for real-time data
      await this.connectToDataIngestion();
    }
  }

  private async connectToDataIngestion(): Promise<void> {
    const dataIngestionUrl = process.env.DATA_INGESTION_URL || 'http://localhost:3001';
    
    this.dataIngestionSocket = io(dataIngestionUrl, {
      transports: ['websocket'],
      reconnection: true,
      reconnectionAttempts: 5,
      reconnectionDelay: 1000
    });

    this.dataIngestionSocket.on('connect', () => {
      this.container.logger.info('Connected to data-ingestion service');
      // Re-subscribe to symbols
      this.subscriptions.forEach(symbol => {
        this.dataIngestionSocket!.emit('subscribe', { symbol });
      });
    });

    this.dataIngestionSocket.on('disconnect', () => {
      this.container.logger.warn('Disconnected from data-ingestion service');
    });

    this.dataIngestionSocket.on('marketData', (data: any) => {
      this.handleMarketData(data);
    });

    this.dataIngestionSocket.on('error', (error: any) => {
      this.container.logger.error('Data ingestion socket error:', error);
    });
  }

  async subscribeToSymbol(symbol: string): Promise<void> {
    this.subscriptions.add(symbol);
    
    if (this.mode !== 'backtest' && this.dataIngestionSocket?.connected) {
      this.dataIngestionSocket.emit('subscribe', { symbol });
    }
    
    this.container.logger.debug(`Subscribed to ${symbol}`);
  }

  async unsubscribeFromSymbol(symbol: string): Promise<void> {
    this.subscriptions.delete(symbol);
    
    if (this.mode !== 'backtest' && this.dataIngestionSocket?.connected) {
      this.dataIngestionSocket.emit('unsubscribe', { symbol });
    }
    
    this.container.logger.debug(`Unsubscribed from ${symbol}`);
  }

  private handleMarketData(data: any): void {
    try {
      // Validate and transform data
      let marketData: MarketData;
      
      if (data.bid !== undefined && data.ask !== undefined) {
        const quote = QuoteSchema.parse({
          symbol: data.symbol,
          bid: data.bid,
          ask: data.ask,
          bidSize: data.bidSize || data.bid_size || 0,
          askSize: data.askSize || data.ask_size || 0,
          timestamp: data.timestamp || Date.now()
        });
        marketData = { type: 'quote', data: quote };
      } else if (data.price !== undefined && data.size !== undefined) {
        const trade = TradeSchema.parse({
          symbol: data.symbol,
          price: data.price,
          size: data.size,
          side: data.side || 'buy',
          timestamp: data.timestamp || Date.now()
        });
        marketData = { type: 'trade', data: trade };
      } else if (data.open !== undefined && data.close !== undefined) {
        const bar = BarSchema.parse({
          symbol: data.symbol,
          open: data.open,
          high: data.high,
          low: data.low,
          close: data.close,
          volume: data.volume,
          vwap: data.vwap,
          timestamp: data.timestamp || Date.now()
        });
        marketData = { type: 'bar', data: bar };
      } else {
        this.container.logger.warn('Unknown market data format:', data);
        return;
      }

      // Add to batch buffer
      this.batchBuffer.push(marketData);
      
      // Process batch if size threshold reached
      if (this.batchBuffer.length >= this.BATCH_SIZE) {
        this.processBatch();
      } else if (!this.batchTimer) {
        // Set timer for time-based batching
        this.batchTimer = setTimeout(() => this.processBatch(), this.BATCH_INTERVAL_MS);
      }
      
    } catch (error) {
      this.container.logger.error('Error handling market data:', error);
    }
  }

  private processBatch(): void {
    if (this.batchBuffer.length === 0) return;
    
    // Clear timer
    if (this.batchTimer) {
      clearTimeout(this.batchTimer);
      this.batchTimer = null;
    }
    
    // Emit batch
    const batch = [...this.batchBuffer];
    this.batchBuffer = [];
    
    this.emit('marketDataBatch', batch);
    
    // Also emit individual events for strategies that need them
    batch.forEach(data => {
      this.emit('marketData', data);
    });
  }

  async loadHistoricalData(
    symbols: string[],
    startTime: Date,
    endTime: Date,
    interval: string = '1m'
  ): Promise<MarketData[]> {
    if (!this.questdbClient) {
      throw new Error('QuestDB client not initialized');
    }

    const data: MarketData[] = [];
    
    for (const symbol of symbols) {
      // Query for bars
      const bars = await this.questdbClient.query(`
        SELECT 
          timestamp,
          open,
          high,
          low,
          close,
          volume,
          vwap
        FROM bars_${interval}
        WHERE symbol = '${symbol}'
          AND timestamp >= '${startTime.toISOString()}'
          AND timestamp < '${endTime.toISOString()}'
        ORDER BY timestamp
      `);
      
      // Convert to MarketData format
      bars.forEach((row: any) => {
        data.push({
          type: 'bar',
          data: {
            symbol,
            open: row.open,
            high: row.high,
            low: row.low,
            close: row.close,
            volume: row.volume,
            vwap: row.vwap,
            timestamp: new Date(row.timestamp).getTime()
          }
        });
      });
      
      // Also query for trades if needed for more granular simulation
      if (interval === '1m' || interval === 'tick') {
        const trades = await this.questdbClient.query(`
          SELECT 
            timestamp,
            price,
            size,
            side
          FROM trades
          WHERE symbol = '${symbol}'
            AND timestamp >= '${startTime.toISOString()}'
            AND timestamp < '${endTime.toISOString()}'
          ORDER BY timestamp
        `);
        
        trades.forEach((row: any) => {
          data.push({
            type: 'trade',
            data: {
              symbol,
              price: row.price,
              size: row.size,
              side: row.side,
              timestamp: new Date(row.timestamp).getTime()
            }
          });
        });
      }
    }
    
    // Sort all data by timestamp
    data.sort((a, b) => {
      const timeA = a.type === 'bar' ? a.data.timestamp : 
                    a.type === 'trade' ? a.data.timestamp : 
                    a.data.timestamp;
      const timeB = b.type === 'bar' ? b.data.timestamp : 
                    b.type === 'trade' ? b.data.timestamp : 
                    b.data.timestamp;
      return timeA - timeB;
    });
    
    return data;
  }

  async shutdown(): Promise<void> {
    // Clear batch timer
    if (this.batchTimer) {
      clearTimeout(this.batchTimer);
      this.batchTimer = null;
    }
    
    // Process any remaining data
    if (this.batchBuffer.length > 0) {
      this.processBatch();
    }
    
    // Disconnect from data ingestion
    if (this.dataIngestionSocket) {
      this.dataIngestionSocket.disconnect();
      this.dataIngestionSocket = null;
    }
    
    // Close QuestDB connection
    if (this.questdbClient) {
      await this.questdbClient.disconnect();
      this.questdbClient = null;
    }
    
    this.subscriptions.clear();
    this.removeAllListeners();
  }
}