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;

// Helper function to wait for SMTP response
const waitForResponse = (socket: net.Socket, expectedCode: string, timeout = 5000): Promise<string> => {
  return new Promise((resolve, reject) => {
    let buffer = '';
    const timer = setTimeout(() => {
      socket.removeListener('data', handler);
      reject(new Error(`Timeout waiting for ${expectedCode} response`));
    }, timeout);
    
    const handler = (data: Buffer) => {
      buffer += data.toString();
      const lines = buffer.split('\r\n');
      
      // Check if we have a complete response
      for (const line of lines) {
        if (line.startsWith(expectedCode + ' ')) {
          clearTimeout(timer);
          socket.removeListener('data', handler);
          resolve(buffer);
          return;
        }
      }
    };
    
    socket.on('data', handler);
  });
};

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

tap.test('PERF-04: Memory usage - Connection memory test', async (tools) => {
  const done = tools.defer();
  const connectionCount = 10; // Reduced from 20 to make test faster
  const connections: net.Socket[] = [];

  try {
    // Force garbage collection if available
    if (global.gc) {
      global.gc();
    }

    // Record initial memory usage
    const initialMemory = process.memoryUsage();
    console.log(`Initial memory usage: ${Math.round(initialMemory.heapUsed / (1024 * 1024))}MB`);

    // Create multiple connections with large email content
    console.log(`Creating ${connectionCount} connections with large emails...`);

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

      connections.push(socket);

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

      // Read greeting
      await waitForResponse(socket, '220');

      // Send EHLO
      socket.write(`EHLO testhost-mem-${i}\r\n`);
      await waitForResponse(socket, '250');

      // Send email transaction
      socket.write(`MAIL FROM:<sender${i}@example.com>\r\n`);
      await waitForResponse(socket, '250');

      socket.write(`RCPT TO:<recipient${i}@example.com>\r\n`);
      await waitForResponse(socket, '250');

      socket.write('DATA\r\n');
      await waitForResponse(socket, '354');

      // Send large email content
      const largeContent = 'This is a large email content for memory testing. '.repeat(100);
      const emailContent = [
        `From: sender${i}@example.com`,
        `To: recipient${i}@example.com`,
        `Subject: Memory Usage Test ${i}`,
        '',
        largeContent,
        '.',
        ''
      ].join('\r\n');

      socket.write(emailContent);
      await waitForResponse(socket, '250');

      // Pause every 5 connections
      if (i > 0 && i % 5 === 0) {
        await new Promise(resolve => setTimeout(resolve, 100));
        const intermediateMemory = process.memoryUsage();
        console.log(`Memory after ${i} connections: ${Math.round(intermediateMemory.heapUsed / (1024 * 1024))}MB`);
      }
    }

    // Wait to let memory stabilize
    await new Promise(resolve => setTimeout(resolve, 2000));

    // Record final memory usage
    const finalMemory = process.memoryUsage();
    const memoryIncreaseMB = (finalMemory.heapUsed - initialMemory.heapUsed) / (1024 * 1024);
    const memoryPerConnectionKB = (memoryIncreaseMB * 1024) / connectionCount;

    console.log(`\nMemory Usage Results:`);
    console.log(`Initial heap: ${Math.round(initialMemory.heapUsed / (1024 * 1024))}MB`);
    console.log(`Final heap: ${Math.round(finalMemory.heapUsed / (1024 * 1024))}MB`);
    console.log(`Memory increase: ${memoryIncreaseMB.toFixed(2)}MB`);
    console.log(`Memory per connection: ${memoryPerConnectionKB.toFixed(2)}KB`);
    console.log(`RSS increase: ${Math.round((finalMemory.rss - initialMemory.rss) / (1024 * 1024))}MB`);

    // Clean up connections
    for (const socket of connections) {
      if (socket.writable) {
        socket.write('QUIT\r\n');
        socket.end();
      }
    }

    // Test passes if memory increase is reasonable (less than 30MB for 10 connections)
    expect(memoryIncreaseMB).toBeLessThan(30);
    done.resolve();
  } catch (error) {
    // Clean up on error
    connections.forEach(socket => socket.destroy());
    done.reject(error);
  }
});

tap.test('PERF-04: Memory usage - Memory leak detection', async (tools) => {
  const done = tools.defer();
  const iterations = 3; // Reduced from 5
  const connectionsPerIteration = 3; // Reduced from 5

  try {
    // Force GC if available
    if (global.gc) {
      global.gc();
    }

    const initialMemory = process.memoryUsage();
    const memorySnapshots: number[] = [];

    console.log(`\nRunning memory leak detection (${iterations} iterations)...`);

    for (let iteration = 0; iteration < iterations; iteration++) {
      const sockets: net.Socket[] = [];

      // Create and close connections
      for (let i = 0; i < connectionsPerIteration; i++) {
        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);
        });

        // Quick transaction
        await waitForResponse(socket, '220');

        socket.write('EHLO leaktest\r\n');
        await waitForResponse(socket, '250');

        socket.write('QUIT\r\n');
        await waitForResponse(socket, '221');

        socket.end();
        sockets.push(socket);
      }

      // Wait for sockets to close
      await new Promise(resolve => setTimeout(resolve, 500));

      // Force cleanup
      sockets.forEach(s => s.destroy());

      // Force GC if available
      if (global.gc) {
        global.gc();
      }

      // Record memory after each iteration
      const currentMemory = process.memoryUsage();
      const memoryMB = currentMemory.heapUsed / (1024 * 1024);
      memorySnapshots.push(memoryMB);

      console.log(`Iteration ${iteration + 1}: ${memoryMB.toFixed(2)}MB`);

      await new Promise(resolve => setTimeout(resolve, 500));
    }

    // Check for memory leak pattern
    const firstSnapshot = memorySnapshots[0];
    const lastSnapshot = memorySnapshots[memorySnapshots.length - 1];
    const memoryGrowth = lastSnapshot - firstSnapshot;
    const avgGrowthPerIteration = memoryGrowth / (iterations - 1);

    console.log(`\nMemory Leak Detection Results:`);
    console.log(`First snapshot: ${firstSnapshot.toFixed(2)}MB`);
    console.log(`Last snapshot: ${lastSnapshot.toFixed(2)}MB`);
    console.log(`Total growth: ${memoryGrowth.toFixed(2)}MB`);
    console.log(`Average growth per iteration: ${avgGrowthPerIteration.toFixed(2)}MB`);

    // Test passes if average growth per iteration is less than 2MB
    expect(avgGrowthPerIteration).toBeLessThan(2);
    done.resolve();
  } catch (error) {
    done.reject(error);
  }
});

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

export default tap.start();