import * as plugins from '../../plugins.js';
import * as paths from '../../paths.js';
import { Email } from '../core/classes.email.js';
import { EmailSignJob } from './classes.emailsignjob.js';
import type { UnifiedEmailServer } from '../routing/classes.unified.email.server.js';
import type { SmtpClient } from './smtpclient/smtp-client.js';
import type { ISmtpSendResult } from './smtpclient/interfaces.js';

// Configuration options for email sending
export interface IEmailSendOptions {
  maxRetries?: number;
  retryDelay?: number; // in milliseconds
  connectionTimeout?: number; // in milliseconds
  tlsOptions?: plugins.tls.ConnectionOptions;
  debugMode?: boolean;
}

// Email delivery status
export enum DeliveryStatus {
  PENDING = 'pending',
  SENDING = 'sending',
  DELIVERED = 'delivered',
  FAILED = 'failed',
  DEFERRED = 'deferred' // Temporary failure, will retry
}

// Detailed information about delivery attempts
export interface DeliveryInfo {
  status: DeliveryStatus;
  attempts: number;
  error?: Error;
  lastAttempt?: Date;
  nextAttempt?: Date;
  mxServer?: string;
  deliveryTime?: Date;
  logs: string[];
}

export class EmailSendJob {
  emailServerRef: UnifiedEmailServer;
  private email: Email;
  private mxServers: string[] = [];
  private currentMxIndex = 0;
  private options: IEmailSendOptions;
  public deliveryInfo: DeliveryInfo;

  constructor(emailServerRef: UnifiedEmailServer, emailArg: Email, options: IEmailSendOptions = {}) {
    this.email = emailArg;
    this.emailServerRef = emailServerRef;
    
    // Set default options
    this.options = {
      maxRetries: options.maxRetries || 3,
      retryDelay: options.retryDelay || 30000, // 30 seconds
      connectionTimeout: options.connectionTimeout || 60000, // 60 seconds
      tlsOptions: options.tlsOptions || {},
      debugMode: options.debugMode || false
    };
    
    // Initialize delivery info
    this.deliveryInfo = {
      status: DeliveryStatus.PENDING,
      attempts: 0,
      logs: []
    };
  }

  /**
   * Send the email to its recipients
   */
  async send(): Promise<DeliveryStatus> {
    try {
      // Check if the email is valid before attempting to send
      this.validateEmail();
      
      // Resolve MX records for the recipient domain
      await this.resolveMxRecords();
      
      // Try to send the email
      return await this.attemptDelivery();
    } catch (error) {
      this.log(`Critical error in send process: ${error.message}`);
      this.deliveryInfo.status = DeliveryStatus.FAILED;
      this.deliveryInfo.error = error;
      
      // Save failed email for potential future retry or analysis
      await this.saveFailed();
      return DeliveryStatus.FAILED;
    }
  }

  /**
   * Validate the email before sending
   */
  private validateEmail(): void {
    if (!this.email.to || this.email.to.length === 0) {
      throw new Error('No recipients specified');
    }
    
    if (!this.email.from) {
      throw new Error('No sender specified');
    }
    
    const fromDomain = this.email.getFromDomain();
    if (!fromDomain) {
      throw new Error('Invalid sender domain');
    }
  }

  /**
   * Resolve MX records for the recipient domain
   */
  private async resolveMxRecords(): Promise<void> {
    const domain = this.email.getPrimaryRecipient()?.split('@')[1];
    if (!domain) {
      throw new Error('Invalid recipient domain');
    }
    
    this.log(`Resolving MX records for domain: ${domain}`);
    try {
      const addresses = await this.resolveMx(domain);
      
      // Sort by priority (lowest number = highest priority)
      addresses.sort((a, b) => a.priority - b.priority);
      
      this.mxServers = addresses.map(mx => mx.exchange);
      this.log(`Found ${this.mxServers.length} MX servers: ${this.mxServers.join(', ')}`);
      
      if (this.mxServers.length === 0) {
        throw new Error(`No MX records found for domain: ${domain}`);
      }
    } catch (error) {
      this.log(`Failed to resolve MX records: ${error.message}`);
      throw new Error(`MX lookup failed for ${domain}: ${error.message}`);
    }
  }

  /**
   * Attempt to deliver the email with retries
   */
  private async attemptDelivery(): Promise<DeliveryStatus> {
    while (this.deliveryInfo.attempts < this.options.maxRetries) {
      this.deliveryInfo.attempts++;
      this.deliveryInfo.lastAttempt = new Date();
      this.deliveryInfo.status = DeliveryStatus.SENDING;
      
      try {
        this.log(`Delivery attempt ${this.deliveryInfo.attempts} of ${this.options.maxRetries}`);
        
        // Try each MX server in order of priority
        while (this.currentMxIndex < this.mxServers.length) {
          const currentMx = this.mxServers[this.currentMxIndex];
          this.deliveryInfo.mxServer = currentMx;
          
          try {
            this.log(`Attempting delivery to MX server: ${currentMx}`);
            await this.connectAndSend(currentMx);
            
            // If we get here, email was sent successfully
            this.deliveryInfo.status = DeliveryStatus.DELIVERED;
            this.deliveryInfo.deliveryTime = new Date();
            this.log(`Email delivered successfully to ${currentMx}`);
            
            // Record delivery for sender reputation monitoring
            this.recordDeliveryEvent('delivered');
            
            // Save successful email record
            await this.saveSuccess();
            return DeliveryStatus.DELIVERED;
          } catch (error) {
            this.log(`Failed to deliver to ${currentMx}: ${error.message}`);
            this.currentMxIndex++;
            
            // If this MX server failed, try the next one
            if (this.currentMxIndex >= this.mxServers.length) {
              throw error; // No more MX servers to try
            }
          }
        }
        
        throw new Error('All MX servers failed');
      } catch (error) {
        this.deliveryInfo.error = error;
        
        // Check if this is a permanent failure
        if (this.isPermanentFailure(error)) {
          this.log('Permanent failure detected, not retrying');
          this.deliveryInfo.status = DeliveryStatus.FAILED;
          
          // Record permanent failure for bounce management
          this.recordDeliveryEvent('bounced', true);
          
          await this.saveFailed();
          return DeliveryStatus.FAILED;
        }
        
        // This is a temporary failure
        if (this.deliveryInfo.attempts < this.options.maxRetries) {
          this.log(`Temporary failure, will retry in ${this.options.retryDelay}ms`);
          this.deliveryInfo.status = DeliveryStatus.DEFERRED;
          this.deliveryInfo.nextAttempt = new Date(Date.now() + this.options.retryDelay);
          
          // Record temporary failure for monitoring
          this.recordDeliveryEvent('deferred');
          
          // Reset MX server index for next retry
          this.currentMxIndex = 0;
          
          // Wait before retrying
          await this.delay(this.options.retryDelay);
        }
      }
    }
    
    // If we get here, all retries failed
    this.deliveryInfo.status = DeliveryStatus.FAILED;
    await this.saveFailed();
    return DeliveryStatus.FAILED;
  }

  /**
   * Connect to a specific MX server and send the email using SmtpClient
   */
  private async connectAndSend(mxServer: string): Promise<void> {
    this.log(`Connecting to ${mxServer}:25`);
    
    try {
      // Check if IP warmup is enabled and get an IP to use
      let localAddress: string | undefined = undefined;
      try {
        const fromDomain = this.email.getFromDomain();
        const bestIP = this.emailServerRef.getBestIPForSending({
          from: this.email.from,
          to: this.email.getAllRecipients(),
          domain: fromDomain,
          isTransactional: this.email.priority === 'high'
        });
        
        if (bestIP) {
          this.log(`Using warmed-up IP ${bestIP} for sending`);
          localAddress = bestIP;
          
          // Record the send for warm-up tracking
          this.emailServerRef.recordIPSend(bestIP);
        }
      } catch (error) {
        this.log(`Error selecting IP address: ${error.message}`);
      }
      
      // Get SMTP client from UnifiedEmailServer
      const smtpClient = this.emailServerRef.getSmtpClient(mxServer, 25);
      
      // Sign the email with DKIM if available
      let signedEmail = this.email;
      try {
        const fromDomain = this.email.getFromDomain();
        if (fromDomain && this.emailServerRef.hasDkimKey(fromDomain)) {
          // Convert email to RFC822 format for signing
          const emailMessage = this.email.toRFC822String();
          
          // Create sign job with proper options
          const emailSignJob = new EmailSignJob(this.emailServerRef, {
            domain: fromDomain,
            selector: 'default', // Using default selector
            headers: {}, // Headers will be extracted from emailMessage
            body: emailMessage
          });
          
          // Get the DKIM signature header
          const signatureHeader = await emailSignJob.getSignatureHeader(emailMessage);
          
          // Add the signature to the email
          if (signatureHeader) {
            // For now, we'll use the email as-is since SmtpClient will handle DKIM
            this.log(`Email ready for DKIM signing for domain: ${fromDomain}`);
          }
        }
      } catch (error) {
        this.log(`Failed to prepare DKIM: ${error.message}`);
      }
      
      // Send the email using SmtpClient
      const result: ISmtpSendResult = await smtpClient.sendMail(signedEmail);
      
      if (result.success) {
        this.log(`Email sent successfully: ${result.response}`);
        
        // Record the send for reputation monitoring
        this.recordDeliveryEvent('delivered');
      } else {
        throw new Error(result.error?.message || 'Failed to send email');
      }
    } catch (error) {
      this.log(`Failed to send email via ${mxServer}: ${error.message}`);
      throw error;
    }
  }

  /**
   * Record delivery event for monitoring
   */
  private recordDeliveryEvent(
    eventType: 'delivered' | 'bounced' | 'deferred',
    isHardBounce: boolean = false
  ): void {
    try {
      const domain = this.email.getFromDomain();
      if (domain) {
        if (eventType === 'delivered') {
          this.emailServerRef.recordDelivery(domain);
        } else if (eventType === 'bounced') {
          // Get the receiving domain for bounce recording
          let receivingDomain = null;
          const primaryRecipient = this.email.getPrimaryRecipient();
          if (primaryRecipient) {
            receivingDomain = primaryRecipient.split('@')[1];
          }
          
          if (receivingDomain) {
            this.emailServerRef.recordBounce(
              domain,
              receivingDomain,
              isHardBounce ? 'hard' : 'soft',
              this.deliveryInfo.error?.message || 'Unknown error'
            );
          }
        }
      }
    } catch (error) {
      this.log(`Failed to record delivery event: ${error.message}`);
    }
  }

  /**
   * Check if an error represents a permanent failure
   */
  private isPermanentFailure(error: Error): boolean {
    const permanentFailurePatterns = [
      'User unknown',
      'No such user',
      'Mailbox not found',
      'Invalid recipient',
      'Account disabled',
      'Account suspended',
      'Domain not found',
      'No such domain',
      'Invalid domain',
      'Relay access denied',
      'Access denied',
      'Blacklisted',
      'Blocked',
      '550', // Permanent failure SMTP code
      '551',
      '552',
      '553',
      '554'
    ];
    
    const errorMessage = error.message.toLowerCase();
    return permanentFailurePatterns.some(pattern => 
      errorMessage.includes(pattern.toLowerCase())
    );
  }

  /**
   * Resolve MX records for a domain
   */
  private resolveMx(domain: string): Promise<plugins.dns.MxRecord[]> {
    return new Promise((resolve, reject) => {
      plugins.dns.resolveMx(domain, (err, addresses) => {
        if (err) {
          reject(err);
        } else {
          resolve(addresses || []);
        }
      });
    });
  }

  /**
   * Log a message with timestamp
   */
  private log(message: string): void {
    const timestamp = new Date().toISOString();
    const logEntry = `[${timestamp}] ${message}`;
    this.deliveryInfo.logs.push(logEntry);
    
    if (this.options.debugMode) {
      console.log(`[EmailSendJob] ${logEntry}`);
    }
  }

  /**
   * Save successful email to storage
   */
  private async saveSuccess(): Promise<void> {
    try {
      // Use the existing email storage path
      const emailContent = this.email.toRFC822String();
      const fileName = `${Date.now()}_${this.email.from}_to_${this.email.to[0]}_success.eml`;
      const filePath = plugins.path.join(paths.sentEmailsDir, fileName);
      
      await plugins.smartfs.directory(paths.sentEmailsDir).recursive().create();
      await plugins.smartfs.file(filePath).write(emailContent);

      // Also save delivery info
      const infoFileName = `${Date.now()}_${this.email.from}_to_${this.email.to[0]}_info.json`;
      const infoPath = plugins.path.join(paths.sentEmailsDir, infoFileName);
      await plugins.smartfs.file(infoPath).write(JSON.stringify(this.deliveryInfo, null, 2));
      
      this.log(`Email saved to ${fileName}`);
    } catch (error) {
      this.log(`Failed to save email: ${error.message}`);
    }
  }

  /**
   * Save failed email to storage
   */
  private async saveFailed(): Promise<void> {
    try {
      // Use the existing email storage path
      const emailContent = this.email.toRFC822String();
      const fileName = `${Date.now()}_${this.email.from}_to_${this.email.to[0]}_failed.eml`;
      const filePath = plugins.path.join(paths.failedEmailsDir, fileName);
      
      await plugins.smartfs.directory(paths.failedEmailsDir).recursive().create();
      await plugins.smartfs.file(filePath).write(emailContent);

      // Also save delivery info with error details
      const infoFileName = `${Date.now()}_${this.email.from}_to_${this.email.to[0]}_error.json`;
      const infoPath = plugins.path.join(paths.failedEmailsDir, infoFileName);
      await plugins.smartfs.file(infoPath).write(JSON.stringify(this.deliveryInfo, null, 2));
      
      this.log(`Failed email saved to ${fileName}`);
    } catch (error) {
      this.log(`Failed to save failed email: ${error.message}`);
    }
  }

  /**
   * Delay for specified milliseconds
   */
  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}