/**
 * Performance Monitoring System
 * Tracks operation duration and performance metrics.
 * Ported from Python monitoring/performance.py
 */

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

export interface PerformanceStats {
  operation: string;
  count: number;
  total_calls: number;
  min: number;
  max: number;
  mean: number;
  median: number;
  p95: number;
  p99: number;
}

// ============================================================================
// Performance Monitor Class
// ============================================================================

/**
 * Monitors and tracks performance metrics for operations.
 */
export class PerformanceMonitor {
  private metrics: Map<string, number[]> = new Map();
  private operationCounts: Map<string, number> = new Map();
  private slowThreshold = 1.0; // Log operations taking > 1 second

  /**
   * Record the duration of an operation.
   */
  recordDuration(operation: string, duration: number): void {
    // Get or initialize durations array
    let durations = this.metrics.get(operation) || [];
    durations.push(duration);

    // Update count
    const count = (this.operationCounts.get(operation) || 0) + 1;
    this.operationCounts.set(operation, count);

    // Log slow operations
    if (duration > this.slowThreshold) {
      console.warn(`Slow operation '${operation}': ${duration.toFixed(3)}s`);
    }

    // Keep only last 1000 measurements per operation
    if (durations.length > 1000) {
      durations = durations.slice(-1000);
    }

    this.metrics.set(operation, durations);
  }

  /**
   * Get statistics for an operation.
   */
  getStats(operation: string): PerformanceStats | null {
    const durations = this.metrics.get(operation);
    if (!durations || durations.length === 0) {
      return null;
    }

    return {
      operation,
      count: durations.length,
      total_calls: this.operationCounts.get(operation) || 0,
      min: Math.min(...durations),
      max: Math.max(...durations),
      mean: this.mean(durations),
      median: this.median(durations),
      p95: this.percentile(durations, 0.95),
      p99: this.percentile(durations, 0.99),
    };
  }

  /**
   * Get statistics for all operations.
   */
  getAllStats(): Record<string, PerformanceStats> {
    const result: Record<string, PerformanceStats> = {};
    for (const operation of this.metrics.keys()) {
      const stats = this.getStats(operation);
      if (stats) {
        result[operation] = stats;
      }
    }
    return result;
  }

  /**
   * Calculate mean of array.
   */
  private mean(data: number[]): number {
    if (data.length === 0) return 0;
    return data.reduce((a, b) => a + b, 0) / data.length;
  }

  /**
   * Calculate median of array.
   */
  private median(data: number[]): number {
    if (data.length === 0) return 0;
    const sorted = [...data].sort((a, b) => a - b);
    const mid = Math.floor(sorted.length / 2);
    return sorted.length % 2 !== 0
      ? sorted[mid]
      : (sorted[mid - 1] + sorted[mid]) / 2;
  }

  /**
   * Calculate percentile value.
   */
  private percentile(data: number[], pct: number): number {
    if (data.length === 0) return 0;
    const sorted = [...data].sort((a, b) => a - b);
    const index = Math.floor(sorted.length * pct);
    return sorted[Math.min(index, sorted.length - 1)];
  }

  /**
   * Clear metrics for an operation or all operations.
   */
  clear(operation?: string): void {
    if (operation) {
      this.metrics.delete(operation);
      this.operationCounts.delete(operation);
    } else {
      this.metrics.clear();
      this.operationCounts.clear();
    }
  }

  /**
   * Set the threshold for logging slow operations.
   */
  setSlowThreshold(threshold: number): void {
    this.slowThreshold = threshold;
    console.log(`Slow operation threshold set to ${threshold}s`);
  }
}

// ============================================================================
// Global Instance
// ============================================================================

const monitor = new PerformanceMonitor();

export function getMonitor(): PerformanceMonitor {
  return monitor;
}

// ============================================================================
// Decorator for measuring performance
// ============================================================================

/**
 * Decorator to measure function performance.
 *
 * Usage:
 *   @measurePerformance()
 *   processEvent(player, event) {
 *     // function code
 *   }
 *
 *   @measurePerformance("custom_operation")
 *   complexCalculation() {
 *     // function code
 *   }
 */
export function measurePerformance(operationName?: string) {
  return function <T extends (...args: unknown[]) => unknown>(
    target: object,
    propertyKey: string,
    descriptor: TypedPropertyDescriptor<T>
  ): TypedPropertyDescriptor<T> {
    const originalMethod = descriptor.value;
    if (!originalMethod) return descriptor;

    const opName = operationName || propertyKey;

    descriptor.value = function (this: unknown, ...args: unknown[]): ReturnType<T> {
      const startTime = performance.now();
      try {
        const result = originalMethod.apply(this, args);

        // Handle async functions
        if (result instanceof Promise) {
          return result.finally(() => {
            const duration = (performance.now() - startTime) / 1000;
            monitor.recordDuration(opName, duration);
          }) as ReturnType<T>;
        }

        const duration = (performance.now() - startTime) / 1000;
        monitor.recordDuration(opName, duration);
        return result as ReturnType<T>;
      } catch (error) {
        const duration = (performance.now() - startTime) / 1000;
        monitor.recordDuration(opName, duration);
        throw error;
      }
    } as T;

    return descriptor;
  };
}

// ============================================================================
// Measure Block Class
// ============================================================================

/**
 * Class for measuring performance of code blocks.
 *
 * Usage:
 *   const block = new MeasureBlock('database_query');
 *   block.start();
 *   await executeQuery();
 *   block.end();
 */
export class MeasureBlock {
  private operationName: string;
  private startTime: number | null = null;

  constructor(operationName: string) {
    this.operationName = operationName;
  }

  start(): void {
    this.startTime = performance.now();
  }

  end(): void {
    if (this.startTime !== null) {
      const duration = (performance.now() - this.startTime) / 1000;
      monitor.recordDuration(this.operationName, duration);
      this.startTime = null;
    }
  }
}

/**
 * Helper function to measure a code block.
 *
 * Usage:
 *   const result = await measureBlock('database_query', async () => {
 *     return await executeQuery();
 *   });
 */
export async function measureBlock<T>(
  operationName: string,
  fn: () => T | Promise<T>
): Promise<T> {
  const startTime = performance.now();
  try {
    const result = await fn();
    return result;
  } finally {
    const duration = (performance.now() - startTime) / 1000;
    monitor.recordDuration(operationName, duration);
  }
}

// ============================================================================
// Convenience Functions
// ============================================================================

/**
 * Log a performance report for all monitored operations.
 */
export function logPerformanceReport(): void {
  const stats = monitor.getAllStats();
  const operations = Object.keys(stats);

  if (operations.length === 0) {
    console.log('No performance data available');
    return;
  }

  console.log('=== Performance Report ===');

  // Sort by mean duration (descending)
  operations
    .sort((a, b) => stats[b].mean - stats[a].mean)
    .forEach((operation) => {
      const data = stats[operation];
      console.log(
        `${operation}: ` +
          `calls=${data.total_calls}, ` +
          `mean=${(data.mean * 1000).toFixed(1)}ms, ` +
          `median=${(data.median * 1000).toFixed(1)}ms, ` +
          `p95=${(data.p95 * 1000).toFixed(1)}ms, ` +
          `max=${(data.max * 1000).toFixed(1)}ms`
      );
    });
}

// ============================================================================
// Export
// ============================================================================

export const performanceMonitor = {
  PerformanceMonitor,
  MeasureBlock,
  getMonitor,
  measurePerformance,
  measureBlock,
  logPerformanceReport,
};
