import { EventEmitter } from 'events';
import { logger } from '../utils/logger';

export interface UploadMetrics {
  totalUploads: number;
  successfulUploads: number;
  failedUploads: number;
  successRate: number;
  averageProcessingTime: number;
  totalProcessingTime: number;
  uploadsByHour: { [hour: string]: number };
  errorsByType: { [errorType: string]: number };
  errorsByStage: { [stage: string]: number };
  fileSizeDistribution: {
    small: number; // < 1MB
    medium: number; // 1MB - 10MB
    large: number; // > 10MB
  };
  processingTimeDistribution: {
    fast: number; // < 30 seconds
    normal: number; // 30 seconds - 5 minutes
    slow: number; // > 5 minutes
  };
}

export interface UploadEvent {
  id: string;
  userId: string;
  fileInfo: {
    originalName: string;
    size: number;
    mimetype: string;
  };
  status: 'started' | 'success' | 'failed';
  stage?: string;
  error?: {
    message: string;
    code?: string;
    type: string;
  };
  processingTime?: number;
  timestamp: Date;
  correlationId?: string;
}

export interface UploadEventInput {
  userId: string;
  fileInfo: {
    originalName: string;
    size: number;
    mimetype: string;
  };
  status: 'started' | 'success' | 'failed';
  stage?: string;
  error?: {
    message: string;
    code?: string;
    type: string;
  };
  processingTime?: number;
  correlationId?: string;
}

export interface UploadHealthStatus {
  status: 'healthy' | 'degraded' | 'unhealthy';
  successRate: number;
  averageProcessingTime: number;
  recentErrors: UploadEvent[];
  recommendations: string[];
  timestamp: Date;
}

class UploadMonitoringService extends EventEmitter {
  private uploadEvents: UploadEvent[] = [];
  private maxEvents: number = 10000; // Keep last 10k events
  private healthThresholds = {
    successRate: {
      healthy: 0.95,
      degraded: 0.85,
    },
    averageProcessingTime: {
      healthy: 30000, // 30 seconds
      degraded: 120000, // 2 minutes
    },
    maxRecentErrors: 10,
  };

  constructor() {
    super();
    logger.info('Upload monitoring service initialized');
  }

  /**
   * Track upload event
   */
  trackUploadEvent(event: UploadEventInput): void {
    const uploadEvent: UploadEvent = {
      ...event,
      id: `upload_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
      timestamp: new Date(),
    };

    this.uploadEvents.push(uploadEvent);

    // Keep only the last maxEvents
    if (this.uploadEvents.length > this.maxEvents) {
      this.uploadEvents = this.uploadEvents.slice(-this.maxEvents);
    }

    // Emit event for real-time monitoring
    this.emit('uploadEvent', uploadEvent);

    // Log structured event
    if (uploadEvent.status === 'success') {
      logger.info('Upload tracked - success', {
        category: 'monitoring',
        operation: 'upload_tracked',
        uploadId: uploadEvent.id,
        userId: uploadEvent.userId,
        processingTime: uploadEvent.processingTime,
        correlationId: uploadEvent.correlationId,
      });
    } else if (uploadEvent.status === 'failed') {
      logger.error('Upload tracked - failure', {
        category: 'monitoring',
        operation: 'upload_tracked',
        uploadId: uploadEvent.id,
        userId: uploadEvent.userId,
        error: uploadEvent.error,
        stage: uploadEvent.stage,
        correlationId: uploadEvent.correlationId,
      });
    }
  }

  /**
   * Get upload metrics for a time period
   */
  getUploadMetrics(hours: number = 24): UploadMetrics {
    const cutoffTime = new Date(Date.now() - hours * 60 * 60 * 1000);
    const recentEvents = this.uploadEvents.filter(event => event.timestamp >= cutoffTime);

    const totalUploads = recentEvents.length;
    const successfulUploads = recentEvents.filter(event => event.status === 'success').length;
    const failedUploads = recentEvents.filter(event => event.status === 'failed').length;
    const successRate = totalUploads > 0 ? successfulUploads / totalUploads : 1;

    // Calculate processing times
    const successfulEvents = recentEvents.filter(event => event.status === 'success' && event.processingTime);
    const totalProcessingTime = successfulEvents.reduce((sum: number, event: any) => sum + (event.processingTime || 0), 0);
    const averageProcessingTime = successfulEvents.length > 0 ? totalProcessingTime / successfulEvents.length : 0;

    // Group by hour
    const uploadsByHour: { [hour: string]: number } = {};
    recentEvents.forEach(event => {
      const hour = event.timestamp.toISOString().substring(0, 13) + ':00:00Z';
      uploadsByHour[hour] = (uploadsByHour[hour] || 0) + 1;
    });

    // Group errors by type
    const errorsByType: { [errorType: string]: number } = {};
    recentEvents
      .filter(event => event.status === 'failed' && event.error)
      .forEach(event => {
        const errorType = event.error!.type || 'unknown';
        errorsByType[errorType] = (errorsByType[errorType] || 0) + 1;
      });

    // Group errors by stage
    const errorsByStage: { [stage: string]: number } = {};
    recentEvents
      .filter(event => event.status === 'failed' && event.stage)
      .forEach(event => {
        const stage = event.stage!;
        errorsByStage[stage] = (errorsByStage[stage] || 0) + 1;
      });

    // File size distribution
    const fileSizeDistribution = {
      small: recentEvents.filter(event => event.fileInfo.size < 1024 * 1024).length,
      medium: recentEvents.filter(event => event.fileInfo.size >= 1024 * 1024 && event.fileInfo.size < 10 * 1024 * 1024).length,
      large: recentEvents.filter(event => event.fileInfo.size >= 10 * 1024 * 1024).length,
    };

    // Processing time distribution
    const processingTimeDistribution = {
      fast: successfulEvents.filter(event => (event.processingTime || 0) < 30000).length,
      normal: successfulEvents.filter(event => (event.processingTime || 0) >= 30000 && (event.processingTime || 0) < 300000).length,
      slow: successfulEvents.filter(event => (event.processingTime || 0) >= 300000).length,
    };

    return {
      totalUploads,
      successfulUploads,
      failedUploads,
      successRate,
      averageProcessingTime,
      totalProcessingTime,
      uploadsByHour,
      errorsByType,
      errorsByStage,
      fileSizeDistribution,
      processingTimeDistribution,
    };
  }

  /**
   * Get upload health status
   */
  getUploadHealthStatus(): UploadHealthStatus {
    const metrics = this.getUploadMetrics(1); // Last hour
    const recentErrors = this.uploadEvents
      .filter(event => event.status === 'failed' && event.timestamp >= new Date(Date.now() - 60 * 60 * 1000))
      .slice(-this.healthThresholds.maxRecentErrors);

    // Determine health status
    let status: 'healthy' | 'degraded' | 'unhealthy' = 'healthy';
    const recommendations: string[] = [];

    if (metrics.successRate < this.healthThresholds.successRate.healthy) {
      if (metrics.successRate < this.healthThresholds.successRate.degraded) {
        status = 'unhealthy';
        recommendations.push('Critical: Upload success rate is below acceptable threshold');
      } else {
        status = 'degraded';
        recommendations.push('Warning: Upload success rate is below optimal threshold');
      }
    }

    if (metrics.averageProcessingTime > this.healthThresholds.averageProcessingTime.healthy) {
      if (metrics.averageProcessingTime > this.healthThresholds.averageProcessingTime.degraded) {
        status = status === 'healthy' ? 'degraded' : status;
        recommendations.push('Warning: Average processing time is significantly high');
      } else {
        status = status === 'healthy' ? 'degraded' : status;
        recommendations.push('Info: Processing time is above optimal threshold');
      }
    }

    // Add specific recommendations based on error patterns
    if (Object.keys(metrics.errorsByStage).length > 0) {
      const topErrorStage = Object.entries(metrics.errorsByStage)
        .sort(([, a], [, b]) => b - a)[0];
      if (topErrorStage) {
        recommendations.push(`Most common error stage: ${topErrorStage[0]} (${topErrorStage[1]} errors)`);
      }
    }

    if (Object.keys(metrics.errorsByType).length > 0) {
      const topErrorType = Object.entries(metrics.errorsByType)
        .sort(([, a], [, b]) => b - a)[0];
      if (topErrorType) {
        recommendations.push(`Most common error type: ${topErrorType[0]} (${topErrorType[1]} errors)`);
      }
    }

    return {
      status,
      successRate: metrics.successRate,
      averageProcessingTime: metrics.averageProcessingTime,
      recentErrors,
      recommendations,
      timestamp: new Date(),
    };
  }

  /**
   * Get real-time upload statistics
   */
  getRealTimeStats(): {
    activeUploads: number;
    uploadsLastMinute: number;
    uploadsLastHour: number;
    currentSuccessRate: number;
  } {
    const now = new Date();
    const oneMinuteAgo = new Date(now.getTime() - 60 * 1000);
    const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000);

    const uploadsLastMinute = this.uploadEvents.filter(event => event.timestamp >= oneMinuteAgo).length;
    const uploadsLastHour = this.uploadEvents.filter(event => event.timestamp >= oneHourAgo).length;
    
    const recentUploads = this.uploadEvents.filter(event => event.timestamp >= oneHourAgo);
    const currentSuccessRate = recentUploads.length > 0 
      ? recentUploads.filter(event => event.status === 'success').length / recentUploads.length 
      : 1;

    // Estimate active uploads (uploads started in last 5 minutes that haven't completed)
    const fiveMinutesAgo = new Date(now.getTime() - 5 * 60 * 1000);
    const recentStarted = this.uploadEvents.filter(event => 
      event.status === 'started' && event.timestamp >= fiveMinutesAgo
    );
    const recentCompleted = this.uploadEvents.filter(event => 
      (event.status === 'success' || event.status === 'failed') && event.timestamp >= fiveMinutesAgo
    );
    const activeUploads = Math.max(0, recentStarted.length - recentCompleted.length);

    return {
      activeUploads,
      uploadsLastMinute,
      uploadsLastHour,
      currentSuccessRate,
    };
  }

  /**
   * Get error analysis for debugging
   */
  getErrorAnalysis(hours: number = 24): {
    topErrorTypes: Array<{ type: string; count: number; percentage: number }>;
    topErrorStages: Array<{ stage: string; count: number; percentage: number }>;
    errorTrends: Array<{ hour: string; errorCount: number; totalCount: number }>;
  } {
    const cutoffTime = new Date(Date.now() - hours * 60 * 60 * 1000);
    const recentEvents = this.uploadEvents.filter(event => event.timestamp >= cutoffTime);
    const failedEvents = recentEvents.filter(event => event.status === 'failed');

    // Top error types
    const errorTypeCounts: { [type: string]: number } = {};
    failedEvents.forEach(event => {
      if (event.error) {
        const type = event.error.type || 'unknown';
        errorTypeCounts[type] = (errorTypeCounts[type] || 0) + 1;
      }
    });

    const topErrorTypes = Object.entries(errorTypeCounts)
      .map(([type, count]) => ({
        type,
        count,
        percentage: (count / failedEvents.length) * 100,
      }))
      .sort((a, b) => b.count - a.count)
      .slice(0, 10);

    // Top error stages
    const errorStageCounts: { [stage: string]: number } = {};
    failedEvents.forEach(event => {
      if (event.stage) {
        errorStageCounts[event.stage] = (errorStageCounts[event.stage] || 0) + 1;
      }
    });

    const topErrorStages = Object.entries(errorStageCounts)
      .map(([stage, count]) => ({
        stage,
        count,
        percentage: (count / failedEvents.length) * 100,
      }))
      .sort((a, b) => b.count - a.count)
      .slice(0, 10);

    // Error trends by hour
    const errorTrends: { [hour: string]: { errorCount: number; totalCount: number } } = {};
    recentEvents.forEach(event => {
      const hour = event.timestamp.toISOString().substring(0, 13) + ':00:00Z';
      if (!errorTrends[hour]) {
        errorTrends[hour] = { errorCount: 0, totalCount: 0 };
      }
      errorTrends[hour].totalCount++;
      if (event.status === 'failed') {
        errorTrends[hour].errorCount++;
      }
    });

    const errorTrendsArray = Object.entries(errorTrends)
      .map(([hour, counts]) => ({
        hour,
        errorCount: counts.errorCount,
        totalCount: counts.totalCount,
      }))
      .sort((a, b) => a.hour.localeCompare(b.hour));

    return {
      topErrorTypes,
      topErrorStages,
      errorTrends: errorTrendsArray,
    };
  }

  /**
   * Clear old events (for cleanup)
   */
  clearOldEvents(daysToKeep: number = 7): number {
    const cutoffTime = new Date(Date.now() - daysToKeep * 24 * 60 * 60 * 1000);
    const initialCount = this.uploadEvents.length;
    this.uploadEvents = this.uploadEvents.filter(event => event.timestamp >= cutoffTime);
    const removedCount = initialCount - this.uploadEvents.length;
    
    logger.info('Cleared old upload events', {
      category: 'monitoring',
      operation: 'clear_old_events',
      removedCount,
      remainingCount: this.uploadEvents.length,
      daysToKeep,
    });

    return removedCount;
  }
}

// Export singleton instance
export const uploadMonitoringService = new UploadMonitoringService(); 