import * as plugins from '../../plugins.js';
import * as paths from '../../paths.js';
import { logger } from '../../logger.js';
import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../../security/index.js';
import { LRUCache } from 'lru-cache';

/**
 * Bounce types for categorizing the reasons for bounces
 */
export enum BounceType {
  // Hard bounces (permanent failures)
  INVALID_RECIPIENT = 'invalid_recipient',
  DOMAIN_NOT_FOUND = 'domain_not_found',
  MAILBOX_FULL = 'mailbox_full',
  MAILBOX_INACTIVE = 'mailbox_inactive',
  BLOCKED = 'blocked',
  SPAM_RELATED = 'spam_related',
  POLICY_RELATED = 'policy_related',
  
  // Soft bounces (temporary failures)
  SERVER_UNAVAILABLE = 'server_unavailable',
  TEMPORARY_FAILURE = 'temporary_failure',
  QUOTA_EXCEEDED = 'quota_exceeded',
  NETWORK_ERROR = 'network_error',
  TIMEOUT = 'timeout',
  
  // Special cases
  AUTO_RESPONSE = 'auto_response',
  CHALLENGE_RESPONSE = 'challenge_response',
  UNKNOWN = 'unknown'
}

/**
 * Hard vs soft bounce classification
 */
export enum BounceCategory {
  HARD = 'hard',
  SOFT = 'soft',
  AUTO_RESPONSE = 'auto_response',
  UNKNOWN = 'unknown'
}

/**
 * Bounce data structure
 */
export interface BounceRecord {
  id: string;
  originalEmailId?: string;
  recipient: string;
  sender: string;
  domain: string;
  subject?: string;
  bounceType: BounceType;
  bounceCategory: BounceCategory;
  timestamp: number;
  smtpResponse?: string;
  diagnosticCode?: string;
  statusCode?: string;
  headers?: Record<string, string>;
  processed: boolean;
  retryCount?: number;
  nextRetryTime?: number;
}

/**
 * Email bounce patterns to identify bounce types in SMTP responses and bounce messages
 */
const BOUNCE_PATTERNS = {
  // Hard bounce patterns
  [BounceType.INVALID_RECIPIENT]: [
    /no such user/i,
    /user unknown/i,
    /does not exist/i,
    /invalid recipient/i,
    /unknown recipient/i,
    /no mailbox/i,
    /user not found/i,
    /recipient address rejected/i,
    /550 5\.1\.1/i
  ],
  [BounceType.DOMAIN_NOT_FOUND]: [
    /domain not found/i,
    /unknown domain/i,
    /no such domain/i,
    /host not found/i,
    /domain invalid/i,
    /550 5\.1\.2/i
  ],
  [BounceType.MAILBOX_FULL]: [
    /mailbox full/i,
    /over quota/i,
    /quota exceeded/i,
    /552 5\.2\.2/i
  ],
  [BounceType.MAILBOX_INACTIVE]: [
    /mailbox disabled/i,
    /mailbox inactive/i,
    /account disabled/i,
    /mailbox not active/i,
    /account suspended/i
  ],
  [BounceType.BLOCKED]: [
    /blocked/i,
    /rejected/i,
    /denied/i,
    /blacklisted/i,
    /prohibited/i,
    /refused/i,
    /550 5\.7\./i
  ],
  [BounceType.SPAM_RELATED]: [
    /spam/i,
    /bulk mail/i,
    /content rejected/i,
    /message rejected/i,
    /550 5\.7\.1/i
  ],
  
  // Soft bounce patterns
  [BounceType.SERVER_UNAVAILABLE]: [
    /server unavailable/i,
    /service unavailable/i,
    /try again later/i,
    /try later/i,
    /451 4\.3\./i,
    /421 4\.3\./i
  ],
  [BounceType.TEMPORARY_FAILURE]: [
    /temporary failure/i,
    /temporary error/i,
    /temporary problem/i,
    /try again/i,
    /451 4\./i
  ],
  [BounceType.QUOTA_EXCEEDED]: [
    /quota temporarily exceeded/i,
    /mailbox temporarily full/i,
    /452 4\.2\.2/i
  ],
  [BounceType.NETWORK_ERROR]: [
    /network error/i,
    /connection error/i,
    /connection timed out/i,
    /routing error/i,
    /421 4\.4\./i
  ],
  [BounceType.TIMEOUT]: [
    /timed out/i,
    /timeout/i,
    /450 4\.4\.2/i
  ],
  
  // Auto-responses
  [BounceType.AUTO_RESPONSE]: [
    /auto[- ]reply/i,
    /auto[- ]response/i,
    /vacation/i,
    /out of office/i,
    /away from office/i,
    /on vacation/i,
    /automatic reply/i
  ],
  [BounceType.CHALLENGE_RESPONSE]: [
    /challenge[- ]response/i,
    /verify your email/i,
    /confirm your email/i,
    /email verification/i
  ]
};

/**
 * Retry strategy configuration for soft bounces
 */
interface RetryStrategy {
  maxRetries: number;
  initialDelay: number; // milliseconds
  maxDelay: number; // milliseconds
  backoffFactor: number;
}

/**
 * Manager for handling email bounces
 */
export class BounceManager {
  // Retry strategy with exponential backoff
  private retryStrategy: RetryStrategy = {
    maxRetries: 5,
    initialDelay: 15 * 60 * 1000, // 15 minutes
    maxDelay: 24 * 60 * 60 * 1000, // 24 hours
    backoffFactor: 2
  };
  
  // Store of bounced emails
  private bounceStore: BounceRecord[] = [];
  
  // Cache of recently bounced email addresses to avoid sending to known bad addresses
  private bounceCache: LRUCache<string, {
    lastBounce: number;
    count: number;
    type: BounceType;
    category: BounceCategory;
  }>;
  
  // Suppression list for addresses that should not receive emails
  private suppressionList: Map<string, {
    reason: string;
    timestamp: number;
    expiresAt?: number; // undefined means permanent
  }> = new Map();
  
  constructor(options?: {
    retryStrategy?: Partial<RetryStrategy>;
    maxCacheSize?: number;
    cacheTTL?: number;
  }) {
    // Set retry strategy with defaults
    if (options?.retryStrategy) {
      this.retryStrategy = {
        ...this.retryStrategy,
        ...options.retryStrategy
      };
    }
    
    // Initialize bounce cache with LRU (least recently used) caching
    this.bounceCache = new LRUCache<string, any>({
      max: options?.maxCacheSize || 10000,
      ttl: options?.cacheTTL || 30 * 24 * 60 * 60 * 1000, // 30 days default
    });
    
    // Load suppression list from storage
    this.loadSuppressionList();
  }
  
  /**
   * Process a bounce notification
   * @param bounceData Bounce data to process
   * @returns Processed bounce record
   */
  public async processBounce(bounceData: Partial<BounceRecord>): Promise<BounceRecord> {
    try {
      // Add required fields if missing
      const bounce: BounceRecord = {
        id: bounceData.id || plugins.uuid.v4(),
        recipient: bounceData.recipient,
        sender: bounceData.sender,
        domain: bounceData.domain || bounceData.recipient.split('@')[1],
        subject: bounceData.subject,
        bounceType: bounceData.bounceType || BounceType.UNKNOWN,
        bounceCategory: bounceData.bounceCategory || BounceCategory.UNKNOWN,
        timestamp: bounceData.timestamp || Date.now(),
        smtpResponse: bounceData.smtpResponse,
        diagnosticCode: bounceData.diagnosticCode,
        statusCode: bounceData.statusCode,
        headers: bounceData.headers,
        processed: false,
        originalEmailId: bounceData.originalEmailId,
        retryCount: bounceData.retryCount || 0,
        nextRetryTime: bounceData.nextRetryTime
      };
      
      // Determine bounce type and category if not provided
      if (!bounceData.bounceType || bounceData.bounceType === BounceType.UNKNOWN) {
        const bounceInfo = this.detectBounceType(
          bounce.smtpResponse || '',
          bounce.diagnosticCode || '',
          bounce.statusCode || ''
        );
        
        bounce.bounceType = bounceInfo.type;
        bounce.bounceCategory = bounceInfo.category;
      }
      
      // Process the bounce based on category
      switch (bounce.bounceCategory) {
        case BounceCategory.HARD:
          // Handle hard bounce - add to suppression list
          await this.handleHardBounce(bounce);
          break;
          
        case BounceCategory.SOFT:
          // Handle soft bounce - schedule retry if eligible
          await this.handleSoftBounce(bounce);
          break;
          
        case BounceCategory.AUTO_RESPONSE:
          // Handle auto-response - typically no action needed
          logger.log('info', `Auto-response detected for ${bounce.recipient}`);
          break;
          
        default:
          // Unknown bounce type - log for investigation
          logger.log('warn', `Unknown bounce type for ${bounce.recipient}`, {
            bounceType: bounce.bounceType,
            smtpResponse: bounce.smtpResponse
          });
          break;
      }
      
      // Store the bounce record
      bounce.processed = true;
      this.bounceStore.push(bounce);
      
      // Update the bounce cache
      this.updateBounceCache(bounce);
      
      // Log the bounce
      logger.log(
        bounce.bounceCategory === BounceCategory.HARD ? 'warn' : 'info',
        `Email bounce processed: ${bounce.bounceCategory} bounce for ${bounce.recipient}`,
        {
          bounceType: bounce.bounceType,
          domain: bounce.domain,
          category: bounce.bounceCategory
        }
      );
      
      // Enhanced security logging
      SecurityLogger.getInstance().logEvent({
        level: bounce.bounceCategory === BounceCategory.HARD 
          ? SecurityLogLevel.WARN 
          : SecurityLogLevel.INFO,
        type: SecurityEventType.EMAIL_VALIDATION,
        message: `Email bounce detected: ${bounce.bounceCategory} bounce for recipient`,
        domain: bounce.domain,
        details: {
          recipient: bounce.recipient,
          bounceType: bounce.bounceType,
          smtpResponse: bounce.smtpResponse,
          diagnosticCode: bounce.diagnosticCode,
          statusCode: bounce.statusCode
        },
        success: false
      });
      
      return bounce;
    } catch (error) {
      logger.log('error', `Error processing bounce: ${error.message}`, {
        error: error.message,
        bounceData
      });
      throw error;
    }
  }
  
  /**
   * Process an SMTP failure as a bounce
   * @param recipient Recipient email
   * @param smtpResponse SMTP error response
   * @param options Additional options
   * @returns Processed bounce record
   */
  public async processSmtpFailure(
    recipient: string,
    smtpResponse: string,
    options: {
      sender?: string;
      originalEmailId?: string;
      statusCode?: string;
      headers?: Record<string, string>;
    } = {}
  ): Promise<BounceRecord> {
    // Create bounce data from SMTP failure
    const bounceData: Partial<BounceRecord> = {
      recipient,
      sender: options.sender || '',
      domain: recipient.split('@')[1],
      smtpResponse,
      statusCode: options.statusCode,
      headers: options.headers,
      originalEmailId: options.originalEmailId,
      timestamp: Date.now()
    };
    
    // Process as a regular bounce
    return this.processBounce(bounceData);
  }
  
  /**
   * Process a bounce notification email
   * @param bounceEmail The email containing bounce information
   * @returns Processed bounce record or null if not a bounce
   */
  public async processBounceEmail(bounceEmail: plugins.smartmail.Smartmail<any>): Promise<BounceRecord | null> {
    try {
      // Check if this is a bounce notification
      const subject = bounceEmail.getSubject();
      const body = bounceEmail.getBody();
      
      // Check for common bounce notification subject patterns
      const isBounceSubject = /mail delivery|delivery (failed|status|notification)|failure notice|returned mail|undeliverable|delivery problem/i.test(subject);
      
      if (!isBounceSubject) {
        // Not a bounce notification based on subject
        return null;
      }
      
      // Extract original recipient from the body or headers
      let recipient = '';
      let originalMessageId = '';
      
      // Extract recipient from common bounce formats
      const recipientMatch = body.match(/(?:failed recipient|to[:=]\s*|recipient:|delivery failed:)\s*<?([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})>?/i);
      if (recipientMatch && recipientMatch[1]) {
        recipient = recipientMatch[1];
      }
      
      // Extract diagnostic code
      let diagnosticCode = '';
      const diagnosticMatch = body.match(/diagnostic(?:-|\\s+)code:\s*(.+)(?:\n|$)/i);
      if (diagnosticMatch && diagnosticMatch[1]) {
        diagnosticCode = diagnosticMatch[1].trim();
      }
      
      // Extract SMTP status code
      let statusCode = '';
      const statusMatch = body.match(/status(?:-|\\s+)code:\s*([0-9.]+)/i);
      if (statusMatch && statusMatch[1]) {
        statusCode = statusMatch[1].trim();
      }
      
      // If recipient not found in standard patterns, try DSN (Delivery Status Notification) format
      if (!recipient) {
        // Look for DSN format with Original-Recipient or Final-Recipient fields
        const originalRecipientMatch = body.match(/original-recipient:.*?([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/i);
        const finalRecipientMatch = body.match(/final-recipient:.*?([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/i);
        
        if (originalRecipientMatch && originalRecipientMatch[1]) {
          recipient = originalRecipientMatch[1];
        } else if (finalRecipientMatch && finalRecipientMatch[1]) {
          recipient = finalRecipientMatch[1];
        }
      }
      
      // If still no recipient, can't process as bounce
      if (!recipient) {
        logger.log('warn', 'Could not extract recipient from bounce notification', {
          subject,
          sender: bounceEmail.options.from
        });
        return null;
      }
      
      // Extract original message ID if available
      const messageIdMatch = body.match(/original[ -]message[ -]id:[ \t]*<?([^>]+)>?/i);
      if (messageIdMatch && messageIdMatch[1]) {
        originalMessageId = messageIdMatch[1].trim();
      }
      
      // Create bounce data
      const bounceData: Partial<BounceRecord> = {
        recipient,
        sender: bounceEmail.options.from,
        domain: recipient.split('@')[1],
        subject: bounceEmail.getSubject(),
        diagnosticCode,
        statusCode,
        timestamp: Date.now(),
        headers: {}
      };
      
      // Process as a regular bounce
      return this.processBounce(bounceData);
    } catch (error) {
      logger.log('error', `Error processing bounce email: ${error.message}`);
      return null;
    }
  }
  
  /**
   * Handle a hard bounce by adding to suppression list
   * @param bounce The bounce record
   */
  private async handleHardBounce(bounce: BounceRecord): Promise<void> {
    // Add to suppression list permanently (no expiry)
    this.addToSuppressionList(bounce.recipient, `Hard bounce: ${bounce.bounceType}`, undefined);
    
    // Increment bounce count in cache
    this.updateBounceCache(bounce);
    
    // Save to permanent storage
    this.saveBounceRecord(bounce);
    
    // Log hard bounce for monitoring
    logger.log('warn', `Hard bounce for ${bounce.recipient}: ${bounce.bounceType}`, {
      domain: bounce.domain,
      smtpResponse: bounce.smtpResponse,
      diagnosticCode: bounce.diagnosticCode
    });
  }
  
  /**
   * Handle a soft bounce by scheduling a retry if eligible
   * @param bounce The bounce record
   */
  private async handleSoftBounce(bounce: BounceRecord): Promise<void> {
    // Check if we've exceeded max retries
    if (bounce.retryCount >= this.retryStrategy.maxRetries) {
      logger.log('warn', `Max retries exceeded for ${bounce.recipient}, treating as hard bounce`);
      
      // Convert to hard bounce after max retries
      bounce.bounceCategory = BounceCategory.HARD;
      await this.handleHardBounce(bounce);
      return;
    }
    
    // Calculate next retry time with exponential backoff
    const delay = Math.min(
      this.retryStrategy.initialDelay * Math.pow(this.retryStrategy.backoffFactor, bounce.retryCount),
      this.retryStrategy.maxDelay
    );
    
    bounce.retryCount++;
    bounce.nextRetryTime = Date.now() + delay;
    
    // Add to suppression list temporarily (with expiry)
    this.addToSuppressionList(
      bounce.recipient,
      `Soft bounce: ${bounce.bounceType}`,
      bounce.nextRetryTime
    );
    
    // Log the retry schedule
    logger.log('info', `Scheduled retry ${bounce.retryCount} for ${bounce.recipient} at ${new Date(bounce.nextRetryTime).toISOString()}`, {
      bounceType: bounce.bounceType,
      retryCount: bounce.retryCount,
      nextRetry: bounce.nextRetryTime
    });
  }
  
  /**
   * Add an email address to the suppression list
   * @param email Email address to suppress
   * @param reason Reason for suppression
   * @param expiresAt Expiration timestamp (undefined for permanent)
   */
  public addToSuppressionList(
    email: string,
    reason: string,
    expiresAt?: number
  ): void {
    this.suppressionList.set(email.toLowerCase(), {
      reason,
      timestamp: Date.now(),
      expiresAt
    });
    
    this.saveSuppressionList();
    
    logger.log('info', `Added ${email} to suppression list`, {
      reason,
      expiresAt: expiresAt ? new Date(expiresAt).toISOString() : 'permanent'
    });
  }
  
  /**
   * Remove an email address from the suppression list
   * @param email Email address to remove
   */
  public removeFromSuppressionList(email: string): void {
    const wasRemoved = this.suppressionList.delete(email.toLowerCase());
    
    if (wasRemoved) {
      this.saveSuppressionList();
      logger.log('info', `Removed ${email} from suppression list`);
    }
  }
  
  /**
   * Check if an email is on the suppression list
   * @param email Email address to check
   * @returns Whether the email is suppressed
   */
  public isEmailSuppressed(email: string): boolean {
    const lowercaseEmail = email.toLowerCase();
    const suppression = this.suppressionList.get(lowercaseEmail);
    
    if (!suppression) {
      return false;
    }
    
    // Check if suppression has expired
    if (suppression.expiresAt && Date.now() > suppression.expiresAt) {
      this.suppressionList.delete(lowercaseEmail);
      this.saveSuppressionList();
      return false;
    }
    
    return true;
  }
  
  /**
   * Get suppression information for an email
   * @param email Email address to check
   * @returns Suppression information or null if not suppressed
   */
  public getSuppressionInfo(email: string): {
    reason: string;
    timestamp: number;
    expiresAt?: number;
  } | null {
    const lowercaseEmail = email.toLowerCase();
    const suppression = this.suppressionList.get(lowercaseEmail);
    
    if (!suppression) {
      return null;
    }
    
    // Check if suppression has expired
    if (suppression.expiresAt && Date.now() > suppression.expiresAt) {
      this.suppressionList.delete(lowercaseEmail);
      this.saveSuppressionList();
      return null;
    }
    
    return suppression;
  }
  
  /**
   * Save suppression list to disk
   */
  private saveSuppressionList(): void {
    try {
      const suppressionData = JSON.stringify(Array.from(this.suppressionList.entries()));
      plugins.smartfile.memory.toFsSync(
        suppressionData,
        plugins.path.join(paths.dataDir, 'emails', 'suppression_list.json')
      );
    } catch (error) {
      logger.log('error', `Failed to save suppression list: ${error.message}`);
    }
  }
  
  /**
   * Load suppression list from disk
   */
  private loadSuppressionList(): void {
    try {
      const suppressionPath = plugins.path.join(paths.dataDir, 'emails', 'suppression_list.json');
      
      if (plugins.fs.existsSync(suppressionPath)) {
        const data = plugins.fs.readFileSync(suppressionPath, 'utf8');
        const entries = JSON.parse(data);
        
        this.suppressionList = new Map(entries);
        
        // Clean expired entries
        const now = Date.now();
        let expiredCount = 0;
        
        for (const [email, info] of this.suppressionList.entries()) {
          if (info.expiresAt && now > info.expiresAt) {
            this.suppressionList.delete(email);
            expiredCount++;
          }
        }
        
        if (expiredCount > 0) {
          logger.log('info', `Cleaned ${expiredCount} expired entries from suppression list`);
          this.saveSuppressionList();
        }
        
        logger.log('info', `Loaded ${this.suppressionList.size} entries from suppression list`);
      }
    } catch (error) {
      logger.log('error', `Failed to load suppression list: ${error.message}`);
    }
  }
  
  /**
   * Save bounce record to disk
   * @param bounce Bounce record to save
   */
  private saveBounceRecord(bounce: BounceRecord): void {
    try {
      const bounceData = JSON.stringify(bounce);
      const bouncePath = plugins.path.join(
        paths.dataDir,
        'emails',
        'bounces',
        `${bounce.id}.json`
      );
      
      // Ensure directory exists
      const bounceDir = plugins.path.join(paths.dataDir, 'emails', 'bounces');
      plugins.smartfile.fs.ensureDirSync(bounceDir);
      
      plugins.smartfile.memory.toFsSync(bounceData, bouncePath);
    } catch (error) {
      logger.log('error', `Failed to save bounce record: ${error.message}`);
    }
  }
  
  /**
   * Update bounce cache with new bounce information
   * @param bounce Bounce record to update cache with
   */
  private updateBounceCache(bounce: BounceRecord): void {
    const email = bounce.recipient.toLowerCase();
    const existing = this.bounceCache.get(email);
    
    if (existing) {
      // Update existing cache entry
      existing.lastBounce = bounce.timestamp;
      existing.count++;
      existing.type = bounce.bounceType;
      existing.category = bounce.bounceCategory;
    } else {
      // Create new cache entry
      this.bounceCache.set(email, {
        lastBounce: bounce.timestamp,
        count: 1,
        type: bounce.bounceType,
        category: bounce.bounceCategory
      });
    }
  }
  
  /**
   * Check bounce history for an email address
   * @param email Email address to check
   * @returns Bounce information or null if no bounces
   */
  public getBounceInfo(email: string): {
    lastBounce: number;
    count: number;
    type: BounceType;
    category: BounceCategory;
  } | null {
    return this.bounceCache.get(email.toLowerCase()) || null;
  }
  
  /**
   * Analyze SMTP response and diagnostic codes to determine bounce type
   * @param smtpResponse SMTP response string
   * @param diagnosticCode Diagnostic code from bounce
   * @param statusCode Status code from bounce
   * @returns Detected bounce type and category
   */
  private detectBounceType(
    smtpResponse: string,
    diagnosticCode: string,
    statusCode: string
  ): {
    type: BounceType;
    category: BounceCategory;
  } {
    // Combine all text for comprehensive pattern matching
    const fullText = `${smtpResponse} ${diagnosticCode} ${statusCode}`.toLowerCase();
    
    // Check for auto-responses first
    if (this.matchesPattern(fullText, BounceType.AUTO_RESPONSE) || 
        this.matchesPattern(fullText, BounceType.CHALLENGE_RESPONSE)) {
      return {
        type: BounceType.AUTO_RESPONSE,
        category: BounceCategory.AUTO_RESPONSE
      };
    }
    
    // Check for hard bounces
    for (const bounceType of [
      BounceType.INVALID_RECIPIENT,
      BounceType.DOMAIN_NOT_FOUND,
      BounceType.MAILBOX_FULL,
      BounceType.MAILBOX_INACTIVE,
      BounceType.BLOCKED,
      BounceType.SPAM_RELATED,
      BounceType.POLICY_RELATED
    ]) {
      if (this.matchesPattern(fullText, bounceType)) {
        return {
          type: bounceType,
          category: BounceCategory.HARD
        };
      }
    }
    
    // Check for soft bounces
    for (const bounceType of [
      BounceType.SERVER_UNAVAILABLE,
      BounceType.TEMPORARY_FAILURE,
      BounceType.QUOTA_EXCEEDED,
      BounceType.NETWORK_ERROR,
      BounceType.TIMEOUT
    ]) {
      if (this.matchesPattern(fullText, bounceType)) {
        return {
          type: bounceType,
          category: BounceCategory.SOFT
        };
      }
    }
    
    // Handle DSN (Delivery Status Notification) status codes
    if (statusCode) {
      // Format: class.subject.detail
      const parts = statusCode.split('.');
      if (parts.length >= 2) {
        const statusClass = parts[0];
        const statusSubject = parts[1];
        
        // 5.X.X is permanent failure (hard bounce)
        if (statusClass === '5') {
          // Try to determine specific type based on subject
          if (statusSubject === '1') {
            return { type: BounceType.INVALID_RECIPIENT, category: BounceCategory.HARD };
          } else if (statusSubject === '2') {
            return { type: BounceType.MAILBOX_FULL, category: BounceCategory.HARD };
          } else if (statusSubject === '7') {
            return { type: BounceType.BLOCKED, category: BounceCategory.HARD };
          } else {
            return { type: BounceType.UNKNOWN, category: BounceCategory.HARD };
          }
        }
        
        // 4.X.X is temporary failure (soft bounce)
        if (statusClass === '4') {
          // Try to determine specific type based on subject
          if (statusSubject === '2') {
            return { type: BounceType.QUOTA_EXCEEDED, category: BounceCategory.SOFT };
          } else if (statusSubject === '3') {
            return { type: BounceType.SERVER_UNAVAILABLE, category: BounceCategory.SOFT };
          } else if (statusSubject === '4') {
            return { type: BounceType.NETWORK_ERROR, category: BounceCategory.SOFT };
          } else {
            return { type: BounceType.TEMPORARY_FAILURE, category: BounceCategory.SOFT };
          }
        }
      }
    }
    
    // Default to unknown
    return {
      type: BounceType.UNKNOWN,
      category: BounceCategory.UNKNOWN
    };
  }
  
  /**
   * Check if text matches any pattern for a bounce type
   * @param text Text to check against patterns
   * @param bounceType Bounce type to get patterns for
   * @returns Whether the text matches any pattern
   */
  private matchesPattern(text: string, bounceType: BounceType): boolean {
    const patterns = BOUNCE_PATTERNS[bounceType];
    
    if (!patterns) {
      return false;
    }
    
    for (const pattern of patterns) {
      if (pattern.test(text)) {
        return true;
      }
    }
    
    return false;
  }
  
  /**
   * Get all known hard bounced addresses
   * @returns Array of hard bounced email addresses
   */
  public getHardBouncedAddresses(): string[] {
    const hardBounced: string[] = [];
    
    for (const [email, info] of this.bounceCache.entries()) {
      if (info.category === BounceCategory.HARD) {
        hardBounced.push(email);
      }
    }
    
    return hardBounced;
  }
  
  /**
   * Get suppression list
   * @returns Array of suppressed email addresses
   */
  public getSuppressionList(): string[] {
    return Array.from(this.suppressionList.keys());
  }
  
  /**
   * Clear old bounce records (for maintenance)
   * @param olderThan Timestamp to remove records older than
   * @returns Number of records removed
   */
  public clearOldBounceRecords(olderThan: number): number {
    let removed = 0;
    
    this.bounceStore = this.bounceStore.filter(bounce => {
      if (bounce.timestamp < olderThan) {
        removed++;
        return false;
      }
      return true;
    });
    
    return removed;
  }
}