/**
 * Adaptive SMTP Logging System
 * Automatically switches between logging modes based on server load (active connections)
 * to maintain performance during high-concurrency scenarios
 */

import * as plugins from '../../../../plugins.js';
import { logger } from '../../../../logger.js';
import { SecurityLogLevel, SecurityEventType } from '../constants.js';
import type { ISmtpSession } from '../interfaces.js';
import type { LogLevel, ISmtpLogOptions } from './logging.js';

/**
 * Log modes based on server load
 */
export enum LogMode {
  VERBOSE = 'VERBOSE',     // < 20 connections: Full detailed logging
  REDUCED = 'REDUCED',     // 20-40 connections: Limited command/response logging, full error logging
  MINIMAL = 'MINIMAL'      // 40+ connections: Aggregated logging only, critical errors only
}

/**
 * Configuration for adaptive logging thresholds
 */
export interface IAdaptiveLogConfig {
  verboseThreshold: number;     // Switch to REDUCED mode above this connection count
  reducedThreshold: number;     // Switch to MINIMAL mode above this connection count
  aggregationInterval: number;  // How often to flush aggregated logs (ms)
  maxAggregatedEntries: number; // Max entries to hold before forced flush
}

/**
 * Aggregated log entry for batching similar events
 */
interface IAggregatedLogEntry {
  type: 'connection' | 'command' | 'response' | 'error';
  count: number;
  firstSeen: number;
  lastSeen: number;
  sample: {
    message: string;
    level: LogLevel;
    options?: ISmtpLogOptions;
  };
}

/**
 * Connection metadata for aggregation tracking
 */
interface IConnectionTracker {
  activeConnections: number;
  peakConnections: number;
  totalConnections: number;
  connectionsPerSecond: number;
  lastConnectionTime: number;
}

/**
 * Adaptive SMTP Logger that scales logging based on server load
 */
export class AdaptiveSmtpLogger {
  private static instance: AdaptiveSmtpLogger;
  private currentMode: LogMode = LogMode.VERBOSE;
  private config: IAdaptiveLogConfig;
  private aggregatedEntries: Map<string, IAggregatedLogEntry> = new Map();
  private aggregationTimer: NodeJS.Timeout | null = null;
  private connectionTracker: IConnectionTracker = {
    activeConnections: 0,
    peakConnections: 0,
    totalConnections: 0,
    connectionsPerSecond: 0,
    lastConnectionTime: Date.now()
  };

  private constructor(config?: Partial<IAdaptiveLogConfig>) {
    this.config = {
      verboseThreshold: 20,
      reducedThreshold: 40,
      aggregationInterval: 30000, // 30 seconds
      maxAggregatedEntries: 100,
      ...config
    };

    this.startAggregationTimer();
  }

  /**
   * Get singleton instance
   */
  public static getInstance(config?: Partial<IAdaptiveLogConfig>): AdaptiveSmtpLogger {
    if (!AdaptiveSmtpLogger.instance) {
      AdaptiveSmtpLogger.instance = new AdaptiveSmtpLogger(config);
    }
    return AdaptiveSmtpLogger.instance;
  }

  /**
   * Update active connection count and adjust log mode if needed
   */
  public updateConnectionCount(activeConnections: number): void {
    this.connectionTracker.activeConnections = activeConnections;
    this.connectionTracker.peakConnections = Math.max(
      this.connectionTracker.peakConnections,
      activeConnections
    );

    const newMode = this.determineLogMode(activeConnections);
    if (newMode !== this.currentMode) {
      this.switchLogMode(newMode);
    }
  }

  /**
   * Track new connection for rate calculation
   */
  public trackConnection(): void {
    this.connectionTracker.totalConnections++;
    const now = Date.now();
    const timeDiff = (now - this.connectionTracker.lastConnectionTime) / 1000;
    if (timeDiff > 0) {
      this.connectionTracker.connectionsPerSecond = 1 / timeDiff;
    }
    this.connectionTracker.lastConnectionTime = now;
  }

  /**
   * Get current logging mode
   */
  public getCurrentMode(): LogMode {
    return this.currentMode;
  }

  /**
   * Get connection statistics
   */
  public getConnectionStats(): IConnectionTracker {
    return { ...this.connectionTracker };
  }

  /**
   * Log a message with adaptive behavior
   */
  public log(level: LogLevel, message: string, options: ISmtpLogOptions = {}): void {
    // Always log structured data
    const errorInfo = options.error ? {
      errorMessage: options.error.message,
      errorStack: options.error.stack,
      errorName: options.error.name
    } : {};
    
    const logData = {
      component: 'smtp-server',
      logMode: this.currentMode,
      activeConnections: this.connectionTracker.activeConnections,
      ...options,
      ...errorInfo
    };
    
    if (logData.error) {
      delete logData.error;
    }
    
    logger.log(level, message, logData);

    // Adaptive console logging based on mode
    switch (this.currentMode) {
      case LogMode.VERBOSE:
        // Full console logging
        if (level === 'error' || level === 'warn') {
          console[level](`[SMTP] ${message}`, logData);
        }
        break;

      case LogMode.REDUCED:
        // Only errors and warnings to console
        if (level === 'error' || level === 'warn') {
          console[level](`[SMTP] ${message}`, logData);
        }
        break;

      case LogMode.MINIMAL:
        // Only critical errors to console
        if (level === 'error' && (message.includes('critical') || message.includes('security') || message.includes('crash'))) {
          console[level](`[SMTP] ${message}`, logData);
        }
        break;
    }
  }

  /**
   * Log command with adaptive behavior
   */
  public logCommand(command: string, socket: plugins.net.Socket | plugins.tls.TLSSocket, session?: ISmtpSession): void {
    const clientInfo = {
      remoteAddress: socket.remoteAddress,
      remotePort: socket.remotePort,
      secure: socket instanceof plugins.tls.TLSSocket,
      sessionId: session?.id,
      sessionState: session?.state
    };

    switch (this.currentMode) {
      case LogMode.VERBOSE:
        this.log('info', `Command received: ${command}`, {
          ...clientInfo,
          command: command.split(' ')[0]?.toUpperCase()
        });
        console.log(`← ${command}`);
        break;

      case LogMode.REDUCED:
        // Aggregate commands instead of logging each one
        this.aggregateEntry('command', 'info', `Command: ${command.split(' ')[0]?.toUpperCase()}`, clientInfo);
        // Only show error commands
        if (command.toUpperCase().startsWith('QUIT') || command.includes('error')) {
          console.log(`← ${command}`);
        }
        break;

      case LogMode.MINIMAL:
        // Only aggregate, no console output unless it's an error command
        this.aggregateEntry('command', 'info', `Command: ${command.split(' ')[0]?.toUpperCase()}`, clientInfo);
        break;
    }
  }

  /**
   * Log response with adaptive behavior
   */
  public logResponse(response: string, socket: plugins.net.Socket | plugins.tls.TLSSocket): void {
    const clientInfo = {
      remoteAddress: socket.remoteAddress,
      remotePort: socket.remotePort,
      secure: socket instanceof plugins.tls.TLSSocket
    };

    const responseCode = response.substring(0, 3);
    const isError = responseCode.startsWith('4') || responseCode.startsWith('5');

    switch (this.currentMode) {
      case LogMode.VERBOSE:
        if (responseCode.startsWith('2') || responseCode.startsWith('3')) {
          this.log('debug', `Response sent: ${response}`, clientInfo);
        } else if (responseCode.startsWith('4')) {
          this.log('warn', `Temporary error response: ${response}`, clientInfo);
        } else if (responseCode.startsWith('5')) {
          this.log('error', `Permanent error response: ${response}`, clientInfo);
        }
        console.log(`→ ${response}`);
        break;

      case LogMode.REDUCED:
        // Log errors normally, aggregate success responses
        if (isError) {
          if (responseCode.startsWith('4')) {
            this.log('warn', `Temporary error response: ${response}`, clientInfo);
          } else {
            this.log('error', `Permanent error response: ${response}`, clientInfo);
          }
          console.log(`→ ${response}`);
        } else {
          this.aggregateEntry('response', 'debug', `Response: ${responseCode}xx`, clientInfo);
        }
        break;

      case LogMode.MINIMAL:
        // Only log critical errors
        if (responseCode.startsWith('5')) {
          this.log('error', `Permanent error response: ${response}`, clientInfo);
          console.log(`→ ${response}`);
        } else {
          this.aggregateEntry('response', 'debug', `Response: ${responseCode}xx`, clientInfo);
        }
        break;
    }
  }

  /**
   * Log connection event with adaptive behavior
   */
  public logConnection(
    socket: plugins.net.Socket | plugins.tls.TLSSocket,
    eventType: 'connect' | 'close' | 'error',
    session?: ISmtpSession,
    error?: Error
  ): void {
    const clientInfo = {
      remoteAddress: socket.remoteAddress,
      remotePort: socket.remotePort,
      secure: socket instanceof plugins.tls.TLSSocket,
      sessionId: session?.id,
      sessionState: session?.state
    };

    if (eventType === 'connect') {
      this.trackConnection();
    }

    switch (this.currentMode) {
      case LogMode.VERBOSE:
        // Full connection logging
        switch (eventType) {
          case 'connect':
            this.log('info', `New ${clientInfo.secure ? 'secure ' : ''}connection from ${clientInfo.remoteAddress}:${clientInfo.remotePort}`, clientInfo);
            break;
          case 'close':
            this.log('info', `Connection closed from ${clientInfo.remoteAddress}:${clientInfo.remotePort}`, clientInfo);
            break;
          case 'error':
            this.log('error', `Connection error from ${clientInfo.remoteAddress}:${clientInfo.remotePort}`, {
              ...clientInfo,
              error
            });
            break;
        }
        break;

      case LogMode.REDUCED:
        // Aggregate normal connections, log errors
        if (eventType === 'error') {
          this.log('error', `Connection error from ${clientInfo.remoteAddress}:${clientInfo.remotePort}`, {
            ...clientInfo,
            error
          });
        } else {
          this.aggregateEntry('connection', 'info', `Connection ${eventType}`, clientInfo);
        }
        break;

      case LogMode.MINIMAL:
        // Only aggregate, except for critical errors
        if (eventType === 'error' && error && (error.message.includes('security') || error.message.includes('critical'))) {
          this.log('error', `Critical connection error from ${clientInfo.remoteAddress}:${clientInfo.remotePort}`, {
            ...clientInfo,
            error
          });
        } else {
          this.aggregateEntry('connection', 'info', `Connection ${eventType}`, clientInfo);
        }
        break;
    }
  }

  /**
   * Log security event (always logged regardless of mode)
   */
  public logSecurityEvent(
    level: SecurityLogLevel,
    type: SecurityEventType,
    message: string,
    details: Record<string, any>,
    ipAddress?: string,
    domain?: string,
    success?: boolean
  ): void {
    const logLevel: LogLevel = level === SecurityLogLevel.DEBUG ? 'debug' :
                               level === SecurityLogLevel.INFO ? 'info' :
                               level === SecurityLogLevel.WARN ? 'warn' : 'error';
    
    // Security events are always logged in full detail
    this.log(logLevel, message, {
      component: 'smtp-security',
      eventType: type,
      success,
      ipAddress,
      domain,
      ...details
    });
  }

  /**
   * Determine appropriate log mode based on connection count
   */
  private determineLogMode(activeConnections: number): LogMode {
    if (activeConnections >= this.config.reducedThreshold) {
      return LogMode.MINIMAL;
    } else if (activeConnections >= this.config.verboseThreshold) {
      return LogMode.REDUCED;
    } else {
      return LogMode.VERBOSE;
    }
  }

  /**
   * Switch to a new log mode
   */
  private switchLogMode(newMode: LogMode): void {
    const oldMode = this.currentMode;
    this.currentMode = newMode;

    // Log the mode switch
    console.log(`[SMTP] Adaptive logging switched from ${oldMode} to ${newMode} (${this.connectionTracker.activeConnections} active connections)`);
    
    this.log('info', `Adaptive logging mode changed to ${newMode}`, {
      oldMode,
      newMode,
      activeConnections: this.connectionTracker.activeConnections,
      peakConnections: this.connectionTracker.peakConnections,
      totalConnections: this.connectionTracker.totalConnections
    });

    // If switching to more verbose mode, flush aggregated entries
    if ((oldMode === LogMode.MINIMAL && newMode !== LogMode.MINIMAL) ||
        (oldMode === LogMode.REDUCED && newMode === LogMode.VERBOSE)) {
      this.flushAggregatedEntries();
    }
  }

  /**
   * Add entry to aggregation buffer
   */
  private aggregateEntry(
    type: 'connection' | 'command' | 'response' | 'error',
    level: LogLevel,
    message: string,
    options?: ISmtpLogOptions
  ): void {
    const key = `${type}:${message}`;
    const now = Date.now();

    if (this.aggregatedEntries.has(key)) {
      const entry = this.aggregatedEntries.get(key)!;
      entry.count++;
      entry.lastSeen = now;
    } else {
      this.aggregatedEntries.set(key, {
        type,
        count: 1,
        firstSeen: now,
        lastSeen: now,
        sample: { message, level, options }
      });
    }

    // Force flush if we have too many entries
    if (this.aggregatedEntries.size >= this.config.maxAggregatedEntries) {
      this.flushAggregatedEntries();
    }
  }

  /**
   * Start the aggregation timer
   */
  private startAggregationTimer(): void {
    if (this.aggregationTimer) {
      clearInterval(this.aggregationTimer);
    }

    this.aggregationTimer = setInterval(() => {
      this.flushAggregatedEntries();
    }, this.config.aggregationInterval);
    
    // Unref the timer so it doesn't keep the process alive
    if (this.aggregationTimer && typeof this.aggregationTimer.unref === 'function') {
      this.aggregationTimer.unref();
    }
  }

  /**
   * Flush aggregated entries to logs
   */
  private flushAggregatedEntries(): void {
    if (this.aggregatedEntries.size === 0) {
      return;
    }

    const summary: Record<string, number> = {};
    let totalAggregated = 0;

    for (const [key, entry] of this.aggregatedEntries.entries()) {
      summary[entry.type] = (summary[entry.type] || 0) + entry.count;
      totalAggregated += entry.count;

      // Log a sample of high-frequency entries
      if (entry.count >= 10) {
        this.log(entry.sample.level, `${entry.sample.message} (aggregated: ${entry.count} occurrences)`, {
          ...entry.sample.options,
          aggregated: true,
          occurrences: entry.count,
          timeSpan: entry.lastSeen - entry.firstSeen
        });
      }
    }

    // Log aggregation summary
    console.log(`[SMTP] Aggregated ${totalAggregated} log entries: ${JSON.stringify(summary)}`);
    
    this.log('info', 'Aggregated log summary', {
      totalEntries: totalAggregated,
      breakdown: summary,
      logMode: this.currentMode,
      activeConnections: this.connectionTracker.activeConnections
    });

    // Clear aggregated entries
    this.aggregatedEntries.clear();
  }

  /**
   * Cleanup resources
   */
  public destroy(): void {
    if (this.aggregationTimer) {
      clearInterval(this.aggregationTimer);
      this.aggregationTimer = null;
    }
    this.flushAggregatedEntries();
  }
}

/**
 * Default instance for easy access
 */
export const adaptiveLogger = AdaptiveSmtpLogger.getInstance();