import * as plugins from '../plugins.js';
import * as paths from '../paths.js';
import { logger } from '../logger.js';
import { Email } from '../mail/core/classes.email.js';
import type { IAttachment } from '../mail/core/classes.email.js';
import { SecurityLogger, SecurityLogLevel, SecurityEventType } from './classes.securitylogger.js';
import { RustSecurityBridge } from './classes.rustsecuritybridge.js';
import { LRUCache } from 'lru-cache';

/**
 * Scan result information
 */
export interface IScanResult {
  isClean: boolean;           // Whether the content is clean (no threats detected)
  threatType?: string;        // Type of threat if detected
  threatDetails?: string;     // Details about the detected threat
  threatScore: number;        // 0 (clean) to 100 (definitely malicious)
  scannedElements: string[];  // What was scanned (subject, body, attachments, etc.)
  timestamp: number;          // When this scan was performed
}

/**
 * Options for content scanner configuration
 */
export interface IContentScannerOptions {
  maxCacheSize?: number;              // Maximum number of entries to cache
  cacheTTL?: number;                  // TTL for cache entries in ms
  scanSubject?: boolean;              // Whether to scan email subjects
  scanBody?: boolean;                 // Whether to scan email bodies
  scanAttachments?: boolean;          // Whether to scan attachments
  maxAttachmentSizeToScan?: number;   // Max size of attachments to scan in bytes
  scanAttachmentNames?: boolean;      // Whether to scan attachment filenames
  blockExecutables?: boolean;         // Whether to block executable attachments
  blockMacros?: boolean;              // Whether to block documents with macros
  customRules?: Array<{              // Custom scanning rules
    pattern: string | RegExp;         // Pattern to match
    type: string;                     // Type of threat
    score: number;                    // Threat score
    description: string;              // Description of the threat
  }>;
  minThreatScore?: number;            // Minimum score to consider content as a threat
  highThreatScore?: number;           // Score above which content is considered high threat
}

/**
 * Threat categories
 */
export enum ThreatCategory {
  SPAM = 'spam',
  PHISHING = 'phishing',
  MALWARE = 'malware',
  EXECUTABLE = 'executable',
  SUSPICIOUS_LINK = 'suspicious_link',
  MALICIOUS_MACRO = 'malicious_macro',
  XSS = 'xss',
  SENSITIVE_DATA = 'sensitive_data',
  BLACKLISTED_CONTENT = 'blacklisted_content',
  CUSTOM_RULE = 'custom_rule'
}

/**
 * Content Scanner for detecting malicious email content
 */
export class ContentScanner {
  private static instance: ContentScanner;
  private scanCache: LRUCache<string, IScanResult>;
  private options: Required<IContentScannerOptions>;
  
  /**
   * Default options for the content scanner
   */
  private static readonly DEFAULT_OPTIONS: Required<IContentScannerOptions> = {
    maxCacheSize: 10000,
    cacheTTL: 24 * 60 * 60 * 1000, // 24 hours
    scanSubject: true,
    scanBody: true,
    scanAttachments: true,
    maxAttachmentSizeToScan: 10 * 1024 * 1024, // 10MB
    scanAttachmentNames: true,
    blockExecutables: true,
    blockMacros: true,
    customRules: [],
    minThreatScore: 30, // Minimum score to consider content as a threat
    highThreatScore: 70  // Score above which content is considered high threat
  };
  
  /**
   * Constructor for the ContentScanner
   * @param options Configuration options
   */
  constructor(options: IContentScannerOptions = {}) {
    // Merge with default options
    this.options = {
      ...ContentScanner.DEFAULT_OPTIONS,
      ...options
    };
    
    // Initialize cache
    this.scanCache = new LRUCache<string, IScanResult>({
      max: this.options.maxCacheSize,
      ttl: this.options.cacheTTL,
    });
    
    logger.log('info', 'ContentScanner initialized');
  }
  
  /**
   * Get the singleton instance of the scanner
   * @param options Configuration options
   * @returns Singleton scanner instance
   */
  public static getInstance(options: IContentScannerOptions = {}): ContentScanner {
    if (!ContentScanner.instance) {
      ContentScanner.instance = new ContentScanner(options);
    }
    return ContentScanner.instance;
  }
  
  /**
   * Scan an email for malicious content.
   * Delegates text/subject/html/filename pattern scanning to Rust.
   * Binary attachment scanning (PE headers, VBA macros) stays in TS.
   * @param email The email to scan
   * @returns Scan result
   */
  public async scanEmail(email: Email): Promise<IScanResult> {
    try {
      // Generate a cache key from the email
      const cacheKey = this.generateCacheKey(email);

      // Check cache first
      const cachedResult = this.scanCache.get(cacheKey);
      if (cachedResult) {
        logger.log('info', `Using cached scan result for email ${email.getMessageId()}`);
        return cachedResult;
      }

      // Delegate text/subject/html/filename scanning to Rust
      const bridge = RustSecurityBridge.getInstance();
      const rustResult = await bridge.scanContent({
        subject: this.options.scanSubject ? email.subject : undefined,
        textBody: this.options.scanBody ? email.text : undefined,
        htmlBody: this.options.scanBody ? email.html : undefined,
        attachmentNames: this.options.scanAttachmentNames
          ? email.attachments?.map(a => a.filename) ?? []
          : [],
      });

      const result: IScanResult = {
        isClean: true,
        threatScore: rustResult.threatScore,
        threatType: rustResult.threatType ?? undefined,
        threatDetails: rustResult.threatDetails ?? undefined,
        scannedElements: rustResult.scannedElements,
        timestamp: Date.now(),
      };

      // Attachment binary scanning stays in TS (PE headers, macro detection)
      if (this.options.scanAttachments && email.attachments?.length > 0) {
        for (const attachment of email.attachments) {
          this.scanAttachmentBinary(attachment, result);
        }
      }

      // Apply custom rules (TS-only, runtime-configured)
      this.applyCustomRules(email, result);

      // Determine if the email is clean based on threat score
      result.isClean = result.threatScore < this.options.minThreatScore;

      // Save to cache
      this.scanCache.set(cacheKey, result);

      // Log high threat findings
      if (result.threatScore >= this.options.highThreatScore) {
        this.logHighThreatFound(email, result);
      } else if (!result.isClean) {
        this.logThreatFound(email, result);
      }

      return result;
    } catch (error) {
      logger.log('error', `Error scanning email: ${error.message}`, {
        messageId: email.getMessageId(),
        error: error.stack
      });

      // Return a safe default with error indication
      return {
        isClean: true,
        threatScore: 0,
        scannedElements: ['error'],
        timestamp: Date.now(),
        threatType: 'scan_error',
        threatDetails: `Scan error: ${error.message}`
      };
    }
  }

  /**
   * Generate a cache key from an email
   * @param email The email to generate a key for
   * @returns Cache key
   */
  private generateCacheKey(email: Email): string {
    // Use message ID if available
    if (email.getMessageId()) {
      return `email:${email.getMessageId()}`;
    }

    // Fallback to a hash of key content
    const contentToHash = [
      email.from,
      email.subject || '',
      email.text?.substring(0, 1000) || '',
      email.html?.substring(0, 1000) || '',
      email.attachments?.length || 0
    ].join(':');

    return `email:${plugins.crypto.createHash('sha256').update(contentToHash).digest('hex')}`;
  }

  /**
   * Scan attachment binary content for PE headers and VBA macros.
   * This stays in TS because it accesses raw Buffer data (too large for IPC).
   * @param attachment The attachment to scan
   * @param result The scan result to update
   */
  private scanAttachmentBinary(attachment: IAttachment, result: IScanResult): void {
    if (!attachment.content) {
      return;
    }

    // Skip large attachments
    if (attachment.content.length > this.options.maxAttachmentSizeToScan) {
      return;
    }

    const filename = attachment.filename.toLowerCase();

    // Check for PE headers (Windows executables disguised with non-.exe extensions)
    if (attachment.content.length > 64 &&
        attachment.content[0] === 0x4D &&
        attachment.content[1] === 0x5A) { // 'MZ' header
      result.threatScore += 80;
      result.threatType = ThreatCategory.EXECUTABLE;
      result.threatDetails = `Attachment contains executable code: ${filename}`;
      return;
    }

    // Check for VBA macro indicators in Office documents
    if (this.options.blockMacros && this.likelyContainsMacros(attachment)) {
      result.threatScore += 60;
      result.threatType = ThreatCategory.MALICIOUS_MACRO;
      result.threatDetails = `Attachment appears to contain macros: ${filename}`;
    }
  }

  /**
   * Apply custom rules (runtime-configured patterns) to the email.
   * These stay in TS because they are configured at runtime.
   * @param email The email to check
   * @param result The scan result to update
   */
  private applyCustomRules(email: Email, result: IScanResult): void {
    if (!this.options.customRules.length) {
      return;
    }

    const textsToCheck: string[] = [];
    if (email.subject) textsToCheck.push(email.subject);
    if (email.text) textsToCheck.push(email.text);
    if (email.html) textsToCheck.push(email.html);

    for (const rule of this.options.customRules) {
      const pattern = rule.pattern instanceof RegExp ? rule.pattern : new RegExp(rule.pattern, 'i');
      for (const text of textsToCheck) {
        if (pattern.test(text)) {
          result.threatScore += rule.score;
          result.threatType = rule.type;
          result.threatDetails = rule.description;
          return;
        }
      }
    }
  }

  /**
   * Extract text from a binary buffer for scanning
   * @param buffer Binary content
   * @returns Extracted text (may be partial)
   */
  private extractTextFromBuffer(buffer: Buffer): string {
    try {
      // Limit the amount we convert to avoid memory issues
      const sampleSize = Math.min(buffer.length, 100 * 1024); // 100KB max sample
      const sample = buffer.slice(0, sampleSize);

      // Try to convert to string, filtering out non-printable chars
      return sample.toString('utf8')
        .replace(/[\x00-\x09\x0B-\x1F\x7F-\x9F]/g, '') // Remove control chars
        .replace(/\uFFFD/g, ''); // Remove replacement char
    } catch (error) {
      logger.log('warn', `Error extracting text from buffer: ${error.message}`);
      return '';
    }
  }

  /**
   * Check if an Office document likely contains macros
   * @param attachment The attachment to check
   * @returns Whether the file likely contains macros
   */
  private likelyContainsMacros(attachment: IAttachment): boolean {
    const content = this.extractTextFromBuffer(attachment.content);
    const macroIndicators = [
      /vbaProject\.bin/i,
      /Microsoft VBA/i,
      /\bVBA\b/,
      /Auto_Open/i,
      /AutoExec/i,
      /DocumentOpen/i,
      /AutoOpen/i,
      /\bExecute\(/i,
      /\bShell\(/i,
      /\bCreateObject\(/i
    ];

    for (const indicator of macroIndicators) {
      if (indicator.test(content)) {
        return true;
      }
    }

    return false;
  }

  /**
   * Log a high threat finding to the security logger
   * @param email The email containing the threat
   * @param result The scan result
   */
  private logHighThreatFound(email: Email, result: IScanResult): void {
    SecurityLogger.getInstance().logEvent({
      level: SecurityLogLevel.ERROR,
      type: SecurityEventType.MALWARE,
      message: `High threat content detected in email from ${email.from} to ${email.to.join(', ')}`,
      details: {
        messageId: email.getMessageId(),
        threatType: result.threatType,
        threatDetails: result.threatDetails,
        threatScore: result.threatScore,
        scannedElements: result.scannedElements,
        subject: email.subject
      },
      success: false,
      domain: email.getFromDomain()
    });
  }
  
  /**
   * Log a threat finding to the security logger
   * @param email The email containing the threat
   * @param result The scan result
   */
  private logThreatFound(email: Email, result: IScanResult): void {
    SecurityLogger.getInstance().logEvent({
      level: SecurityLogLevel.WARN,
      type: SecurityEventType.SPAM,
      message: `Suspicious content detected in email from ${email.from} to ${email.to.join(', ')}`,
      details: {
        messageId: email.getMessageId(),
        threatType: result.threatType,
        threatDetails: result.threatDetails,
        threatScore: result.threatScore,
        scannedElements: result.scannedElements,
        subject: email.subject
      },
      success: false,
      domain: email.getFromDomain()
    });
  }
  
  /**
   * Get threat level description based on score
   * @param score Threat score
   * @returns Threat level description
   */
  public static getThreatLevel(score: number): 'none' | 'low' | 'medium' | 'high' {
    if (score < 20) {
      return 'none';
    } else if (score < 40) {
      return 'low';
    } else if (score < 70) {
      return 'medium';
    } else {
      return 'high';
    }
  }
}