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;
const TEST_TIMEOUT = 10000;

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

// Test: Basic NOOP command
tap.test('NOOP - should accept NOOP command', async (tools) => {
  const done = tools.defer();
  
  const socket = net.createConnection({
    host: 'localhost',
    port: TEST_PORT,
    timeout: TEST_TIMEOUT
  });
  
  let receivedData = '';
  let currentStep = 'connecting';
  
  socket.on('data', (data) => {
    receivedData += data.toString();
    
    if (currentStep === 'connecting' && receivedData.includes('220')) {
      currentStep = 'ehlo';
      socket.write('EHLO test.example.com\r\n');
    } else if (currentStep === 'ehlo' && receivedData.includes('250')) {
      currentStep = 'noop';
      socket.write('NOOP\r\n');
    } else if (currentStep === 'noop' && receivedData.includes('250')) {
      socket.write('QUIT\r\n');
      setTimeout(() => {
        socket.destroy();
        expect(receivedData).toInclude('250'); // NOOP response
        done.resolve();
      }, 100);
    }
  });
  
  socket.on('error', (error) => {
    done.reject(error);
  });
  
  socket.on('timeout', () => {
    socket.destroy();
    done.reject(new Error(`Connection timeout at step: ${currentStep}`));
  });
  
  await done.promise;
});

// Test: Multiple NOOP commands
tap.test('NOOP - should handle multiple consecutive NOOP commands', async (tools) => {
  const done = tools.defer();
  
  const socket = net.createConnection({
    host: 'localhost',
    port: TEST_PORT,
    timeout: TEST_TIMEOUT
  });
  
  let receivedData = '';
  let currentStep = 'connecting';
  let noopCount = 0;
  const maxNoops = 3;
  
  socket.on('data', (data) => {
    receivedData += data.toString();
    
    if (currentStep === 'connecting' && receivedData.includes('220')) {
      currentStep = 'ehlo';
      receivedData = ''; // Clear buffer after processing
      socket.write('EHLO test.example.com\r\n');
    } else if (currentStep === 'ehlo' && receivedData.includes('250 ')) {
      currentStep = 'noop';
      receivedData = ''; // Clear buffer after processing
      socket.write('NOOP\r\n');
    } else if (currentStep === 'noop' && receivedData.includes('250 OK')) {
      noopCount++;
      receivedData = ''; // Clear buffer after processing
      
      if (noopCount < maxNoops) {
        // Send another NOOP command
        socket.write('NOOP\r\n');
      } else {
        socket.write('QUIT\r\n');
        setTimeout(() => {
          socket.destroy();
          expect(noopCount).toEqual(maxNoops);
          done.resolve();
        }, 100);
      }
    }
  });
  
  socket.on('error', (error) => {
    done.reject(error);
  });
  
  socket.on('timeout', () => {
    socket.destroy();
    done.reject(new Error(`Connection timeout at step: ${currentStep}`));
  });
  
  await done.promise;
});

// Test: NOOP during transaction
tap.test('NOOP - should work during email transaction', async (tools) => {
  const done = tools.defer();
  
  const socket = net.createConnection({
    host: 'localhost',
    port: TEST_PORT,
    timeout: TEST_TIMEOUT
  });
  
  let receivedData = '';
  let currentStep = 'connecting';
  
  socket.on('data', (data) => {
    receivedData += data.toString();
    
    if (currentStep === 'connecting' && receivedData.includes('220')) {
      currentStep = 'ehlo';
      socket.write('EHLO test.example.com\r\n');
    } else if (currentStep === 'ehlo' && receivedData.includes('250')) {
      currentStep = 'mail_from';
      socket.write('MAIL FROM:<sender@example.com>\r\n');
    } else if (currentStep === 'mail_from' && receivedData.includes('250')) {
      currentStep = 'noop_after_mail';
      socket.write('NOOP\r\n');
    } else if (currentStep === 'noop_after_mail' && receivedData.includes('250')) {
      currentStep = 'rcpt_to';
      socket.write('RCPT TO:<recipient@example.com>\r\n');
    } else if (currentStep === 'rcpt_to' && receivedData.includes('250')) {
      currentStep = 'noop_after_rcpt';
      socket.write('NOOP\r\n');
    } else if (currentStep === 'noop_after_rcpt' && receivedData.includes('250')) {
      socket.write('QUIT\r\n');
      setTimeout(() => {
        socket.destroy();
        expect(receivedData).toInclude('250');
        done.resolve();
      }, 100);
    }
  });
  
  socket.on('error', (error) => {
    done.reject(error);
  });
  
  socket.on('timeout', () => {
    socket.destroy();
    done.reject(new Error(`Connection timeout at step: ${currentStep}`));
  });
  
  await done.promise;
});

// Test: NOOP with parameter (should be ignored)
tap.test('NOOP - should handle NOOP with parameters', async (tools) => {
  const done = tools.defer();
  
  const socket = net.createConnection({
    host: 'localhost',
    port: TEST_PORT,
    timeout: TEST_TIMEOUT
  });
  
  let receivedData = '';
  let currentStep = 'connecting';
  
  socket.on('data', (data) => {
    receivedData += data.toString();
    
    if (currentStep === 'connecting' && receivedData.includes('220')) {
      currentStep = 'ehlo';
      socket.write('EHLO test.example.com\r\n');
    } else if (currentStep === 'ehlo' && receivedData.includes('250')) {
      currentStep = 'noop_with_param';
      socket.write('NOOP ignored parameter\r\n'); // Parameters should be ignored
    } else if (currentStep === 'noop_with_param' && receivedData.includes('250')) {
      socket.write('QUIT\r\n');
      setTimeout(() => {
        socket.destroy();
        expect(receivedData).toInclude('250');
        done.resolve();
      }, 100);
    }
  });
  
  socket.on('error', (error) => {
    done.reject(error);
  });
  
  socket.on('timeout', () => {
    socket.destroy();
    done.reject(new Error(`Connection timeout at step: ${currentStep}`));
  });
  
  await done.promise;
});

// Test: NOOP before EHLO/HELO
tap.test('NOOP - should work before EHLO', async (tools) => {
  const done = tools.defer();
  
  const socket = net.createConnection({
    host: 'localhost',
    port: TEST_PORT,
    timeout: TEST_TIMEOUT
  });
  
  let receivedData = '';
  let currentStep = 'connecting';
  
  socket.on('data', (data) => {
    receivedData += data.toString();
    
    if (currentStep === 'connecting' && receivedData.includes('220')) {
      currentStep = 'noop_before_ehlo';
      socket.write('NOOP\r\n');
    } else if (currentStep === 'noop_before_ehlo' && receivedData.includes('250')) {
      currentStep = 'ehlo';
      socket.write('EHLO test.example.com\r\n');
    } else if (currentStep === 'ehlo' && receivedData.includes('250')) {
      socket.write('QUIT\r\n');
      setTimeout(() => {
        socket.destroy();
        expect(receivedData).toInclude('250');
        done.resolve();
      }, 100);
    }
  });
  
  socket.on('error', (error) => {
    done.reject(error);
  });
  
  socket.on('timeout', () => {
    socket.destroy();
    done.reject(new Error(`Connection timeout at step: ${currentStep}`));
  });
  
  await done.promise;
});

// Test: Rapid NOOP commands (stress test)
tap.test('NOOP - should handle rapid NOOP commands', async (tools) => {
  const done = tools.defer();
  
  const socket = net.createConnection({
    host: 'localhost',
    port: TEST_PORT,
    timeout: TEST_TIMEOUT
  });
  
  let receivedData = '';
  let currentStep = 'connecting';
  let noopsSent = 0;
  let noopsReceived = 0;
  const rapidNoops = 10;
  
  socket.on('data', (data) => {
    receivedData += data.toString();
    
    if (currentStep === 'connecting' && receivedData.includes('220')) {
      currentStep = 'ehlo';
      socket.write('EHLO test.example.com\r\n');
    } else if (currentStep === 'ehlo' && receivedData.includes('250')) {
      currentStep = 'rapid_noop';
      // Send multiple NOOPs rapidly
      for (let i = 0; i < rapidNoops; i++) {
        socket.write('NOOP\r\n');
        noopsSent++;
      }
    } else if (currentStep === 'rapid_noop') {
      // Count 250 responses
      const matches = receivedData.match(/250 /g);
      if (matches) {
        noopsReceived = matches.length - 1; // -1 for EHLO response
      }
      
      if (noopsReceived >= rapidNoops) {
        socket.write('QUIT\r\n');
        setTimeout(() => {
          socket.destroy();
          expect(noopsReceived).toBeGreaterThan(rapidNoops - 1);
          done.resolve();
        }, 500);
      }
    }
  });
  
  socket.on('error', (error) => {
    done.reject(error);
  });
  
  socket.on('timeout', () => {
    socket.destroy();
    done.reject(new Error(`Connection timeout at step: ${currentStep}`));
  });
  
  await done.promise;
});

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

export default tap.start();