/**
 * Performance monitoring utilities for tracking execution time and metrics
 *
 * Usage:
 * ```typescript
 * const timer = startTimer('database-query');
 * // ... do work
 * timer.end(); // logs duration
 *
 * // Or with async functions:
 * const result = await measureAsync('fetch-users', async () => {
 *   return await db.getAllUsers();
 * });
 * ```
 */

import log from "../logger";

export interface PerformanceMetrics {
  operation: string;
  duration: number;
  timestamp: Date;
  metadata?: Record<string, any>;
}

// Store recent metrics in memory (last 1000 operations)
const metricsHistory: PerformanceMetrics[] = [];
const MAX_HISTORY_SIZE = 1000;

/**
 * Timer for tracking operation duration
 */
export interface Timer {
  operation: string;
  startTime: number;
  end: (metadata?: Record<string, any>) => number;
}

/**
 * Start a performance timer
 *
 * @param operation - Name of the operation being timed
 * @returns Timer object with end() method
 *
 * @example
 * const timer = startTimer('database-query');
 * const users = await db.getAllUsers();
 * timer.end({ count: users.length });
 */
export function startTimer(operation: string): Timer {
  const startTime = performance.now();

  return {
    operation,
    startTime,
    end(metadata?: Record<string, any>): number {
      const duration = performance.now() - startTime;
      recordMetric(operation, duration, metadata);
      return duration;
    },
  };
}

/**
 * Measure the execution time of a synchronous function
 *
 * @param operation - Name of the operation
 * @param fn - Function to execute and measure
 * @returns Result of the function
 */
export function measure<T>(operation: string, fn: () => T): T {
  const timer = startTimer(operation);
  const result = fn();
  timer.end();
  return result;
}

/**
 * Measure the execution time of an async function
 *
 * @param operation - Name of the operation
 * @param fn - Async function to execute and measure
 * @param metadata - Additional metadata to log
 * @returns Result of the function
 *
 * @example
 * const users = await measureAsync('fetch-users', async () => {
 *   return await db.getAllUsers();
 * }, { limit: 100 });
 */
export async function measureAsync<T>(
  operation: string,
  fn: () => Promise<T>,
  metadata?: Record<string, any>,
): Promise<T> {
  const timer = startTimer(operation);
  try {
    const result = await fn();
    timer.end(metadata);
    return result;
  } catch (error) {
    timer.end({ ...metadata, error: true });
    throw error;
  }
}

/**
 * Record a performance metric
 */
function recordMetric(
  operation: string,
  duration: number,
  metadata?: Record<string, any>,
): void {
  const metric: PerformanceMetrics = {
    operation,
    duration,
    timestamp: new Date(),
    metadata,
  };

  // Add to history (keep last MAX_HISTORY_SIZE entries)
  metricsHistory.push(metric);
  if (metricsHistory.length > MAX_HISTORY_SIZE) {
    metricsHistory.shift();
  }

  // Log based on duration (warn for slow operations)
  if (duration > 1000) {
    log.warn(`Slow operation: ${operation}`, {
      duration: `${duration.toFixed(2)}ms`,
      ...metadata,
    });
  } else if (duration > 500) {
    log.info(`Operation: ${operation}`, {
      duration: `${duration.toFixed(2)}ms`,
      ...metadata,
    });
  } else {
    log.debug(`Operation: ${operation}`, {
      duration: `${duration.toFixed(2)}ms`,
      ...metadata,
    });
  }
}

/**
 * Get performance statistics for a specific operation
 */
export function getOperationStats(operation: string): {
  count: number;
  avgDuration: number;
  minDuration: number;
  maxDuration: number;
  totalDuration: number;
} | null {
  const metrics = metricsHistory.filter((m) => m.operation === operation);

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

  const durations = metrics.map((m) => m.duration);
  const totalDuration = durations.reduce((sum, d) => sum + d, 0);

  return {
    count: metrics.length,
    avgDuration: totalDuration / metrics.length,
    minDuration: Math.min(...durations),
    maxDuration: Math.max(...durations),
    totalDuration,
  };
}

/**
 * Get all recent metrics (last MAX_HISTORY_SIZE operations)
 */
export function getRecentMetrics(limit?: number): PerformanceMetrics[] {
  const count = limit ?? metricsHistory.length;
  return metricsHistory.slice(-count);
}

/**
 * Get aggregated statistics for all operations
 */
export function getAllStats(): Record<
  string,
  {
    count: number;
    avgDuration: number;
    minDuration: number;
    maxDuration: number;
  }
> {
  const operations = Array.from(
    new Set(metricsHistory.map((m) => m.operation)),
  );
  const stats: Record<string, any> = {};

  for (const operation of operations) {
    const opStats = getOperationStats(operation);
    if (opStats) {
      stats[operation] = {
        count: opStats.count,
        avgDuration: opStats.avgDuration,
        minDuration: opStats.minDuration,
        maxDuration: opStats.maxDuration,
      };
    }
  }

  return stats;
}

/**
 * Clear all metrics history
 */
export function clearMetrics(): void {
  metricsHistory.length = 0;
}

/**
 * Decorator for measuring class methods (experimental)
 */
export function Measure(operation?: string) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor,
  ) {
    const originalMethod = descriptor.value;
    const opName = operation || `${target.constructor.name}.${propertyKey}`;

    descriptor.value = async function (...args: any[]) {
      return measureAsync(opName, () => originalMethod.apply(this, args));
    };

    return descriptor;
  };
}
