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

// Test configuration
const TEST_PORT = 2525;

let testServer;
const TEST_TIMEOUT = 10000;

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

// Test: Basic RSET command
tap.test('RSET - should reset transaction after MAIL FROM', 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 = 'rset';
      socket.write('RSET\r\n');
    } else if (currentStep === 'rset' && receivedData.includes('250')) {
      // RSET successful, try to send MAIL FROM again to verify reset
      currentStep = 'mail_from_after_rset';
      socket.write('MAIL FROM:<newsender@example.com>\r\n');
    } else if (currentStep === 'mail_from_after_rset' && receivedData.includes('250')) {
      socket.write('QUIT\r\n');
      setTimeout(() => {
        socket.destroy();
        expect(receivedData).toInclude('250 OK'); // RSET 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: RSET after RCPT TO
tap.test('RSET - should reset transaction after RCPT TO', 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 = 'rcpt_to';
      socket.write('RCPT TO:<recipient@example.com>\r\n');
    } else if (currentStep === 'rcpt_to' && receivedData.includes('250')) {
      currentStep = 'rset';
      socket.write('RSET\r\n');
    } else if (currentStep === 'rset' && receivedData.includes('250')) {
      // After RSET, should need MAIL FROM before RCPT TO
      currentStep = 'rcpt_to_after_rset';
      socket.write('RCPT TO:<newrecipient@example.com>\r\n');
    } else if (currentStep === 'rcpt_to_after_rset' && receivedData.includes('503')) {
      // Should get 503 bad sequence
      socket.write('QUIT\r\n');
      setTimeout(() => {
        socket.destroy();
        expect(receivedData).toInclude('503'); // Bad sequence after RSET
        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: RSET during DATA
tap.test('RSET - should reset transaction during DATA phase', 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 = 'rcpt_to';
      socket.write('RCPT TO:<recipient@example.com>\r\n');
    } else if (currentStep === 'rcpt_to' && receivedData.includes('250')) {
      currentStep = 'data';
      socket.write('DATA\r\n');
    } else if (currentStep === 'data' && receivedData.includes('354')) {
      // Start sending data but then RSET
      currentStep = 'rset_during_data';
      socket.write('Subject: Test\r\n\r\nPartial message...\r\n');
      socket.write('RSET\r\n'); // This should be treated as part of data
      socket.write('\r\n.\r\n'); // End data
    } else if (currentStep === 'rset_during_data' && receivedData.includes('250')) {
      // Message accepted, now send actual RSET
      currentStep = 'rset_after_data';
      socket.write('RSET\r\n');
    } else if (currentStep === 'rset_after_data' && 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: Multiple RSET commands
tap.test('RSET - should handle multiple consecutive RSET 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 rsetCount = 0;
  const maxRsets = 3;
  
  socket.on('data', (data) => {
    receivedData += data.toString();
    
    if (currentStep === 'connecting' && receivedData.includes('220')) {
      currentStep = 'ehlo';
      receivedData = '';
      socket.write('EHLO test.example.com\r\n');
    } else if (currentStep === 'ehlo' && receivedData.includes('250 ')) {
      currentStep = 'mail_from';
      receivedData = '';
      socket.write('MAIL FROM:<sender@example.com>\r\n');
    } else if (currentStep === 'mail_from' && receivedData.includes('250')) {
      currentStep = 'multiple_rsets';
      receivedData = '';
      socket.write('RSET\r\n');
    } else if (currentStep === 'multiple_rsets' && receivedData.includes('250')) {
      rsetCount++;
      receivedData = ''; // Clear buffer after processing
      
      if (rsetCount < maxRsets) {
        socket.write('RSET\r\n');
      } else {
        socket.write('QUIT\r\n');
        setTimeout(() => {
          socket.destroy();
          expect(rsetCount).toEqual(maxRsets);
          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: RSET without transaction
tap.test('RSET - should work without active 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 = 'rset_without_transaction';
      socket.write('RSET\r\n');
    } else if (currentStep === 'rset_without_transaction' && receivedData.includes('250')) {
      socket.write('QUIT\r\n');
      setTimeout(() => {
        socket.destroy();
        expect(receivedData).toInclude('250'); // RSET should work even without transaction
        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: RSET with multiple recipients
tap.test('RSET - should clear all recipients', async (tools) => {
  const done = tools.defer();
  
  const socket = net.createConnection({
    host: 'localhost',
    port: TEST_PORT,
    timeout: TEST_TIMEOUT
  });
  
  let receivedData = '';
  let currentStep = 'connecting';
  let recipientCount = 0;
  
  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 = 'add_recipients';
      recipientCount++;
      socket.write(`RCPT TO:<recipient${recipientCount}@example.com>\r\n`);
    } else if (currentStep === 'add_recipients' && receivedData.includes('250')) {
      if (recipientCount < 3) {
        recipientCount++;
        receivedData = ''; // Clear buffer
        socket.write(`RCPT TO:<recipient${recipientCount}@example.com>\r\n`);
      } else {
        currentStep = 'rset';
        socket.write('RSET\r\n');
      }
    } else if (currentStep === 'rset' && receivedData.includes('250')) {
      // After RSET, all recipients should be cleared
      currentStep = 'data_after_rset';
      socket.write('DATA\r\n');
    } else if (currentStep === 'data_after_rset' && receivedData.includes('503')) {
      // Should get 503 bad sequence (no recipients)
      socket.write('QUIT\r\n');
      setTimeout(() => {
        socket.destroy();
        expect(receivedData).toInclude('503');
        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: RSET with parameter (should be ignored)
tap.test('RSET - should ignore 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 = 'rset_with_param';
      socket.write('RSET ignored parameter\r\n'); // Parameters should be ignored
    } else if (currentStep === 'rset_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;
});

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

// Start the test
export default tap.start();