/**
 * Query Profiler Utility
 * Tracks query performance, memory usage, and execution times
 * Helps identify slow queries and optimization opportunities
 */

export class QueryProfiler {
  /**
   * Profile a query execution
   * @param {Function} queryFn - Async function that executes the query
   * @param {string} queryName - Name/identifier for the query
   * @param {Object} options - Profiling options
   * @returns {Promise<{result: any, metrics: Object}>}
   */
  static async profile(queryFn, queryName, options = {}) {
    const {
      logSlowThreshold = 1000, // Log queries slower than 1s
      logMemoryThreshold = 100, // Log queries using > 100MB
      enableExplain = false, // Get EXPLAIN plan
      explainQuery = null // SQL query for EXPLAIN (if different from queryFn)
    } = options;

    const start = process.hrtime.bigint();
    const startMemory = process.memoryUsage();
    
    let result;
    let error = null;
    
    try {
      result = await queryFn();
      
      const end = process.hrtime.bigint();
      const endMemory = process.memoryUsage();
      
      const duration = Number(end - start) / 1_000_000; // milliseconds
      const memoryDelta = (endMemory.heapUsed - startMemory.heapUsed) / 1024 / 1024; // MB
      const rssDelta = (endMemory.rss - startMemory.rss) / 1024 / 1024; // MB
      
      const metrics = {
        queryName,
        duration,
        memoryDelta,
        rssDelta,
        startMemory: startMemory.heapUsed / 1024 / 1024,
        endMemory: endMemory.heapUsed / 1024 / 1024,
        timestamp: new Date().toISOString(),
        rowCount: Array.isArray(result) ? result.length : null
      };
      
      // Log slow queries
      if (duration > logSlowThreshold) {
        console.warn(`⚠️  SLOW QUERY: ${queryName} took ${duration.toFixed(2)}ms`);
        console.warn(`   Memory delta: ${memoryDelta.toFixed(2)}MB`);
        if (metrics.rowCount !== null) {
          console.warn(`   Rows returned: ${metrics.rowCount}`);
        }
      }
      
      // Log high memory usage
      if (Math.abs(memoryDelta) > logMemoryThreshold) {
        console.warn(`⚠️  HIGH MEMORY: ${queryName} used ${memoryDelta.toFixed(2)}MB`);
      }
      
      // Get EXPLAIN plan if requested
      if (enableExplain && explainQuery) {
        try {
          const explainPlan = await this.getExplainPlan(explainQuery);
          metrics.explainPlan = explainPlan;
          metrics.recommendations = this.analyzeExplainPlan(explainPlan);
        } catch (explainError) {
          console.warn(`Could not get EXPLAIN plan for ${queryName}:`, explainError.message);
        }
      }
      
      // Store metrics for aggregation
      this.recordMetrics(metrics);
      
      return { result, metrics };
      
    } catch (err) {
      const end = process.hrtime.bigint();
      const duration = Number(end - start) / 1_000_000;
      
      error = {
        queryName,
        duration,
        error: err.message,
        timestamp: new Date().toISOString()
      };
      
      console.error(`❌ QUERY FAILED: ${queryName} after ${duration.toFixed(2)}ms`);
      console.error(`   Error: ${err.message}`);
      
      this.recordError(error);
      throw err;
    }
  }
  
  /**
   * Get EXPLAIN plan for a query
   * @param {string} sql - SQL query
   * @param {Array} params - Query parameters
   * @returns {Promise<Object>} EXPLAIN plan
   */
  static async getExplainPlan(sql, params = []) {
    const { rawQuery } = await import('../db.js');
    
    const explainQuery = `EXPLAIN (ANALYZE, BUFFERS, VERBOSE, FORMAT JSON) ${sql}`;
    const result = await rawQuery(explainQuery, params);
    
    return result[0]?.query_plan || result;
  }
  
  /**
   * Analyze EXPLAIN plan and provide recommendations
   * @param {Object} plan - EXPLAIN plan result
   * @returns {Array<Object>} Recommendations
   */
  static analyzeExplainPlan(plan) {
    const recommendations = [];
    const planStr = JSON.stringify(plan);
    
    // Check for sequential scans
    if (planStr.includes('Seq Scan')) {
      recommendations.push({
        severity: 'HIGH',
        issue: 'Sequential scan detected',
        fix: 'Add appropriate index or use index hint',
        impact: 'Sequential scans are slow on large tables'
      });
    }
    
    // Check for high cost
    const costMatch = planStr.match(/"Total Cost":\s*(\d+\.?\d*)/);
    if (costMatch) {
      const totalCost = parseFloat(costMatch[1]);
      if (totalCost > 100000) {
        recommendations.push({
          severity: 'MEDIUM',
          issue: `High query cost: ${totalCost.toFixed(0)}`,
          fix: 'Consider materialized view or query optimization',
          impact: 'Query may be slow on large datasets'
        });
      }
    }
    
    // Check for nested loops (can be slow)
    if (planStr.includes('Nested Loop') && planStr.match(/Nested Loop/g).length > 3) {
      recommendations.push({
        severity: 'MEDIUM',
        issue: 'Multiple nested loops detected',
        fix: 'Consider using hash joins or merge joins',
        impact: 'Nested loops can be slow for large datasets'
      });
    }
    
    // Check for missing indexes
    if (planStr.includes('Index Scan') === false && planStr.includes('Index Only Scan') === false) {
      recommendations.push({
        severity: 'LOW',
        issue: 'No index usage detected',
        fix: 'Consider adding indexes for frequently queried columns',
        impact: 'May benefit from indexing'
      });
    }
    
    return recommendations;
  }
  
  /**
   * Record metrics for aggregation
   * @param {Object} metrics - Query metrics
   */
  static recordMetrics(metrics) {
    if (!this.metrics) {
      this.metrics = [];
    }
    
    // Keep only last 1000 metrics
    this.metrics.push(metrics);
    if (this.metrics.length > 1000) {
      this.metrics.shift();
    }
  }
  
  /**
   * Record error for tracking
   * @param {Object} error - Error information
   */
  static recordError(error) {
    if (!this.errors) {
      this.errors = [];
    }
    
    this.errors.push(error);
    if (this.errors.length > 100) {
      this.errors.shift();
    }
  }
  
  /**
   * Get aggregated statistics
   * @param {string} queryName - Optional: filter by query name
   * @returns {Object} Aggregated stats
   */
  static getStats(queryName = null) {
    const metrics = queryName 
      ? this.metrics?.filter(m => m.queryName === queryName) || []
      : this.metrics || [];
    
    if (metrics.length === 0) {
      return {
        count: 0,
        message: 'No metrics recorded yet'
      };
    }
    
    const durations = metrics.map(m => m.duration);
    const memoryDeltas = metrics.map(m => m.memoryDelta);
    
    return {
      count: metrics.length,
      avgDuration: durations.reduce((a, b) => a + b, 0) / durations.length,
      minDuration: Math.min(...durations),
      maxDuration: Math.max(...durations),
      p95Duration: this.percentile(durations, 0.95),
      p99Duration: this.percentile(durations, 0.99),
      avgMemoryDelta: memoryDeltas.reduce((a, b) => a + b, 0) / memoryDeltas.length,
      totalErrors: this.errors?.length || 0,
      slowQueries: metrics.filter(m => m.duration > 1000).length
    };
  }
  
  /**
   * Get slow queries
   * @param {number} threshold - Duration threshold in ms
   * @returns {Array} Slow query metrics
   */
  static getSlowQueries(threshold = 1000) {
    return (this.metrics || [])
      .filter(m => m.duration > threshold)
      .sort((a, b) => b.duration - a.duration);
  }
  
  /**
   * Calculate percentile
   * @param {Array<number>} values - Array of values
   * @param {number} percentile - Percentile (0-1)
   * @returns {number} Percentile value
   */
  static percentile(values, percentile) {
    const sorted = [...values].sort((a, b) => a - b);
    const index = Math.ceil(sorted.length * percentile) - 1;
    return sorted[index] || 0;
  }
  
  /**
   * Clear all metrics
   */
  static clearMetrics() {
    this.metrics = [];
    this.errors = [];
  }
}

// Initialize metrics storage
QueryProfiler.metrics = [];
QueryProfiler.errors = [];

