import * as plugins from '@git.zone/tstest/tapbundle';
import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js';

const TEST_PORT = 2525;

let testServer;

tap.test('prepare server', async () => {
  testServer = await startTestServer({ port: TEST_PORT });
  await new Promise(resolve => setTimeout(resolve, 100));
});

tap.test('PERF-05: Connection processing time - Connection establishment', async (tools) => {
  const done = tools.defer();
  const testConnections = 10;
  const connectionTimes: number[] = [];

  try {
    console.log(`Testing connection establishment time for ${testConnections} connections...`);

    for (let i = 0; i < testConnections; i++) {
      const connectionStart = Date.now();
      
      const socket = net.createConnection({
        host: 'localhost',
        port: TEST_PORT,
        timeout: 30000
      });

      await new Promise<void>((resolve, reject) => {
        socket.once('connect', () => {
          const connectionTime = Date.now() - connectionStart;
          connectionTimes.push(connectionTime);
          resolve();
        });
        socket.once('error', reject);
      });

      // Read greeting
      await new Promise<void>((resolve) => {
        socket.once('data', () => resolve());
      });

      // Clean close
      socket.write('QUIT\r\n');
      await new Promise<void>((resolve) => {
        socket.once('data', () => resolve());
      });
      socket.end();

      // Small delay between connections
      await new Promise(resolve => setTimeout(resolve, 50));
    }

    // Calculate statistics
    const avgConnectionTime = connectionTimes.reduce((a, b) => a + b, 0) / connectionTimes.length;
    const minConnectionTime = Math.min(...connectionTimes);
    const maxConnectionTime = Math.max(...connectionTimes);

    console.log(`\nConnection Establishment Results:`);
    console.log(`Average: ${avgConnectionTime.toFixed(0)}ms`);
    console.log(`Min: ${minConnectionTime}ms`);
    console.log(`Max: ${maxConnectionTime}ms`);
    console.log(`All times: ${connectionTimes.join(', ')}ms`);

    // Test passes if average connection time is less than 1000ms
    expect(avgConnectionTime).toBeLessThan(1000);
    done.resolve();
  } catch (error) {
    done.reject(error);
  }
});

tap.test('PERF-05: Connection processing time - Transaction processing', async (tools) => {
  const done = tools.defer();
  const testTransactions = 10;
  const processingTimes: number[] = [];
  const fullTransactionTimes: number[] = [];

  // Add a timeout to prevent test from hanging
  const testTimeout = setTimeout(() => {
    console.log('Test timeout reached, moving on...');
    done.resolve();
  }, 30000); // 30 second timeout

  try {
    console.log(`\nTesting transaction processing time for ${testTransactions} transactions...`);

    for (let i = 0; i < testTransactions; i++) {
      const fullTransactionStart = Date.now();
      const socket = net.createConnection({
        host: 'localhost',
        port: TEST_PORT,
        timeout: 30000
      });

      await new Promise<void>((resolve, reject) => {
        socket.once('connect', resolve);
        socket.once('error', reject);
      });

      // Read greeting
      await new Promise<void>((resolve) => {
        socket.once('data', () => resolve());
      });

      const processingStart = Date.now();

      // Send EHLO
      socket.write(`EHLO testhost-perf-${i}\r\n`);
      
      await new Promise<void>((resolve) => {
        let data = '';
        const handleData = (chunk: Buffer) => {
          data += chunk.toString();
          // Look for the end of EHLO response (250 without dash)
          if (data.includes('250 ')) {
            socket.removeListener('data', handleData);
            resolve();
          }
        };
        socket.on('data', handleData);
      });

      // Send MAIL FROM
      socket.write(`MAIL FROM:<sender${i}@example.com>\r\n`);
      
      await new Promise<void>((resolve, reject) => {
        let mailResponse = '';
        const handleMailResponse = (chunk: Buffer) => {
          mailResponse += chunk.toString();
          if (mailResponse.includes('\r\n')) {
            socket.removeListener('data', handleMailResponse);
            if (mailResponse.includes('250')) {
              resolve();
            } else {
              reject(new Error(`MAIL FROM failed: ${mailResponse}`));
            }
          }
        };
        socket.on('data', handleMailResponse);
      });

      // Send RCPT TO
      socket.write(`RCPT TO:<recipient${i}@example.com>\r\n`);
      
      await new Promise<void>((resolve, reject) => {
        let rcptResponse = '';
        const handleRcptResponse = (chunk: Buffer) => {
          rcptResponse += chunk.toString();
          if (rcptResponse.includes('\r\n')) {
            socket.removeListener('data', handleRcptResponse);
            if (rcptResponse.includes('250')) {
              resolve();
            } else {
              reject(new Error(`RCPT TO failed: ${rcptResponse}`));
            }
          }
        };
        socket.on('data', handleRcptResponse);
      });

      // Send DATA
      socket.write('DATA\r\n');
      
      await new Promise<void>((resolve, reject) => {
        let dataResponse = '';
        const handleDataResponse = (chunk: Buffer) => {
          dataResponse += chunk.toString();
          if (dataResponse.includes('\r\n')) {
            socket.removeListener('data', handleDataResponse);
            if (dataResponse.includes('354')) {
              resolve();
            } else {
              reject(new Error(`DATA failed: ${dataResponse}`));
            }
          }
        };
        socket.on('data', handleDataResponse);
      });

      // Send email content
      const emailContent = [
        `From: sender${i}@example.com`,
        `To: recipient${i}@example.com`,
        `Subject: Connection Processing Test ${i}`,
        '',
        'Connection processing time test.',
        '.',
        ''
      ].join('\r\n');

      socket.write(emailContent);

      await new Promise<void>((resolve, reject) => {
        let submitResponse = '';
        const handleSubmitResponse = (chunk: Buffer) => {
          submitResponse += chunk.toString();
          if (submitResponse.includes('\r\n') && submitResponse.includes('250')) {
            socket.removeListener('data', handleSubmitResponse);
            resolve();
          } else if (submitResponse.includes('\r\n') && (submitResponse.includes('4') || submitResponse.includes('5'))) {
            socket.removeListener('data', handleSubmitResponse);
            reject(new Error(`Message submission failed: ${submitResponse}`));
          }
        };
        socket.on('data', handleSubmitResponse);
      });

      const processingTime = Date.now() - processingStart;
      processingTimes.push(processingTime);

      // Send QUIT
      socket.write('QUIT\r\n');
      await new Promise<void>((resolve) => {
        socket.once('data', () => resolve());
      });
      socket.end();

      const fullTransactionTime = Date.now() - fullTransactionStart;
      fullTransactionTimes.push(fullTransactionTime);

      // Small delay between transactions
      await new Promise(resolve => setTimeout(resolve, 50));
    }

    // Calculate statistics
    const avgProcessingTime = processingTimes.reduce((a, b) => a + b, 0) / processingTimes.length;
    const minProcessingTime = Math.min(...processingTimes);
    const maxProcessingTime = Math.max(...processingTimes);

    const avgFullTime = fullTransactionTimes.reduce((a, b) => a + b, 0) / fullTransactionTimes.length;

    console.log(`\nTransaction Processing Results:`);
    console.log(`Average processing: ${avgProcessingTime.toFixed(0)}ms`);
    console.log(`Min processing: ${minProcessingTime}ms`);
    console.log(`Max processing: ${maxProcessingTime}ms`);
    console.log(`Average full transaction: ${avgFullTime.toFixed(0)}ms`);

    // Test passes if average processing time is less than 2000ms
    expect(avgProcessingTime).toBeLessThan(2000);
    clearTimeout(testTimeout);
    done.resolve();
  } catch (error) {
    clearTimeout(testTimeout);
    done.reject(error);
  }
});

tap.test('PERF-05: Connection processing time - Command response times', async (tools) => {
  const done = tools.defer();
  const commandTimings: { [key: string]: number[] } = {
    EHLO: [],
    NOOP: []
  };

  // Add a timeout to prevent test from hanging
  const testTimeout = setTimeout(() => {
    console.log('Command timing test timeout reached, moving on...');
    done.resolve();
  }, 20000); // 20 second timeout

  try {
    console.log(`\nMeasuring individual command response times...`);

    const socket = net.createConnection({
      host: 'localhost',
      port: TEST_PORT,
      timeout: 30000
    });

    await new Promise<void>((resolve, reject) => {
      socket.once('connect', resolve);
      socket.once('error', reject);
    });

    // Read greeting
    await new Promise<void>((resolve) => {
      let greeting = '';
      const handleGreeting = (chunk: Buffer) => {
        greeting += chunk.toString();
        if (greeting.includes('220') && greeting.includes('\r\n')) {
          socket.removeListener('data', handleGreeting);
          resolve();
        }
      };
      socket.on('data', handleGreeting);
    });

    // Measure EHLO response times
    for (let i = 0; i < 3; i++) {
      const start = Date.now();
      socket.write('EHLO testhost\r\n');
      
      await new Promise<void>((resolve) => {
        let data = '';
        const handleData = (chunk: Buffer) => {
          data += chunk.toString();
          if (data.includes('250 ')) {
            socket.removeListener('data', handleData);
            commandTimings.EHLO.push(Date.now() - start);
            resolve();
          }
        };
        socket.on('data', handleData);
      });
    }

    // Measure NOOP response times
    for (let i = 0; i < 3; i++) {
      const start = Date.now();
      socket.write('NOOP\r\n');
      
      await new Promise<void>((resolve) => {
        let noopResponse = '';
        const handleNoop = (chunk: Buffer) => {
          noopResponse += chunk.toString();
          if (noopResponse.includes('\r\n')) {
            socket.removeListener('data', handleNoop);
            commandTimings.NOOP.push(Date.now() - start);
            resolve();
          }
        };
        socket.on('data', handleNoop);
      });
    }

    // Close connection
    socket.write('QUIT\r\n');
    await new Promise<void>((resolve) => {
      socket.once('data', () => {
        socket.end();
        resolve();
      });
    });

    // Calculate and display results
    console.log(`\nCommand Response Times (ms):`);
    for (const [command, times] of Object.entries(commandTimings)) {
      if (times.length > 0) {
        const avg = times.reduce((a, b) => a + b, 0) / times.length;
        console.log(`${command}: avg=${avg.toFixed(0)}, samples=[${times.join(', ')}]`);
        
        // All commands should respond in less than 500ms on average
        expect(avg).toBeLessThan(500);
      }
    }

    clearTimeout(testTimeout);
    done.resolve();
  } catch (error) {
    clearTimeout(testTimeout);
    done.reject(error);
  }
});

tap.test('cleanup server', async () => {
  await stopTestServer(testServer);
});

export default tap.start();