import { v4 as uuidv4 } from 'uuid';
import { getLogger } from '@stock-bot/logger';
import type { IServiceContainer } from '@stock-bot/handlers';
import type { WebSocketHandler } from '../websocket/run-updates';

const logger = getLogger('backtest-v2-service');

// Use environment variable or default
const ORCHESTRATOR_URL = process.env.ORCHESTRATOR_URL || 'http://localhost:2004';

export interface BacktestRequest {
  name: string;
  strategy: string;
  symbols: string[];
  startDate: string;
  endDate: string;
  initialCapital: number;
  config?: Record<string, any>;
}

export interface Backtest {
  id: string;
  name: string;
  strategy: string;
  symbols: string[];
  startDate: Date;
  endDate: Date;
  initialCapital: number;
  config: Record<string, any>;
  createdAt: Date;
  updatedAt: Date;
}

export interface Run {
  id: string;
  backtestId: string;
  runNumber: number;
  status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled' | 'paused';
  speedMultiplier: number;
  error?: string;
  startedAt?: Date;
  completedAt?: Date;
  pausedAt?: Date;
  progress: number;
  currentDate?: Date;
  createdAt: Date;
  updatedAt: Date;
}

export interface RunRequest {
  backtestId: string;
  speedMultiplier?: number | null;
}

export class BacktestServiceV2 {
  private container: IServiceContainer;
  private activeRuns: Map<string, any> = new Map(); // Track active WebSocket connections
  private wsHandler: WebSocketHandler | null = null;

  constructor(container: IServiceContainer) {
    this.container = container;
    logger.info('BacktestServiceV2 initialized');
  }

  // Backtest CRUD operations
  async createBacktest(request: BacktestRequest): Promise<Backtest> {
    const backtestId = uuidv4();
    
    const result = await this.container.postgres.query(
      `INSERT INTO backtests 
       (id, name, strategy, symbols, start_date, end_date, initial_capital, config)
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
       RETURNING *`,
      [
        backtestId,
        request.name,
        request.strategy,
        JSON.stringify(request.symbols),
        request.startDate,
        request.endDate,
        request.initialCapital,
        JSON.stringify(request.config || {})
      ]
    );

    const backtest = this.mapBacktest(result.rows[0]);
    
    // Automatically create and start a run for the new backtest
    try {
      await this.createRun({ 
        backtestId: backtest.id, 
        speedMultiplier: null // Max speed (instant)
      });
    } catch (error) {
      logger.error('Failed to auto-create run for new backtest', { backtestId: backtest.id, error });
      // Don't fail the backtest creation if run creation fails
    }
    
    return backtest;
  }

  async getBacktest(id: string): Promise<Backtest | null> {
    const result = await this.container.postgres.query(
      'SELECT * FROM backtests WHERE id = $1',
      [id]
    );
    
    if (result.rows.length === 0) {
      return null;
    }
    
    return this.mapBacktest(result.rows[0]);
  }

  async updateBacktest(id: string, request: Partial<BacktestRequest>): Promise<Backtest | null> {
    const updates: string[] = [];
    const values: any[] = [];
    let paramCount = 1;

    if (request.name !== undefined) {
      updates.push(`name = $${paramCount++}`);
      values.push(request.name);
    }
    if (request.strategy !== undefined) {
      updates.push(`strategy = $${paramCount++}`);
      values.push(request.strategy);
    }
    if (request.symbols !== undefined) {
      updates.push(`symbols = $${paramCount++}`);
      values.push(JSON.stringify(request.symbols));
    }
    if (request.startDate !== undefined) {
      updates.push(`start_date = $${paramCount++}`);
      values.push(request.startDate);
    }
    if (request.endDate !== undefined) {
      updates.push(`end_date = $${paramCount++}`);
      values.push(request.endDate);
    }
    if (request.initialCapital !== undefined) {
      updates.push(`initial_capital = $${paramCount++}`);
      values.push(request.initialCapital);
    }
    if (request.config !== undefined) {
      updates.push(`config = $${paramCount++}`);
      values.push(JSON.stringify(request.config));
    }

    if (updates.length === 0) {
      return this.getBacktest(id);
    }

    values.push(id);
    const result = await this.container.postgres.query(
      `UPDATE backtests 
       SET ${updates.join(', ')}, updated_at = NOW()
       WHERE id = $${paramCount}
       RETURNING *`,
      values
    );

    if (result.rows.length === 0) {
      return null;
    }

    return this.mapBacktest(result.rows[0]);
  }

  async listBacktests(params: { limit: number; offset: number }): Promise<Backtest[]> {
    const result = await this.container.postgres.query(
      `SELECT * FROM backtests 
       ORDER BY created_at DESC 
       LIMIT $1 OFFSET $2`,
      [params.limit, params.offset]
    );
    
    return result.rows.map(row => this.mapBacktest(row));
  }

  async deleteBacktest(id: string): Promise<void> {
    await this.container.postgres.query(
      'DELETE FROM backtests WHERE id = $1',
      [id]
    );
  }

  // Run operations
  async createRun(request: RunRequest): Promise<Run> {
    const runId = uuidv4();
    
    // Get the next run number for this backtest
    const runNumberResult = await this.container.postgres.query(
      'SELECT COALESCE(MAX(run_number), 0) + 1 as next_run_number FROM runs WHERE backtest_id = $1',
      [request.backtestId]
    );
    const runNumber = runNumberResult.rows[0].next_run_number;

    const result = await this.container.postgres.query(
      `INSERT INTO runs 
       (id, backtest_id, run_number, speed_multiplier, status)
       VALUES ($1, $2, $3, $4, 'pending')
       RETURNING *`,
      [
        runId,
        request.backtestId,
        runNumber,
        request.speedMultiplier ?? 1.0
      ]
    );

    const run = this.mapRun(result.rows[0]);
    
    // Start the run immediately
    this.startRun(run);
    
    return run;
  }

  async getRun(id: string): Promise<Run | null> {
    const result = await this.container.postgres.query(
      'SELECT * FROM runs WHERE id = $1',
      [id]
    );
    
    if (result.rows.length === 0) {
      return null;
    }
    
    return this.mapRun(result.rows[0]);
  }

  async listRuns(backtestId: string): Promise<Run[]> {
    const result = await this.container.postgres.query(
      `SELECT * FROM runs 
       WHERE backtest_id = $1
       ORDER BY run_number DESC`,
      [backtestId]
    );
    
    return result.rows.map(row => this.mapRun(row));
  }

  async updateRunStatus(
    id: string, 
    status: Run['status'], 
    updates?: { 
      error?: string; 
      progress?: number; 
      currentDate?: Date;
      speedMultiplier?: number;
    }
  ): Promise<void> {
    const setClauses = ['status = $2', 'updated_at = NOW()'];
    const values: any[] = [id, status];
    let paramCount = 3;

    if (status === 'running' && !updates?.currentDate) {
      setClauses.push(`started_at = NOW()`);
    }
    if (status === 'completed' || status === 'failed' || status === 'cancelled') {
      setClauses.push(`completed_at = NOW()`);
    }
    if (status === 'paused') {
      setClauses.push(`paused_at = NOW()`);
    }

    if (updates?.error !== undefined) {
      setClauses.push(`error = $${paramCount++}`);
      values.push(updates.error);
    }
    if (updates?.progress !== undefined) {
      setClauses.push(`progress = $${paramCount++}`);
      values.push(updates.progress);
    }
    if (updates?.currentDate !== undefined) {
      setClauses.push(`current_simulation_date = $${paramCount++}`);
      values.push(updates.currentDate);
    }
    if (updates?.speedMultiplier !== undefined) {
      setClauses.push(`speed_multiplier = $${paramCount++}`);
      values.push(updates.speedMultiplier);
    }

    await this.container.postgres.query(
      `UPDATE runs SET ${setClauses.join(', ')} WHERE id = $1`,
      values
    );

    // Send WebSocket update if handler is available
    if (this.getWebSocketHandler()) {
      this.getWebSocketHandler().broadcastRunUpdate(id, {
        type: 'run_update',
        runId: id,
        data: {
          status,
          ...updates,
          timestamp: new Date().toISOString()
        }
      });
    }
  }

  async pauseRun(id: string): Promise<void> {
    await this.updateRunStatus(id, 'paused');
    
    // Send pause command to orchestrator
    try {
      const response = await fetch(`${ORCHESTRATOR_URL}/api/backtest/pause`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' }
      });
      
      if (!response.ok) {
        logger.warn('Failed to pause backtest in orchestrator');
      }
    } catch (error) {
      logger.error('Error pausing backtest in orchestrator', error);
    }
  }

  async resumeRun(id: string): Promise<void> {
    const run = await this.getRun(id);
    if (!run || run.status !== 'paused') {
      throw new Error('Run is not paused');
    }
    
    await this.updateRunStatus(id, 'running');
    
    // Send resume command to orchestrator
    try {
      const response = await fetch(`${ORCHESTRATOR_URL}/api/backtest/resume`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' }
      });
      
      if (!response.ok) {
        logger.warn('Failed to resume backtest in orchestrator');
      }
    } catch (error) {
      logger.error('Error resuming backtest in orchestrator', error);
    }
  }

  async cancelRun(id: string): Promise<void> {
    await this.updateRunStatus(id, 'cancelled');
    // TODO: Send cancel command to orchestrator
  }

  async updateRunSpeed(id: string, speedMultiplier: number | null): Promise<void> {
    await this.updateRunStatus(id, 'running', { speedMultiplier });
    
    // Send speed update to orchestrator
    try {
      const response = await fetch(`${ORCHESTRATOR_URL}/api/backtest/speed`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ speed: speedMultiplier })
      });
      
      if (!response.ok) {
        logger.warn('Failed to update backtest speed in orchestrator');
      }
    } catch (error) {
      logger.error('Error updating backtest speed in orchestrator', error);
    }
  }

  // Get run results
  async getRunResults(runId: string): Promise<any> {
    const result = await this.container.postgres.query(
      `SELECT 
        br.*,
        r.backtest_id,
        b.strategy,
        b.symbols,
        b.start_date,
        b.end_date,
        b.initial_capital,
        b.config as backtest_config
      FROM backtest_results br
      JOIN runs r ON r.id = br.run_id
      JOIN backtests b ON b.id = r.backtest_id
      WHERE br.run_id = $1`,
      [runId]
    );
    
    if (result.rows.length === 0) {
      return null;
    }
    
    const row = result.rows[0];
    
    return {
      runId: row.run_id,
      backtestId: row.backtest_id,
      status: 'completed',
      completedAt: row.completed_at.toISOString(),
      config: {
        name: row.backtest_config?.name || 'Backtest',
        strategy: row.strategy,
        symbols: row.symbols,
        startDate: row.start_date.toISOString(),
        endDate: row.end_date.toISOString(),
        initialCapital: parseFloat(row.initial_capital),
        commission: row.backtest_config?.commission ?? 0.001,
        slippage: row.backtest_config?.slippage ?? 0.0001,
        dataFrequency: row.backtest_config?.dataFrequency || '1d',
      },
      metrics: row.metrics,
      equity: row.equity_curve,
      ohlcData: row.ohlc_data,
      trades: row.trades,
      positions: row.positions,
      analytics: row.analytics,
      executionTime: row.execution_time,
    };
  }

  // Private methods
  private async startRun(run: Run): Promise<void> {
    const backtest = await this.getBacktest(run.backtestId);
    if (!backtest) {
      throw new Error('Backtest not found');
    }

    await this.updateRunStatus(run.id, 'running', { progress: 0 });

    // Send initial WebSocket update
    if (this.getWebSocketHandler()) {
      this.getWebSocketHandler().sendProgressUpdate(run.id, 0);
    }

    try {
      // Call orchestrator to run backtest
      const response = await fetch(`${ORCHESTRATOR_URL}/api/backtest/run`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          runId: run.id,
          mode: 'backtest',
          startDate: new Date(backtest.startDate).toISOString(),
          endDate: new Date(backtest.endDate).toISOString(),
          symbols: backtest.symbols,
          strategy: backtest.strategy,
          initialCapital: backtest.initialCapital,
          dataFrequency: backtest.config?.dataFrequency || '1d',
          commission: backtest.config?.commission ?? 0.001,
          slippage: backtest.config?.slippage ?? 0.0001,
          speed: run.speedMultiplier === null ? 'max' : 
                 run.speedMultiplier >= 10 ? '10x' :
                 run.speedMultiplier >= 5 ? '5x' :
                 run.speedMultiplier >= 2 ? '2x' : 'realtime',
          fillModel: {
            slippage: 'realistic',
            marketImpact: true,
            partialFills: true
          }
        }),
      });

      if (!response.ok) {
        const errorText = await response.text();
        logger.error('Orchestrator request failed', {
          status: response.status,
          statusText: response.statusText,
          error: errorText,
          request: {
            runId: run.id,
            strategy: backtest.strategy,
            symbols: backtest.symbols,
            startDate: new Date(backtest.startDate).toISOString(),
            endDate: new Date(backtest.endDate).toISOString(),
          }
        });
        throw new Error(`Orchestrator returned ${response.status}: ${errorText}`);
      }

      const result = await response.json();
      
      if (result.status === 'completed') {
        await this.updateRunStatus(run.id, 'completed', { progress: 100 });
        await this.saveRunResults(run.id, result);
        
        // Send completion via WebSocket
        if (this.getWebSocketHandler()) {
          this.getWebSocketHandler().sendCompletion(run.id, result);
        }
      }
    } catch (error) {
      logger.error('Failed to start run', { runId: run.id, error });
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      await this.updateRunStatus(run.id, 'failed', { 
        error: errorMessage 
      });
      
      // Send error via WebSocket
      if (this.getWebSocketHandler()) {
        this.getWebSocketHandler().sendError(run.id, errorMessage);
      }
    }
  }

  private async saveRunResults(runId: string, result: any): Promise<void> {
    try {
      await this.container.postgres.query(
        `INSERT INTO backtest_results 
         (run_id, completed_at, metrics, equity_curve, ohlc_data, trades, positions, analytics, execution_time)
         VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
        [
          runId,
          result.completedAt || new Date(),
          JSON.stringify(result.metrics || {}),
          JSON.stringify(result.equity || result.equityCurve || []),
          JSON.stringify(result.ohlcData || {}),
          JSON.stringify(result.trades || []),
          JSON.stringify(result.positions || result.finalPositions || {}),
          JSON.stringify(result.analytics || {}),
          result.executionTime || 0
        ]
      );
    } catch (error) {
      logger.error('Failed to save run results', { runId, error });
      throw error;
    }
  }

  private mapBacktest(row: any): Backtest {
    return {
      id: row.id,
      name: row.name,
      strategy: row.strategy,
      symbols: row.symbols,
      startDate: row.start_date,
      endDate: row.end_date,
      initialCapital: parseFloat(row.initial_capital),
      config: row.config,
      createdAt: row.created_at,
      updatedAt: row.updated_at,
    };
  }

  private getWebSocketHandler(): WebSocketHandler | null {
    // Try to get from global if not already set
    if (!this.wsHandler && (global as any).wsHandler) {
      this.wsHandler = (global as any).wsHandler;
    }
    return this.wsHandler;
  }

  private mapRun(row: any): Run {
    return {
      id: row.id,
      backtestId: row.backtest_id,
      runNumber: row.run_number,
      status: row.status,
      speedMultiplier: parseFloat(row.speed_multiplier),
      error: row.error,
      startedAt: row.started_at,
      completedAt: row.completed_at,
      pausedAt: row.paused_at,
      progress: parseFloat(row.progress),
      currentDate: row.current_simulation_date,
      createdAt: row.created_at,
      updatedAt: row.updated_at,
    };
  }
}