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-07: Resource cleanup - Connection cleanup efficiency', async (tools) => {
  const done = tools.defer();
  const testConnections = 20; // Reduced from 50
  const connections: net.Socket[] = [];
  const cleanupTimes: number[] = [];

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

    const initialMemory = process.memoryUsage();
    console.log(`Initial memory: ${Math.round(initialMemory.heapUsed / (1024 * 1024))}MB`);
    console.log(`Creating ${testConnections} connections for resource cleanup test...`);

    // Create many connections and process emails
    for (let i = 0; i < testConnections; 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-cleanup-${i}\r\n`);
      
      await waitForResponse(socket, '250');

      // Complete 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');

      const emailContent = [
        `From: sender${i}@example.com`,
        `To: recipient${i}@example.com`,
        `Subject: Resource Cleanup Test ${i}`,
        '',
        'Testing resource cleanup.',
        '.',
        ''
      ].join('\r\n');

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

      // Pause every 10 connections
      if (i > 0 && i % 10 === 0) {
        await new Promise(resolve => setTimeout(resolve, 50));
      }
    }

    const midTestMemory = process.memoryUsage();
    console.log(`Memory after creating connections: ${Math.round(midTestMemory.heapUsed / (1024 * 1024))}MB`);

    // Clean up all connections and measure cleanup time
    console.log('\nCleaning up connections...');
    
    for (let i = 0; i < connections.length; i++) {
      const socket = connections[i];
      const cleanupStart = Date.now();

      try {
        if (socket.writable) {
          socket.write('QUIT\r\n');
          try {
            await waitForResponse(socket, '221', 1000);
          } catch (e) {
            // Ignore timeout on QUIT
          }
        }

        socket.end();
        await new Promise<void>((resolve) => {
          socket.once('close', () => resolve());
          setTimeout(() => resolve(), 100); // Fallback timeout
        });

        cleanupTimes.push(Date.now() - cleanupStart);
      } catch (error) {
        cleanupTimes.push(Date.now() - cleanupStart);
      }
    }

    // Wait for cleanup to complete
    await new Promise(resolve => setTimeout(resolve, 2000));

    // Force GC if available
    if (global.gc) {
      global.gc();
      await new Promise(resolve => setTimeout(resolve, 1000));
    }

    const finalMemory = process.memoryUsage();
    const memoryIncreaseMB = (finalMemory.heapUsed - initialMemory.heapUsed) / (1024 * 1024);
    const avgCleanupTime = cleanupTimes.reduce((a, b) => a + b, 0) / cleanupTimes.length;
    const maxCleanupTime = Math.max(...cleanupTimes);

    console.log(`\nResource Cleanup Results:`);
    console.log(`Initial memory: ${Math.round(initialMemory.heapUsed / (1024 * 1024))}MB`);
    console.log(`Mid-test memory: ${Math.round(midTestMemory.heapUsed / (1024 * 1024))}MB`);
    console.log(`Final memory: ${Math.round(finalMemory.heapUsed / (1024 * 1024))}MB`);
    console.log(`Memory increase: ${memoryIncreaseMB.toFixed(2)}MB`);
    console.log(`Average cleanup time: ${avgCleanupTime.toFixed(0)}ms`);
    console.log(`Max cleanup time: ${maxCleanupTime}ms`);

    // Test passes if memory increase is less than 10MB and cleanup is fast
    expect(memoryIncreaseMB).toBeLessThan(10);
    expect(avgCleanupTime).toBeLessThan(100);
    done.resolve();
  } catch (error) {
    // Emergency cleanup
    connections.forEach(socket => socket.destroy());
    done.reject(error);
  }
});

tap.test('PERF-07: Resource cleanup - File descriptor management', async (tools) => {
  const done = tools.defer();
  const rapidConnections = 20;
  let successfulCleanups = 0;

  try {
    console.log(`\nTesting rapid connection open/close cycles...`);

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

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

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

        // Quick EHLO/QUIT
        socket.write('EHLO rapidtest\r\n');
        await waitForResponse(socket, '250');

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

        socket.end();
        
        await new Promise<void>((resolve) => {
          socket.once('close', () => {
            successfulCleanups++;
            resolve();
          });
        });

      } catch (error) {
        socket.destroy();
        console.log(`Connection ${i} failed:`, error);
      }

      // Very short delay
      await new Promise(resolve => setTimeout(resolve, 20));
    }

    console.log(`Successful cleanups: ${successfulCleanups}/${rapidConnections}`);

    // Test passes if at least 90% of connections cleaned up successfully
    const cleanupRate = successfulCleanups / rapidConnections;
    expect(cleanupRate).toBeGreaterThanOrEqual(0.9);
    done.resolve();
  } catch (error) {
    done.reject(error);
  }
});

tap.test('PERF-07: Resource cleanup - Memory recovery after load', async (tools) => {
  const done = tools.defer();

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

    const baselineMemory = process.memoryUsage();
    console.log(`\nBaseline memory: ${Math.round(baselineMemory.heapUsed / (1024 * 1024))}MB`);

    // Create load
    const loadConnections = 10;
    const sockets: net.Socket[] = [];

    console.log('Creating load...');
    for (let i = 0; i < loadConnections; 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);
      });

      sockets.push(socket);

      // Just connect, don't send anything
      await waitForResponse(socket, '220');
    }

    const loadMemory = process.memoryUsage();
    console.log(`Memory under load: ${Math.round(loadMemory.heapUsed / (1024 * 1024))}MB`);

    // Clean up all at once
    console.log('Cleaning up all connections...');
    sockets.forEach(socket => {
      socket.destroy();
    });

    // Wait for cleanup
    await new Promise(resolve => setTimeout(resolve, 3000));

    // Force GC multiple times
    if (global.gc) {
      for (let i = 0; i < 3; i++) {
        global.gc();
        await new Promise(resolve => setTimeout(resolve, 500));
      }
    }

    const recoveredMemory = process.memoryUsage();
    const memoryIncrease = loadMemory.heapUsed - baselineMemory.heapUsed;
    const memoryRecovered = loadMemory.heapUsed - recoveredMemory.heapUsed;
    const recoveryPercent = memoryIncrease > 0 ? (memoryRecovered / memoryIncrease) * 100 : 100;

    console.log(`Memory after cleanup: ${Math.round(recoveredMemory.heapUsed / (1024 * 1024))}MB`);
    console.log(`Memory recovered: ${Math.round(memoryRecovered / (1024 * 1024))}MB`);
    console.log(`Recovery percentage: ${recoveryPercent.toFixed(1)}%`);

    // Test passes if memory is stable (no significant increase) or we recover at least 50%
    if (memoryIncrease < 1024 * 1024) { // Less than 1MB increase
      console.log('Memory usage was stable during test - good resource management!');
      expect(true).toEqual(true);
    } else {
      expect(recoveryPercent).toBeGreaterThan(50);
    }
    done.resolve();
  } catch (error) {
    done.reject(error);
  }
});

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

export default tap.start();