/**
 * SMTP Client Core Implementation
 * Main client class with delegation to handlers
 */

import { EventEmitter } from 'node:events';
import type { Email } from '../../core/classes.email.js';
import type { 
  ISmtpClientOptions,
  ISmtpSendResult,
  ISmtpConnection,
  IConnectionPoolStatus,
  ConnectionState
} from './interfaces.js';
import { CONNECTION_STATES, SmtpErrorType } from './constants.js';
import type { ConnectionManager } from './connection-manager.js';
import type { CommandHandler } from './command-handler.js';
import type { AuthHandler } from './auth-handler.js';
import type { TlsHandler } from './tls-handler.js';
import type { SmtpErrorHandler } from './error-handler.js';
import { validateSender, validateRecipients } from './utils/validation.js';
import { logEmailSend, logPerformance, logDebug } from './utils/logging.js';

interface ISmtpClientDependencies {
  options: ISmtpClientOptions;
  connectionManager: ConnectionManager;
  commandHandler: CommandHandler;
  authHandler: AuthHandler;
  tlsHandler: TlsHandler;
  errorHandler: SmtpErrorHandler;
}

export class SmtpClient extends EventEmitter {
  private options: ISmtpClientOptions;
  private connectionManager: ConnectionManager;
  private commandHandler: CommandHandler;
  private authHandler: AuthHandler;
  private tlsHandler: TlsHandler;
  private errorHandler: SmtpErrorHandler;
  private isShuttingDown: boolean = false;
  
  constructor(dependencies: ISmtpClientDependencies) {
    super();
    
    this.options = dependencies.options;
    this.connectionManager = dependencies.connectionManager;
    this.commandHandler = dependencies.commandHandler;
    this.authHandler = dependencies.authHandler;
    this.tlsHandler = dependencies.tlsHandler;
    this.errorHandler = dependencies.errorHandler;
    
    this.setupEventForwarding();
  }
  
  /**
   * Send an email
   */
  public async sendMail(email: Email): Promise<ISmtpSendResult> {
    const startTime = Date.now();
    
    // Extract clean email addresses without display names for SMTP operations
    const fromAddress = email.getFromAddress();
    const recipients = email.getToAddresses();
    const ccRecipients = email.getCcAddresses();
    const bccRecipients = email.getBccAddresses();
    
    // Combine all recipients for SMTP operations
    const allRecipients = [...recipients, ...ccRecipients, ...bccRecipients];
    
    // Validate email addresses
    if (!validateSender(fromAddress)) {
      throw new Error(`Invalid sender address: ${fromAddress}`);
    }
    
    const recipientErrors = validateRecipients(allRecipients);
    if (recipientErrors.length > 0) {
      throw new Error(`Invalid recipients: ${recipientErrors.join(', ')}`);
    }
    
    logEmailSend('start', allRecipients, this.options);
    
    let connection: ISmtpConnection | null = null;
    const result: ISmtpSendResult = {
      success: false,
      acceptedRecipients: [],
      rejectedRecipients: [],
      envelope: {
        from: fromAddress,
        to: allRecipients
      }
    };
    
    try {
      // Get connection
      connection = await this.connectionManager.getConnection();
      connection.state = CONNECTION_STATES.BUSY as ConnectionState;
      
      // Wait for greeting if new connection
      if (!connection.capabilities) {
        await this.commandHandler.waitForGreeting(connection);
      }
      
      // Perform EHLO
      await this.commandHandler.sendEhlo(connection, this.options.domain);
      
      // Upgrade to TLS if needed
      if (this.tlsHandler.shouldUseTLS(connection)) {
        await this.tlsHandler.upgradeToTLS(connection);
        // Re-send EHLO after TLS upgrade
        await this.commandHandler.sendEhlo(connection, this.options.domain);
      }
      
      // Authenticate if needed
      if (this.options.auth) {
        await this.authHandler.authenticate(connection);
      }
      
      // Send MAIL FROM
      const mailFromResponse = await this.commandHandler.sendMailFrom(connection, fromAddress);
      if (mailFromResponse.code >= 400) {
        throw new Error(`MAIL FROM failed: ${mailFromResponse.message}`);
      }
      
      // Send RCPT TO for each recipient (includes TO, CC, and BCC)
      for (const recipient of allRecipients) {
        try {
          const rcptResponse = await this.commandHandler.sendRcptTo(connection, recipient);
          if (rcptResponse.code >= 400) {
            result.rejectedRecipients.push(recipient);
            logDebug(`Recipient rejected: ${recipient}`, this.options, { response: rcptResponse });
          } else {
            result.acceptedRecipients.push(recipient);
          }
        } catch (error) {
          result.rejectedRecipients.push(recipient);
          logDebug(`Recipient error: ${recipient}`, this.options, { error });
        }
      }
      
      // Check if we have any accepted recipients
      if (result.acceptedRecipients.length === 0) {
        throw new Error('All recipients were rejected');
      }
      
      // Send DATA command
      const dataResponse = await this.commandHandler.sendData(connection);
      if (dataResponse.code !== 354) {
        throw new Error(`DATA command failed: ${dataResponse.message}`);
      }
      
      // Send email content
      const emailData = await this.formatEmailData(email);
      const sendResponse = await this.commandHandler.sendDataContent(connection, emailData);
      
      if (sendResponse.code >= 400) {
        throw new Error(`Email data rejected: ${sendResponse.message}`);
      }
      
      // Success
      result.success = true;
      result.messageId = this.extractMessageId(sendResponse.message);
      result.response = sendResponse.message;
      
      connection.messageCount++;
      logEmailSend('success', recipients, this.options, { 
        messageId: result.messageId,
        duration: Date.now() - startTime
      });
      
    } catch (error) {
      result.success = false;
      result.error = error instanceof Error ? error : new Error(String(error));
      
      // Classify error and determine if we should retry
      const errorType = this.errorHandler.classifyError(result.error);
      result.error = this.errorHandler.createError(
        result.error.message,
        errorType,
        { command: 'SEND_MAIL' },
        result.error
      );
      
      logEmailSend('failure', recipients, this.options, { 
        error: result.error,
        duration: Date.now() - startTime
      });
      
    } finally {
      // Release connection
      if (connection) {
        connection.state = CONNECTION_STATES.READY as ConnectionState;
        this.connectionManager.updateActivity(connection);
        this.connectionManager.releaseConnection(connection);
      }
      
      logPerformance('sendMail', Date.now() - startTime, this.options);
    }
    
    return result;
  }
  
  /**
   * Test connection to SMTP server
   */
  public async verify(): Promise<boolean> {
    let connection: ISmtpConnection | null = null;
    
    try {
      connection = await this.connectionManager.createConnection();
      await this.commandHandler.waitForGreeting(connection);
      await this.commandHandler.sendEhlo(connection, this.options.domain);
      
      if (this.tlsHandler.shouldUseTLS(connection)) {
        await this.tlsHandler.upgradeToTLS(connection);
        await this.commandHandler.sendEhlo(connection, this.options.domain);
      }
      
      if (this.options.auth) {
        await this.authHandler.authenticate(connection);
      }
      
      await this.commandHandler.sendQuit(connection);
      return true;
      
    } catch (error) {
      logDebug('Connection verification failed', this.options, { error });
      return false;
      
    } finally {
      if (connection) {
        this.connectionManager.closeConnection(connection);
      }
    }
  }
  
  /**
   * Check if client is connected
   */
  public isConnected(): boolean {
    const status = this.connectionManager.getPoolStatus();
    return status.total > 0;
  }
  
  /**
   * Get connection pool status
   */
  public getPoolStatus(): IConnectionPoolStatus {
    return this.connectionManager.getPoolStatus();
  }
  
  /**
   * Update client options
   */
  public updateOptions(newOptions: Partial<ISmtpClientOptions>): void {
    this.options = { ...this.options, ...newOptions };
    logDebug('Client options updated', this.options);
  }
  
  /**
   * Close all connections and shutdown client
   */
  public async close(): Promise<void> {
    if (this.isShuttingDown) {
      return;
    }
    
    this.isShuttingDown = true;
    logDebug('Shutting down SMTP client', this.options);
    
    try {
      this.connectionManager.closeAllConnections();
      this.emit('close');
    } catch (error) {
      logDebug('Error during client shutdown', this.options, { error });
    }
  }
  
  private async formatEmailData(email: Email): Promise<string> {
    // Convert Email object to raw SMTP data
    const headers: string[] = [];
    
    // Required headers
    headers.push(`From: ${email.from}`);
    headers.push(`To: ${Array.isArray(email.to) ? email.to.join(', ') : email.to}`);
    headers.push(`Subject: ${email.subject || ''}`);
    headers.push(`Date: ${new Date().toUTCString()}`);
    headers.push(`Message-ID: <${Date.now()}.${Math.random().toString(36)}@${this.options.host}>`);
    
    // Optional headers
    if (email.cc) {
      const cc = Array.isArray(email.cc) ? email.cc.join(', ') : email.cc;
      headers.push(`Cc: ${cc}`);
    }
    
    if (email.bcc) {
      const bcc = Array.isArray(email.bcc) ? email.bcc.join(', ') : email.bcc;
      headers.push(`Bcc: ${bcc}`);
    }
    
    // Content headers
    if (email.html && email.text) {
      // Multipart message
      const boundary = `boundary_${Date.now()}_${Math.random().toString(36)}`;
      headers.push(`Content-Type: multipart/alternative; boundary="${boundary}"`);
      headers.push('MIME-Version: 1.0');
      
      const body = [
        `--${boundary}`,
        'Content-Type: text/plain; charset=utf-8',
        'Content-Transfer-Encoding: quoted-printable',
        '',
        email.text,
        '',
        `--${boundary}`,
        'Content-Type: text/html; charset=utf-8',
        'Content-Transfer-Encoding: quoted-printable',
        '',
        email.html,
        '',
        `--${boundary}--`
      ].join('\r\n');
      
      return headers.join('\r\n') + '\r\n\r\n' + body;
    } else if (email.html) {
      headers.push('Content-Type: text/html; charset=utf-8');
      headers.push('MIME-Version: 1.0');
      return headers.join('\r\n') + '\r\n\r\n' + email.html;
    } else {
      headers.push('Content-Type: text/plain; charset=utf-8');
      headers.push('MIME-Version: 1.0');
      return headers.join('\r\n') + '\r\n\r\n' + (email.text || '');
    }
  }
  
  private extractMessageId(response: string): string | undefined {
    // Try to extract message ID from server response
    const match = response.match(/queued as ([^\s]+)/i) || 
                  response.match(/id=([^\s]+)/i) ||
                  response.match(/Message-ID: <([^>]+)>/i);
    return match ? match[1] : undefined;
  }
  
  private setupEventForwarding(): void {
    // Forward events from connection manager
    this.connectionManager.on('connection', (connection) => {
      this.emit('connection', connection);
    });
    
    this.connectionManager.on('disconnect', (connection) => {
      this.emit('disconnect', connection);
    });
    
    this.connectionManager.on('error', (error) => {
      this.emit('error', error);
    });
  }
}