import { IServiceContainer } from '@stock-bot/di';
import { QuestDBClient } from '@stock-bot/questdb';
import { PostgresClient } from '@stock-bot/postgres';
import { MongoDBClient } from '@stock-bot/mongodb';
import { ModeConfig, MarketData, Position } from '../types';

export class StorageService {
  private questdb: QuestDBClient | null = null;
  private postgres: PostgresClient | null = null;
  private mongodb: MongoDBClient | null = null;
  private mode: 'backtest' | 'paper' | 'live' = 'paper';
  private container: IServiceContainer;

  constructor(
    container: IServiceContainer,
    mongoClient: MongoDBClient,
    postgresClient: PostgresClient,
    questdbClient: QuestDBClient | null
  ) {
    this.container = container;
    this.mongodb = mongoClient;
    this.postgres = postgresClient;
    this.questdb = questdbClient;
  }

  async initialize(config: ModeConfig): Promise<void> {
    this.mode = config.mode;
    // Clients are already injected via DI
    await this.createTables();
  }

  private async createTables(): Promise<void> {
    // Create tables if they don't exist
    if (this.postgres) {
      // Orders table
      await this.postgres.query(`
        CREATE TABLE IF NOT EXISTS orders (
          id UUID PRIMARY KEY,
          client_order_id VARCHAR(255),
          symbol VARCHAR(50) NOT NULL,
          side VARCHAR(10) NOT NULL,
          quantity DECIMAL(20, 8) NOT NULL,
          order_type VARCHAR(20) NOT NULL,
          limit_price DECIMAL(20, 8),
          stop_price DECIMAL(20, 8),
          time_in_force VARCHAR(10) NOT NULL,
          status VARCHAR(20) NOT NULL,
          created_at TIMESTAMP NOT NULL DEFAULT NOW(),
          updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
          mode VARCHAR(10) NOT NULL
        )
      `);

      // Fills table
      await this.postgres.query(`
        CREATE TABLE IF NOT EXISTS fills (
          id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
          order_id UUID NOT NULL REFERENCES orders(id),
          symbol VARCHAR(50) NOT NULL,
          price DECIMAL(20, 8) NOT NULL,
          quantity DECIMAL(20, 8) NOT NULL,
          commission DECIMAL(20, 8) NOT NULL,
          side VARCHAR(10) NOT NULL,
          timestamp TIMESTAMP NOT NULL,
          mode VARCHAR(10) NOT NULL
        )
      `);

      // Positions table
      await this.postgres.query(`
        CREATE TABLE IF NOT EXISTS positions (
          id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
          symbol VARCHAR(50) NOT NULL,
          quantity DECIMAL(20, 8) NOT NULL,
          average_price DECIMAL(20, 8) NOT NULL,
          realized_pnl DECIMAL(20, 8) NOT NULL DEFAULT 0,
          unrealized_pnl DECIMAL(20, 8) NOT NULL DEFAULT 0,
          updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
          mode VARCHAR(10) NOT NULL,
          UNIQUE(symbol, mode)
        )
      `);

      // Strategy performance table
      await this.postgres.query(`
        CREATE TABLE IF NOT EXISTS strategy_performance (
          id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
          strategy_id VARCHAR(255) NOT NULL,
          timestamp TIMESTAMP NOT NULL,
          total_return DECIMAL(20, 8),
          sharpe_ratio DECIMAL(20, 8),
          max_drawdown DECIMAL(20, 8),
          win_rate DECIMAL(20, 8),
          total_trades INTEGER,
          mode VARCHAR(10) NOT NULL
        )
      `);
    }
  }

  async storeMarketData(data: MarketData[]): Promise<void> {
    if (!this.questdb) return;

    for (const item of data) {
      try {
        switch (item.type) {
          case 'quote':
            await this.questdb.insert('quotes', {
              symbol: item.data.symbol,
              bid: item.data.bid,
              ask: item.data.ask,
              bid_size: item.data.bidSize,
              ask_size: item.data.askSize,
              timestamp: new Date(item.data.timestamp)
            });
            break;

          case 'trade':
            await this.questdb.insert('trades', {
              symbol: item.data.symbol,
              price: item.data.price,
              size: item.data.size,
              side: item.data.side,
              timestamp: new Date(item.data.timestamp)
            });
            break;

          case 'bar':
            const interval = '1m'; // Would be determined from context
            await this.questdb.insert(`bars_${interval}`, {
              symbol: item.data.symbol,
              open: item.data.open,
              high: item.data.high,
              low: item.data.low,
              close: item.data.close,
              volume: item.data.volume,
              vwap: item.data.vwap || null,
              timestamp: new Date(item.data.timestamp)
            });
            break;
        }
      } catch (error) {
        logger.error('Error storing market data:', error);
      }
    }
  }

  async storeOrder(order: any): Promise<void> {
    if (!this.postgres) return;

    await this.postgres.query(`
      INSERT INTO orders (
        id, client_order_id, symbol, side, quantity, 
        order_type, limit_price, stop_price, time_in_force, 
        status, mode
      ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
    `, [
      order.id,
      order.clientOrderId,
      order.symbol,
      order.side,
      order.quantity,
      order.orderType,
      order.limitPrice || null,
      order.stopPrice || null,
      order.timeInForce,
      order.status,
      this.mode
    ]);
  }

  async updateOrderStatus(orderId: string, status: string): Promise<void> {
    if (!this.postgres) return;

    await this.postgres.query(`
      UPDATE orders 
      SET status = $1, updated_at = NOW() 
      WHERE id = $2
    `, [status, orderId]);
  }

  async storeFill(fill: any): Promise<void> {
    if (!this.postgres) return;

    await this.postgres.query(`
      INSERT INTO fills (
        order_id, symbol, price, quantity, commission, side, timestamp, mode
      ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
    `, [
      fill.orderId,
      fill.symbol,
      fill.price,
      fill.quantity,
      fill.commission,
      fill.side,
      new Date(fill.timestamp),
      this.mode
    ]);
  }

  async updatePosition(position: Position): Promise<void> {
    if (!this.postgres) return;

    await this.postgres.query(`
      INSERT INTO positions (
        symbol, quantity, average_price, realized_pnl, unrealized_pnl, mode
      ) VALUES ($1, $2, $3, $4, $5, $6)
      ON CONFLICT (symbol, mode) DO UPDATE SET
        quantity = $2,
        average_price = $3,
        realized_pnl = $4,
        unrealized_pnl = $5,
        updated_at = NOW()
    `, [
      position.symbol,
      position.quantity,
      position.averagePrice,
      position.realizedPnl,
      position.unrealizedPnl,
      this.mode
    ]);
  }

  async getPositions(): Promise<Position[]> {
    if (!this.postgres) return [];

    const result = await this.postgres.query(`
      SELECT * FROM positions WHERE mode = $1
    `, [this.mode]);

    return result.rows.map((row: any) => ({
      symbol: row.symbol,
      quantity: parseFloat(row.quantity),
      averagePrice: parseFloat(row.average_price),
      realizedPnl: parseFloat(row.realized_pnl),
      unrealizedPnl: parseFloat(row.unrealized_pnl),
      totalCost: parseFloat(row.quantity) * parseFloat(row.average_price),
      lastUpdate: row.updated_at
    }));
  }

  async storeStrategyPerformance(strategyId: string, metrics: any): Promise<void> {
    if (!this.postgres) return;

    await this.postgres.query(`
      INSERT INTO strategy_performance (
        strategy_id, timestamp, total_return, sharpe_ratio, 
        max_drawdown, win_rate, total_trades, mode
      ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
    `, [
      strategyId,
      new Date(),
      metrics.totalReturn,
      metrics.sharpeRatio,
      metrics.maxDrawdown,
      metrics.winRate,
      metrics.totalTrades,
      this.mode
    ]);
  }

  async getHistoricalBars(
    symbol: string,
    startTime: Date,
    endTime: Date,
    interval: string = '1m'
  ): Promise<any[]> {
    // If QuestDB is available, use real data
    if (this.questdb) {
      const result = await this.questdb.query(`
        SELECT * FROM bars_${interval}
        WHERE symbol = '${symbol}'
          AND timestamp >= '${startTime.toISOString()}'
          AND timestamp < '${endTime.toISOString()}'
        ORDER BY timestamp
      `);
      return result;
    }

    // Otherwise, generate mock data for testing
    this.container.logger.info('Generating mock data for backtest', {
      symbol,
      startTime: startTime.toISOString(),
      endTime: endTime.toISOString(),
      interval
    });

    return this.generateMockBars(symbol, startTime, endTime, interval);
  }

  private generateMockBars(
    symbol: string,
    startTime: Date,
    endTime: Date,
    interval: string
  ): any[] {
    const bars = [];
    const msPerInterval = this.getIntervalMilliseconds(interval);
    let currentTime = new Date(startTime);
    
    // Use current timestamp to seed randomness for different data each run
    const seed = Date.now();
    const seedMultiplier = (seed % 1000) / 1000; // 0-1 based on milliseconds
    
    // Starting price based on symbol with some randomness
    let basePrice = symbol === 'AAPL' ? 150 + (seedMultiplier * 20 - 10) : 
                   symbol === 'MSFT' ? 300 + (seedMultiplier * 30 - 15) :
                   symbol === 'GOOGL' ? 120 + (seedMultiplier * 15 - 7.5) : 
                   100 + (seedMultiplier * 10 - 5);
    
    // Random walk seed to make each run different
    let walkSeed = seedMultiplier;
    
    while (currentTime <= endTime) {
      // Use a pseudo-random based on walkSeed for deterministic but different results
      walkSeed = (walkSeed * 9.73 + 0.27) % 1;
      
      // Random walk with trend - increased volatility for testing
      const trend = 0.0002; // Slight upward trend
      const volatility = 0.01; // 1% volatility
      const change = (walkSeed - 0.5 + trend) * volatility;
      
      basePrice *= (1 + change);
      
      // Generate OHLC data with more realistic volatility
      const openSeed = (walkSeed * 7.13 + 0.31) % 1;
      const highSeed = (walkSeed * 5.17 + 0.41) % 1;
      const lowSeed = (walkSeed * 3.19 + 0.59) % 1;
      const volumeSeed = (walkSeed * 11.23 + 0.67) % 1;
      
      const open = basePrice * (1 + (openSeed - 0.5) * 0.005);
      const close = basePrice;
      const high = Math.max(open, close) * (1 + highSeed * 0.008);
      const low = Math.min(open, close) * (1 - lowSeed * 0.008);
      const volume = 1000000 + volumeSeed * 500000;
      
      bars.push({
        symbol,
        timestamp: new Date(currentTime),
        open,
        high,
        low,
        close,
        volume,
        vwap: (high + low + close) / 3
      });
      
      currentTime = new Date(currentTime.getTime() + msPerInterval);
    }
    
    this.container.logger.info(`Generated ${bars.length} mock bars for ${symbol} with seed ${seed}`);
    
    return bars;
  }

  private getIntervalMilliseconds(interval: string): number {
    switch (interval) {
      case '1m': return 60 * 1000;
      case '5m': return 5 * 60 * 1000;
      case '15m': return 15 * 60 * 1000;
      case '1h': return 60 * 60 * 1000;
      case '1d': return 24 * 60 * 60 * 1000;
      default: return 60 * 1000; // Default to 1 minute
    }
  }

  async shutdown(): Promise<void> {
    if (this.questdb) {
      await this.questdb.disconnect();
      this.questdb = null;
    }

    if (this.postgres) {
      await this.postgres.disconnect();
      this.postgres = null;
    }
  }
}