/**
 * STARTTLS Implementation
 * Provides an improved implementation for STARTTLS upgrades
 */

import * as plugins from '../../../plugins.js';
import { SmtpLogger } from './utils/logging.js';
import { 
  loadCertificatesFromString, 
  createTlsOptions,
  type ICertificateData
} from './certificate-utils.js';
import { getSocketDetails } from './utils/helpers.js';
import type { ISmtpSession, ISessionManager, IConnectionManager } from './interfaces.js';
import { SmtpState } from '../interfaces.js';

/**
 * Enhanced STARTTLS handler for more reliable TLS upgrades
 */
export async function performStartTLS(
  socket: plugins.net.Socket,
  options: {
    key: string;
    cert: string;
    ca?: string;
    session?: ISmtpSession;
    sessionManager?: ISessionManager;
    connectionManager?: IConnectionManager;
    onSuccess?: (tlsSocket: plugins.tls.TLSSocket) => void;
    onFailure?: (error: Error) => void;
    updateSessionState?: (session: ISmtpSession, state: SmtpState) => void;
  }
): Promise<plugins.tls.TLSSocket | undefined> {
  return new Promise<plugins.tls.TLSSocket | undefined>((resolve) => {
    try {
      const socketDetails = getSocketDetails(socket);
      
      SmtpLogger.info('Starting enhanced STARTTLS upgrade process', { 
        remoteAddress: socketDetails.remoteAddress,
        remotePort: socketDetails.remotePort
      });
      
      // Create a proper socket cleanup function
      const cleanupSocket = () => {
        // Remove all listeners to prevent memory leaks
        socket.removeAllListeners('data');
        socket.removeAllListeners('error');
        socket.removeAllListeners('close');
        socket.removeAllListeners('end');
        socket.removeAllListeners('drain');
      };
      
      // Prepare the socket for TLS upgrade
      socket.setNoDelay(true);
      
      // Critical: make sure there's no pending data before TLS handshake
      socket.pause();
      
      // Add error handling for the base socket
      const handleSocketError = (err: Error) => {
        SmtpLogger.error(`Socket error during STARTTLS preparation: ${err.message}`, {
          remoteAddress: socketDetails.remoteAddress,
          remotePort: socketDetails.remotePort,
          error: err,
          stack: err.stack
        });
        
        if (options.onFailure) {
          options.onFailure(err);
        }
        
        // Resolve with undefined to indicate failure
        resolve(undefined);
      };
      
      socket.once('error', handleSocketError);
      
      // Load certificates
      let certificates: ICertificateData;
      try {
        certificates = loadCertificatesFromString({
          key: options.key,
          cert: options.cert,
          ca: options.ca
        });
      } catch (certError) {
        SmtpLogger.error(`Certificate error during STARTTLS: ${certError instanceof Error ? certError.message : String(certError)}`);
        
        if (options.onFailure) {
          options.onFailure(certError instanceof Error ? certError : new Error(String(certError)));
        }
        
        resolve(undefined);
        return;
      }
      
      // Create TLS options optimized for STARTTLS
      const tlsOptions = createTlsOptions(certificates, true);
      
      // Create secure context
      let secureContext;
      try {
        secureContext = plugins.tls.createSecureContext(tlsOptions);
      } catch (contextError) {
        SmtpLogger.error(`Failed to create secure context: ${contextError instanceof Error ? contextError.message : String(contextError)}`);
        
        if (options.onFailure) {
          options.onFailure(contextError instanceof Error ? contextError : new Error(String(contextError)));
        }
        
        resolve(undefined);
        return;
      }
      
      // Log STARTTLS upgrade attempt
      SmtpLogger.debug('Attempting TLS socket upgrade with options', {
        minVersion: tlsOptions.minVersion,
        maxVersion: tlsOptions.maxVersion,
        handshakeTimeout: tlsOptions.handshakeTimeout
      });
      
      // Use a safer approach to create the TLS socket
      const handshakeTimeout = 30000; // 30 seconds timeout for TLS handshake
      let handshakeTimeoutId: NodeJS.Timeout | undefined;
      
      // Create the TLS socket using a conservative approach for STARTTLS
      const tlsSocket = new plugins.tls.TLSSocket(socket, {
        isServer: true,
        secureContext,
        // Server-side options (simpler is more reliable for STARTTLS)
        requestCert: false,
        rejectUnauthorized: false
      });
      
      // Set up error handling for the TLS socket
      tlsSocket.once('error', (err) => {
        if (handshakeTimeoutId) {
          clearTimeout(handshakeTimeoutId);
        }
        
        SmtpLogger.error(`TLS error during STARTTLS: ${err.message}`, {
          remoteAddress: socketDetails.remoteAddress,
          remotePort: socketDetails.remotePort,
          error: err,
          stack: err.stack
        });
        
        // Clean up socket listeners
        cleanupSocket();
        
        if (options.onFailure) {
          options.onFailure(err);
        }
        
        // Destroy the socket to ensure we don't have hanging connections
        tlsSocket.destroy();
        resolve(undefined);
      });
      
      // Set up handshake timeout manually for extra safety
      handshakeTimeoutId = setTimeout(() => {
        SmtpLogger.error('TLS handshake timed out', {
          remoteAddress: socketDetails.remoteAddress,
          remotePort: socketDetails.remotePort
        });
        
        // Clean up socket listeners
        cleanupSocket();
        
        if (options.onFailure) {
          options.onFailure(new Error('TLS handshake timed out'));
        }
        
        // Destroy the socket to ensure we don't have hanging connections
        tlsSocket.destroy();
        resolve(undefined);
      }, handshakeTimeout);
      
      // Set up handler for successful TLS negotiation
      tlsSocket.once('secure', () => {
        if (handshakeTimeoutId) {
          clearTimeout(handshakeTimeoutId);
        }
        
        const protocol = tlsSocket.getProtocol();
        const cipher = tlsSocket.getCipher();
        
        SmtpLogger.info('TLS upgrade successful via STARTTLS', {
          remoteAddress: socketDetails.remoteAddress,
          remotePort: socketDetails.remotePort,
          protocol: protocol || 'unknown',
          cipher: cipher?.name || 'unknown'
        });
        
        // Update socket mapping in session manager
        if (options.sessionManager) {
          const socketReplaced = options.sessionManager.replaceSocket(socket, tlsSocket);
          if (!socketReplaced) {
            SmtpLogger.error('Failed to replace socket in session manager after STARTTLS', {
              remoteAddress: socketDetails.remoteAddress,
              remotePort: socketDetails.remotePort
            });
          }
        }
        
        // Re-attach event handlers from connection manager
        if (options.connectionManager) {
          try {
            options.connectionManager.setupSocketEventHandlers(tlsSocket);
            SmtpLogger.debug('Successfully re-attached connection manager event handlers to TLS socket', {
              remoteAddress: socketDetails.remoteAddress,
              remotePort: socketDetails.remotePort
            });
          } catch (handlerError) {
            SmtpLogger.error('Failed to re-attach event handlers to TLS socket after STARTTLS', {
              remoteAddress: socketDetails.remoteAddress,
              remotePort: socketDetails.remotePort,
              error: handlerError instanceof Error ? handlerError : new Error(String(handlerError))
            });
          }
        }
        
        // Update session if provided
        if (options.session) {
          // Update session properties to indicate TLS is active
          options.session.useTLS = true;
          options.session.secure = true;
          
          // Reset session state as required by RFC 3207
          // After STARTTLS, client must issue a new EHLO
          if (options.updateSessionState) {
            options.updateSessionState(options.session, SmtpState.GREETING);
          }
        }
        
        // Call success callback if provided
        if (options.onSuccess) {
          options.onSuccess(tlsSocket);
        }
        
        // Success - return the TLS socket
        resolve(tlsSocket);
      });
      
      // Resume the socket after we've set up all handlers
      // This allows the TLS handshake to proceed
      socket.resume();
      
    } catch (error) {
      SmtpLogger.error(`Unexpected error in STARTTLS: ${error instanceof Error ? error.message : String(error)}`, {
        error: error instanceof Error ? error : new Error(String(error)),
        stack: error instanceof Error ? error.stack : 'No stack trace available'
      });
      
      if (options.onFailure) {
        options.onFailure(error instanceof Error ? error : new Error(String(error)));
      }
      
      resolve(undefined);
    }
  });
}