import * as plugins from '../../ts/plugins.js';

/**
 * Test result interface
 */
export interface ITestResult {
  success: boolean;
  duration: number;
  message?: string;
  error?: string;
  details?: any;
}

/**
 * Test configuration interface
 */
export interface ITestConfig {
  host: string;
  port: number;
  timeout: number;
  fromAddress?: string;
  toAddress?: string;
  [key: string]: any;
}

/**
 * Connect to SMTP server and get greeting
 */
export async function connectToSmtp(host: string, port: number, timeout: number = 5000): Promise<plugins.net.Socket> {
  return new Promise((resolve, reject) => {
    const socket = plugins.net.createConnection({ host, port });
    const timer = setTimeout(() => {
      socket.destroy();
      reject(new Error(`Connection timeout after ${timeout}ms`));
    }, timeout);
    
    socket.once('connect', () => {
      clearTimeout(timer);
      resolve(socket);
    });
    
    socket.once('error', (error) => {
      clearTimeout(timer);
      reject(error);
    });
  });
}

/**
 * Send SMTP command and wait for response
 */
export async function sendSmtpCommand(
  socket: plugins.net.Socket, 
  command: string, 
  expectedCode?: string,
  timeout: number = 5000
): Promise<string> {
  return new Promise((resolve, reject) => {
    let buffer = '';
    let timer: NodeJS.Timeout;
    
    const onData = (data: Buffer) => {
      buffer += data.toString();
      
      // Check if we have a complete response
      if (buffer.includes('\r\n')) {
        clearTimeout(timer);
        socket.removeListener('data', onData);
        
        if (expectedCode && !buffer.startsWith(expectedCode)) {
          reject(new Error(`Expected ${expectedCode}, got: ${buffer.trim()}`));
        } else {
          resolve(buffer);
        }
      }
    };
    
    timer = setTimeout(() => {
      socket.removeListener('data', onData);
      reject(new Error(`Command timeout after ${timeout}ms`));
    }, timeout);
    
    socket.on('data', onData);
    socket.write(command + '\r\n');
  });
}

/**
 * Wait for SMTP greeting
 */
export async function waitForGreeting(socket: plugins.net.Socket, timeout: number = 5000): Promise<string> {
  return new Promise((resolve, reject) => {
    let buffer = '';
    let timer: NodeJS.Timeout;
    
    const onData = (data: Buffer) => {
      buffer += data.toString();
      
      if (buffer.includes('220')) {
        clearTimeout(timer);
        socket.removeListener('data', onData);
        resolve(buffer);
      }
    };
    
    timer = setTimeout(() => {
      socket.removeListener('data', onData);
      reject(new Error(`Greeting timeout after ${timeout}ms`));
    }, timeout);
    
    socket.on('data', onData);
  });
}

/**
 * Perform SMTP handshake
 */
export async function performSmtpHandshake(
  socket: plugins.net.Socket,
  hostname: string = 'test.example.com'
): Promise<string[]> {
  const capabilities: string[] = [];
  
  // Wait for greeting
  await waitForGreeting(socket);
  
  // Send EHLO
  const ehloResponse = await sendSmtpCommand(socket, `EHLO ${hostname}`, '250');
  
  // Parse capabilities
  const lines = ehloResponse.split('\r\n');
  for (const line of lines) {
    if (line.startsWith('250-') || line.startsWith('250 ')) {
      const capability = line.substring(4).trim();
      if (capability) {
        capabilities.push(capability);
      }
    }
  }
  
  return capabilities;
}

/**
 * Create multiple concurrent connections
 */
export async function createConcurrentConnections(
  host: string,
  port: number,
  count: number,
  timeout: number = 5000
): Promise<plugins.net.Socket[]> {
  const connectionPromises = [];
  
  for (let i = 0; i < count; i++) {
    connectionPromises.push(connectToSmtp(host, port, timeout));
  }
  
  return Promise.all(connectionPromises);
}

/**
 * Close SMTP connection gracefully
 */
export async function closeSmtpConnection(socket: plugins.net.Socket): Promise<void> {
  try {
    await sendSmtpCommand(socket, 'QUIT', '221');
  } catch {
    // Ignore errors during QUIT
  }
  
  socket.destroy();
}

/**
 * Generate random email content
 */
export function generateRandomEmail(size: number = 1024): string {
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 \r\n';
  let content = '';
  
  for (let i = 0; i < size; i++) {
    content += chars.charAt(Math.floor(Math.random() * chars.length));
  }
  
  return content;
}

/**
 * Create MIME message
 */
export function createMimeMessage(options: {
  from: string;
  to: string;
  subject: string;
  text?: string;
  html?: string;
  attachments?: Array<{ filename: string; content: string; contentType: string }>;
}): string {
  const boundary = `----=_Part_${Date.now()}_${Math.random().toString(36).substring(2)}`;
  const date = new Date().toUTCString();
  
  let message = '';
  message += `From: ${options.from}\r\n`;
  message += `To: ${options.to}\r\n`;
  message += `Subject: ${options.subject}\r\n`;
  message += `Date: ${date}\r\n`;
  message += `MIME-Version: 1.0\r\n`;
  
  if (options.attachments && options.attachments.length > 0) {
    message += `Content-Type: multipart/mixed; boundary="${boundary}"\r\n`;
    message += '\r\n';
    
    // Text part
    if (options.text) {
      message += `--${boundary}\r\n`;
      message += 'Content-Type: text/plain; charset=utf-8\r\n';
      message += 'Content-Transfer-Encoding: 8bit\r\n';
      message += '\r\n';
      message += options.text + '\r\n';
    }
    
    // HTML part
    if (options.html) {
      message += `--${boundary}\r\n`;
      message += 'Content-Type: text/html; charset=utf-8\r\n';
      message += 'Content-Transfer-Encoding: 8bit\r\n';
      message += '\r\n';
      message += options.html + '\r\n';
    }
    
    // Attachments
    for (const attachment of options.attachments) {
      message += `--${boundary}\r\n`;
      message += `Content-Type: ${attachment.contentType}\r\n`;
      message += `Content-Disposition: attachment; filename="${attachment.filename}"\r\n`;
      message += 'Content-Transfer-Encoding: base64\r\n';
      message += '\r\n';
      message += Buffer.from(attachment.content).toString('base64') + '\r\n';
    }
    
    message += `--${boundary}--\r\n`;
  } else if (options.html && options.text) {
    const altBoundary = `----=_Alt_${Date.now()}_${Math.random().toString(36).substring(2)}`;
    message += `Content-Type: multipart/alternative; boundary="${altBoundary}"\r\n`;
    message += '\r\n';
    
    // Text part
    message += `--${altBoundary}\r\n`;
    message += 'Content-Type: text/plain; charset=utf-8\r\n';
    message += 'Content-Transfer-Encoding: 8bit\r\n';
    message += '\r\n';
    message += options.text + '\r\n';
    
    // HTML part
    message += `--${altBoundary}\r\n`;
    message += 'Content-Type: text/html; charset=utf-8\r\n';
    message += 'Content-Transfer-Encoding: 8bit\r\n';
    message += '\r\n';
    message += options.html + '\r\n';
    
    message += `--${altBoundary}--\r\n`;
  } else if (options.html) {
    message += 'Content-Type: text/html; charset=utf-8\r\n';
    message += 'Content-Transfer-Encoding: 8bit\r\n';
    message += '\r\n';
    message += options.html;
  } else {
    message += 'Content-Type: text/plain; charset=utf-8\r\n';
    message += 'Content-Transfer-Encoding: 8bit\r\n';
    message += '\r\n';
    message += options.text || '';
  }
  
  return message;
}

/**
 * Measure operation time
 */
export async function measureTime<T>(operation: () => Promise<T>): Promise<{ result: T; duration: number }> {
  const startTime = Date.now();
  const result = await operation();
  const duration = Date.now() - startTime;
  return { result, duration };
}

/**
 * Retry operation with exponential backoff
 */
export async function retryOperation<T>(
  operation: () => Promise<T>,
  maxRetries: number = 3,
  initialDelay: number = 1000
): Promise<T> {
  let lastError: Error;
  
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await operation();
    } catch (error) {
      lastError = error as Error;
      if (i < maxRetries - 1) {
        const delay = initialDelay * Math.pow(2, i);
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }
  
  throw lastError!;
}