/**
 * SMTP Client TLS Handler
 * TLS and STARTTLS client functionality
 */

import * as tls from 'node:tls';
import * as net from 'node:net';
import { DEFAULTS } from './constants.js';
import type { 
  ISmtpConnection, 
  ISmtpClientOptions,
  ConnectionState 
} from './interfaces.js';
import { CONNECTION_STATES } from './constants.js';
import { logTLS, logDebug } from './utils/logging.js';
import { isSuccessCode } from './utils/helpers.js';
import type { CommandHandler } from './command-handler.js';

export class TlsHandler {
  private options: ISmtpClientOptions;
  private commandHandler: CommandHandler;
  
  constructor(options: ISmtpClientOptions, commandHandler: CommandHandler) {
    this.options = options;
    this.commandHandler = commandHandler;
  }
  
  /**
   * Upgrade connection to TLS using STARTTLS
   */
  public async upgradeToTLS(connection: ISmtpConnection): Promise<void> {
    if (connection.secure) {
      logDebug('Connection already secure', this.options);
      return;
    }
    
    // Check if STARTTLS is supported
    if (!connection.capabilities?.starttls) {
      throw new Error('Server does not support STARTTLS');
    }
    
    logTLS('starttls_start', this.options);
    
    try {
      // Send STARTTLS command
      const response = await this.commandHandler.sendStartTls(connection);
      
      if (!isSuccessCode(response.code)) {
        throw new Error(`STARTTLS command failed: ${response.message}`);
      }
      
      // Upgrade the socket to TLS
      await this.performTLSUpgrade(connection);
      
      // Clear capabilities as they may have changed after TLS
      connection.capabilities = undefined;
      connection.secure = true;
      
      logTLS('starttls_success', this.options);
      
    } catch (error) {
      logTLS('starttls_failure', this.options, { error });
      throw error;
    }
  }
  
  /**
   * Create a direct TLS connection
   */
  public async createTLSConnection(host: string, port: number): Promise<tls.TLSSocket> {
    return new Promise((resolve, reject) => {
      const timeout = this.options.connectionTimeout || DEFAULTS.CONNECTION_TIMEOUT;
      
      const tlsOptions: tls.ConnectionOptions = {
        host,
        port,
        ...this.options.tls,
        // Default TLS options for email
        secureProtocol: 'TLS_method',
        ciphers: 'HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA',
        rejectUnauthorized: this.options.tls?.rejectUnauthorized !== false
      };
      
      logTLS('tls_connected', this.options, { host, port });
      
      const socket = tls.connect(tlsOptions);
      
      const timeoutHandler = setTimeout(() => {
        socket.destroy();
        reject(new Error(`TLS connection timeout after ${timeout}ms`));
      }, timeout);
      
      socket.once('secureConnect', () => {
        clearTimeout(timeoutHandler);
        
        if (!socket.authorized && this.options.tls?.rejectUnauthorized !== false) {
          socket.destroy();
          reject(new Error(`TLS certificate verification failed: ${socket.authorizationError}`));
          return;
        }
        
        logDebug('TLS connection established', this.options, {
          authorized: socket.authorized,
          protocol: socket.getProtocol(),
          cipher: socket.getCipher()
        });
        
        resolve(socket);
      });
      
      socket.once('error', (error) => {
        clearTimeout(timeoutHandler);
        reject(error);
      });
    });
  }
  
  /**
   * Validate TLS certificate
   */
  public validateCertificate(socket: tls.TLSSocket): boolean {
    if (!socket.authorized) {
      logDebug('TLS certificate not authorized', this.options, {
        error: socket.authorizationError
      });
      
      // Allow self-signed certificates if explicitly configured
      if (this.options.tls?.rejectUnauthorized === false) {
        logDebug('Accepting unauthorized certificate (rejectUnauthorized: false)', this.options);
        return true;
      }
      
      return false;
    }
    
    const cert = socket.getPeerCertificate();
    if (!cert) {
      logDebug('No peer certificate available', this.options);
      return false;
    }
    
    // Additional certificate validation
    const now = new Date();
    if (cert.valid_from && new Date(cert.valid_from) > now) {
      logDebug('Certificate not yet valid', this.options, { validFrom: cert.valid_from });
      return false;
    }
    
    if (cert.valid_to && new Date(cert.valid_to) < now) {
      logDebug('Certificate expired', this.options, { validTo: cert.valid_to });
      return false;
    }
    
    logDebug('TLS certificate validated', this.options, {
      subject: cert.subject,
      issuer: cert.issuer,
      validFrom: cert.valid_from,
      validTo: cert.valid_to
    });
    
    return true;
  }
  
  /**
   * Get TLS connection information
   */
  public getTLSInfo(socket: tls.TLSSocket): any {
    if (!(socket instanceof tls.TLSSocket)) {
      return null;
    }
    
    return {
      authorized: socket.authorized,
      authorizationError: socket.authorizationError,
      protocol: socket.getProtocol(),
      cipher: socket.getCipher(),
      peerCertificate: socket.getPeerCertificate(),
      alpnProtocol: socket.alpnProtocol
    };
  }
  
  /**
   * Check if TLS upgrade is required or recommended
   */
  public shouldUseTLS(connection: ISmtpConnection): boolean {
    // Already secure
    if (connection.secure) {
      return false;
    }
    
    // Direct TLS connection configured
    if (this.options.secure) {
      return false; // Already handled in connection establishment
    }
    
    // STARTTLS available and not explicitly disabled
    if (connection.capabilities?.starttls) {
      return this.options.tls !== null && this.options.tls !== undefined; // Use TLS if configured
    }
    
    return false;
  }
  
  private async performTLSUpgrade(connection: ISmtpConnection): Promise<void> {
    return new Promise((resolve, reject) => {
      const plainSocket = connection.socket as net.Socket;
      const timeout = this.options.connectionTimeout || DEFAULTS.CONNECTION_TIMEOUT;
      
      const tlsOptions: tls.ConnectionOptions = {
        socket: plainSocket,
        host: this.options.host,
        ...this.options.tls,
        // Default TLS options for STARTTLS
        secureProtocol: 'TLS_method',
        ciphers: 'HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA',
        rejectUnauthorized: this.options.tls?.rejectUnauthorized !== false
      };
      
      const timeoutHandler = setTimeout(() => {
        reject(new Error(`TLS upgrade timeout after ${timeout}ms`));
      }, timeout);
      
      // Create TLS socket from existing connection
      const tlsSocket = tls.connect(tlsOptions);
      
      tlsSocket.once('secureConnect', () => {
        clearTimeout(timeoutHandler);
        
        // Validate certificate if required
        if (!this.validateCertificate(tlsSocket)) {
          tlsSocket.destroy();
          reject(new Error('TLS certificate validation failed'));
          return;
        }
        
        // Replace the socket in the connection
        connection.socket = tlsSocket;
        connection.secure = true;
        
        logDebug('STARTTLS upgrade completed', this.options, {
          protocol: tlsSocket.getProtocol(),
          cipher: tlsSocket.getCipher()
        });
        
        resolve();
      });
      
      tlsSocket.once('error', (error) => {
        clearTimeout(timeoutHandler);
        reject(error);
      });
    });
  }
}