/**
 * Performance monitoring utilities for the email library
 */

export interface PerformanceMetrics {
  operation: string;
  duration: number;
  timestamp: Date;
  success: boolean;
  error: string | undefined;
  metadata: Record<string, any> | undefined;
}

export interface PerformanceConfig {
  enabled: boolean;
  thresholdMs: number;
  logSlowOperations: boolean;
  trackMemoryUsage: boolean;
}

export class PerformanceMonitor {
  private static instance: PerformanceMonitor;
  private metrics: PerformanceMetrics[] = [];
  private config: PerformanceConfig;
  private startTimes: Map<string, number> = new Map();
  private metadataStore: Map<string, string> = new Map();

  private constructor(config: Partial<PerformanceConfig> = {}) {
    this.config = {
      enabled: true,
      thresholdMs: 1000,
      logSlowOperations: true,
      trackMemoryUsage: false,
      ...config
    };
  }

  static getInstance(config?: Partial<PerformanceConfig>): PerformanceMonitor {
    if (!PerformanceMonitor.instance) {
      PerformanceMonitor.instance = new PerformanceMonitor(config);
    }
    return PerformanceMonitor.instance;
  }

  /**
   * Start timing an operation
   */
  startOperation(operation: string, metadata?: Record<string, any>): void {
    if (!this.config.enabled) return;
    
    this.startTimes.set(operation, Date.now());
    if (metadata) {
      this.metadataStore.set(operation, JSON.stringify(metadata));
    }
  }

  /**
   * End timing an operation and record metrics
   */
  endOperation(operation: string, success: boolean, error?: string): PerformanceMetrics | null {
    if (!this.config.enabled) return null;

    const startTime = this.startTimes.get(operation);
    if (!startTime) {
      console.warn(`PerformanceMonitor: No start time found for operation: ${operation}`);
      return null;
    }

    const duration = Date.now() - startTime;
    const timestamp = new Date();
    
    const metric: PerformanceMetrics = {
      operation,
      duration,
      timestamp,
      success,
      error,
      metadata: this.getMetadata(operation)
    };

    this.metrics.push(metric);
    this.startTimes.delete(operation);
    this.metadataStore.delete(operation);

    // Log slow operations
    if (this.config.logSlowOperations && duration > this.config.thresholdMs) {
      console.warn(`Slow operation detected: ${operation} took ${duration}ms`);
    }

    return metric;
  }

  /**
   * Get performance summary
   */
  getSummary(): {
    totalOperations: number;
    averageDuration: number;
    slowOperations: PerformanceMetrics[];
    successRate: number;
    topOperations: Array<{ operation: string; count: number; avgDuration: number }>;
  } {
    if (this.metrics.length === 0) {
      return {
        totalOperations: 0,
        averageDuration: 0,
        slowOperations: [],
        successRate: 0,
        topOperations: []
      };
    }

    const totalOperations = this.metrics.length;
    const averageDuration = this.metrics.reduce((sum, m) => sum + m.duration, 0) / totalOperations;
    const slowOperations = this.metrics.filter(m => m.duration > this.config.thresholdMs);
    const successRate = this.metrics.filter(m => m.success).length / totalOperations;

    // Group by operation
    const operationStats = new Map<string, { count: number; totalDuration: number }>();
    this.metrics.forEach(metric => {
      const existing = operationStats.get(metric.operation) || { count: 0, totalDuration: 0 };
      existing.count++;
      existing.totalDuration += metric.duration;
      operationStats.set(metric.operation, existing);
    });

    const topOperations = Array.from(operationStats.entries())
      .map(([operation, stats]) => ({
        operation,
        count: stats.count,
        avgDuration: stats.totalDuration / stats.count
      }))
      .sort((a, b) => b.count - a.count)
      .slice(0, 10);

    return {
      totalOperations,
      averageDuration,
      slowOperations,
      successRate,
      topOperations
    };
  }

  /**
   * Clear all metrics
   */
  clearMetrics(): void {
    this.metrics = [];
    this.startTimes.clear();
  }

  /**
   * Get all metrics
   */
  getMetrics(): PerformanceMetrics[] {
    return [...this.metrics];
  }

  /**
   * Update configuration
   */
  updateConfig(config: Partial<PerformanceConfig>): void {
    this.config = { ...this.config, ...config };
  }

  private getMetadata(operation: string): Record<string, any> | undefined {
    const metadataStr = this.metadataStore.get(operation);
    if (metadataStr) {
      try {
        return JSON.parse(metadataStr);
      } catch {
        return undefined;
      }
    }
    return undefined;
  }
}

/**
 * Performance decorator for methods
 */
export function trackPerformance(operationName?: string) {
  return function (target: any, propertyName: string, descriptor: PropertyDescriptor) {
    const method = descriptor.value;
    const monitor = PerformanceMonitor.getInstance();

    descriptor.value = async function (...args: any[]) {
      const operation = operationName || `${target.constructor.name}.${propertyName}`;
      
      monitor.startOperation(operation);
      
      try {
        const result = await method.apply(this, args);
        monitor.endOperation(operation, true);
        return result;
      } catch (error) {
        monitor.endOperation(operation, false, error instanceof Error ? error.message : String(error));
        throw error;
      }
    };
  };
} 