import * as plugins from '../plugins.js';
import { logger } from '../logger.js';

/**
 * Log level for security events
 */
export enum SecurityLogLevel {
  INFO = 'info',
  WARN = 'warn',
  ERROR = 'error',
  CRITICAL = 'critical'
}

/**
 * Security event types for categorization
 */
export enum SecurityEventType {
  AUTHENTICATION = 'authentication',
  ACCESS_CONTROL = 'access_control',
  EMAIL_VALIDATION = 'email_validation',
  EMAIL_PROCESSING = 'email_processing',
  EMAIL_FORWARDING = 'email_forwarding',
  EMAIL_DELIVERY = 'email_delivery',
  DKIM = 'dkim',
  SPF = 'spf',
  DMARC = 'dmarc',
  RATE_LIMIT = 'rate_limit',
  RATE_LIMITING = 'rate_limiting',
  SPAM = 'spam',
  MALWARE = 'malware',
  CONNECTION = 'connection',
  DATA_EXPOSURE = 'data_exposure',
  CONFIGURATION = 'configuration',
  IP_REPUTATION = 'ip_reputation',
  REJECTED_CONNECTION = 'rejected_connection'
}

/**
 * Security event interface
 */
export interface ISecurityEvent {
  timestamp: number;
  level: SecurityLogLevel;
  type: SecurityEventType;
  message: string;
  details?: any;
  ipAddress?: string;
  userId?: string;
  sessionId?: string;
  emailId?: string;
  domain?: string;
  action?: string;
  result?: string;
  success?: boolean;
}

/**
 * Security logger for enhanced security monitoring
 */
export class SecurityLogger {
  private static instance: SecurityLogger | undefined;
  private securityEvents: ISecurityEvent[] = [];
  private maxEventHistory: number;
  private enableNotifications: boolean;
  
  private constructor(options?: {
    maxEventHistory?: number;
    enableNotifications?: boolean;
  }) {
    this.maxEventHistory = options?.maxEventHistory || 1000;
    this.enableNotifications = options?.enableNotifications || false;
  }
  
  /**
   * Get singleton instance
   */
  public static getInstance(options?: {
    maxEventHistory?: number;
    enableNotifications?: boolean;
  }): SecurityLogger {
    if (!SecurityLogger.instance) {
      SecurityLogger.instance = new SecurityLogger(options);
    }
    return SecurityLogger.instance;
  }

  /**
   * Reset the singleton instance (for shutdown/testing)
   */
  public static resetInstance(): void {
    SecurityLogger.instance = undefined;
  }

  /**
   * Log a security event
   * @param event The security event to log
   */
  public logEvent(event: Omit<ISecurityEvent, 'timestamp'>): void {
    const fullEvent: ISecurityEvent = {
      ...event,
      timestamp: Date.now()
    };
    
    // Store in memory buffer
    this.securityEvents.push(fullEvent);
    
    // Trim history if needed
    if (this.securityEvents.length > this.maxEventHistory) {
      this.securityEvents.shift();
    }
    
    // Log to regular logger with appropriate level
    switch (event.level) {
      case SecurityLogLevel.INFO:
        logger.log('info', `[SECURITY:${event.type}] ${event.message}`, event.details);
        break;
      case SecurityLogLevel.WARN:
        logger.log('warn', `[SECURITY:${event.type}] ${event.message}`, event.details);
        break;
      case SecurityLogLevel.ERROR:
      case SecurityLogLevel.CRITICAL:
        logger.log('error', `[SECURITY:${event.type}] ${event.message}`, event.details);
        
        // Send notification for critical events if enabled
        if (event.level === SecurityLogLevel.CRITICAL && this.enableNotifications) {
          this.sendNotification(fullEvent);
        }
        break;
    }
  }
  
  /**
   * Get recent security events
   * @param limit Maximum number of events to return
   * @param filter Filter for specific event types
   * @returns Recent security events
   */
  public getRecentEvents(limit: number = 100, filter?: {
    level?: SecurityLogLevel;
    type?: SecurityEventType;
    fromTimestamp?: number;
    toTimestamp?: number;
  }): ISecurityEvent[] {
    let filteredEvents = this.securityEvents;
    
    // Apply filters
    if (filter) {
      if (filter.level) {
        filteredEvents = filteredEvents.filter(event => event.level === filter.level);
      }
      
      if (filter.type) {
        filteredEvents = filteredEvents.filter(event => event.type === filter.type);
      }
      
      if (filter.fromTimestamp) {
        const fromTs = filter.fromTimestamp;
        filteredEvents = filteredEvents.filter(event => event.timestamp >= fromTs);
      }

      if (filter.toTimestamp) {
        const toTs = filter.toTimestamp;
        filteredEvents = filteredEvents.filter(event => event.timestamp <= toTs);
      }
    }
    
    // Return most recent events up to limit (slice first to avoid mutating source)
    return filteredEvents
      .slice()
      .sort((a, b) => b.timestamp - a.timestamp)
      .slice(0, limit);
  }
  
  /**
   * Get events by security level
   * @param level The security level to filter by
   * @param limit Maximum number of events to return
   * @returns Security events matching the level
   */
  public getEventsByLevel(level: SecurityLogLevel, limit: number = 100): ISecurityEvent[] {
    return this.getRecentEvents(limit, { level });
  }
  
  /**
   * Get events by security type
   * @param type The event type to filter by
   * @param limit Maximum number of events to return
   * @returns Security events matching the type
   */
  public getEventsByType(type: SecurityEventType, limit: number = 100): ISecurityEvent[] {
    return this.getRecentEvents(limit, { type });
  }
  
  /**
   * Get security events for a specific IP address
   * @param ipAddress The IP address to filter by
   * @param limit Maximum number of events to return
   * @returns Security events for the IP address
   */
  public getEventsByIP(ipAddress: string, limit: number = 100): ISecurityEvent[] {
    return this.securityEvents
      .filter(event => event.ipAddress === ipAddress)
      .sort((a, b) => b.timestamp - a.timestamp)
      .slice(0, limit);
  }
  
  /**
   * Get security events for a specific domain
   * @param domain The domain to filter by
   * @param limit Maximum number of events to return
   * @returns Security events for the domain
   */
  public getEventsByDomain(domain: string, limit: number = 100): ISecurityEvent[] {
    return this.securityEvents
      .filter(event => event.domain === domain)
      .sort((a, b) => b.timestamp - a.timestamp)
      .slice(0, limit);
  }
  
  /**
   * Send a notification for critical security events
   * @param event The security event to notify about
   * @private
   */
  private sendNotification(event: ISecurityEvent): void {
    // In a production environment, this would integrate with a notification service
    // For now, we'll just log that we would send a notification
    logger.log('error', `[SECURITY NOTIFICATION] ${event.message}`, {
      ...event,
      notificationSent: true
    });
    
    // Future integration with alerting systems would go here
  }
  
  /**
   * Clear event history
   */
  public clearEvents(): void {
    this.securityEvents = [];
  }
  
  /**
   * Get statistical summary of security events
   * @param timeWindow Optional time window in milliseconds
   * @returns Summary of security events
   */
  public getEventsSummary(timeWindow?: number): {
    total: number;
    byLevel: Record<SecurityLogLevel, number>;
    byType: Record<SecurityEventType, number>;
    topIPs: Array<{ ip: string; count: number }>;
    topDomains: Array<{ domain: string; count: number }>;
  } {
    const cutoff = timeWindow ? Date.now() - timeWindow : 0;

    // Initialize counters
    const byLevel = {} as Record<SecurityLogLevel, number>;
    for (const level of Object.values(SecurityLogLevel)) {
      byLevel[level] = 0;
    }
    const byType = {} as Record<SecurityEventType, number>;
    for (const type of Object.values(SecurityEventType)) {
      byType[type] = 0;
    }
    const ipCounts = new Map<string, number>();
    const domainCounts = new Map<string, number>();

    // Single pass over all events
    let total = 0;
    for (const e of this.securityEvents) {
      if (cutoff && e.timestamp < cutoff) continue;
      total++;
      byLevel[e.level]++;
      byType[e.type]++;
      if (e.ipAddress) {
        ipCounts.set(e.ipAddress, (ipCounts.get(e.ipAddress) || 0) + 1);
      }
      if (e.domain) {
        domainCounts.set(e.domain, (domainCounts.get(e.domain) || 0) + 1);
      }
    }

    // Sort and limit top entries
    const topIPs = Array.from(ipCounts.entries())
      .map(([ip, count]) => ({ ip, count }))
      .sort((a, b) => b.count - a.count)
      .slice(0, 10);

    const topDomains = Array.from(domainCounts.entries())
      .map(([domain, count]) => ({ domain, count }))
      .sort((a, b) => b.count - a.count)
      .slice(0, 10);

    return { total, byLevel, byType, topIPs, topDomains };
  }
}