import { tap, expect } 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));
});

tap.test('CMD-02: MAIL FROM - accepts valid sender addresses', async (tools) => {
  const done = tools.defer();
  
  const socket = net.createConnection({
    host: 'localhost',
    port: TEST_PORT,
    timeout: TEST_TIMEOUT
  });
  
  let receivedData = '';
  let currentStep = 'connecting';
  let testIndex = 0;
  
  const validAddresses = [
    'sender@example.com',
    'test.user+tag@example.com',
    'user@[192.168.1.1]',  // IP literal
    'user@subdomain.example.com',
    'user@very-long-domain-name-that-is-still-valid.example.com',
    'test_user@example.com'  // underscore in local part
  ];
  
  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 = '';
      console.log(`Testing valid address: ${validAddresses[testIndex]}`);
      socket.write(`MAIL FROM:<${validAddresses[testIndex]}>\r\n`);
    } else if (currentStep === 'mail_from' && receivedData.includes('250')) {
      testIndex++;
      if (testIndex < validAddresses.length) {
        currentStep = 'rset';
        receivedData = '';
        socket.write('RSET\r\n');
      } else {
        socket.write('QUIT\r\n');
        setTimeout(() => {
          socket.destroy();
          done.resolve();
        }, 100);
      }
    } else if (currentStep === 'rset' && receivedData.includes('250')) {
      currentStep = 'mail_from';
      receivedData = '';
      console.log(`Testing valid address: ${validAddresses[testIndex]}`);
      socket.write(`MAIL FROM:<${validAddresses[testIndex]}>\r\n`);
    }
  });
  
  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('CMD-02: MAIL FROM - rejects invalid sender addresses', async (tools) => {
  const done = tools.defer();
  
  const socket = net.createConnection({
    host: 'localhost',
    port: TEST_PORT,
    timeout: TEST_TIMEOUT
  });
  
  let receivedData = '';
  let currentStep = 'connecting';
  let testIndex = 0;
  
  const invalidAddresses = [
    'notanemail',               // No @ symbol
    '@example.com',             // Missing local part
    'user@',                    // Missing domain
    'user@.com',                // Invalid domain
    'user@domain..com',         // Double dot
    'user with spaces@example.com', // Unquoted spaces
    'user@<example.com>',       // Invalid characters
    'user@@example.com',        // Double @
    'user@localhost'            // localhost not valid domain
  ];
  
  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 = '';
      console.log(`Testing invalid address: "${invalidAddresses[testIndex]}"`);
      socket.write(`MAIL FROM:<${invalidAddresses[testIndex]}>\r\n`);
    } else if (currentStep === 'mail_from' && (receivedData.includes('250') || receivedData.includes('5'))) {
      // Server might accept some addresses or reject with 5xx error
      // For this test, we just verify the server responds appropriately
      console.log(`  Response: ${receivedData.trim()}`);
      
      testIndex++;
      if (testIndex < invalidAddresses.length) {
        currentStep = 'rset';
        receivedData = '';
        socket.write('RSET\r\n');
      } else {
        socket.write('QUIT\r\n');
        setTimeout(() => {
          socket.destroy();
          done.resolve();
        }, 100);
      }
    } else if (currentStep === 'rset' && receivedData.includes('250')) {
      currentStep = 'mail_from';
      receivedData = '';
      console.log(`Testing invalid address: "${invalidAddresses[testIndex]}"`);
      socket.write(`MAIL FROM:<${invalidAddresses[testIndex]}>\r\n`);
    }
  });
  
  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('CMD-02: MAIL FROM with SIZE parameter', 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';
      receivedData = '';
      socket.write('EHLO test.example.com\r\n');
    } else if (currentStep === 'ehlo' && receivedData.includes('250 ')) {
      currentStep = 'mail_from_small';
      receivedData = '';
      // Test small size
      socket.write('MAIL FROM:<sender@example.com> SIZE=1024\r\n');
    } else if (currentStep === 'mail_from_small' && receivedData.includes('250')) {
      currentStep = 'rset';
      receivedData = '';
      socket.write('RSET\r\n');
    } else if (currentStep === 'rset' && receivedData.includes('250')) {
      currentStep = 'mail_from_large';
      receivedData = '';
      // Test large size (should be rejected if exceeds limit)
      socket.write('MAIL FROM:<sender@example.com> SIZE=99999999\r\n');
    } else if (currentStep === 'mail_from_large') {
      // Should get either 250 (accepted) or 552 (message size exceeds limit)
      expect(receivedData).toMatch(/^(250|552)/);
      socket.write('QUIT\r\n');
      setTimeout(() => {
        socket.destroy();
        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;
});

tap.test('CMD-02: MAIL FROM 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';
      receivedData = '';
      socket.write('EHLO test.example.com\r\n');
    } else if (currentStep === 'ehlo' && receivedData.includes('250 ')) {
      currentStep = 'mail_from_8bitmime';
      receivedData = '';
      // Test BODY=8BITMIME
      socket.write('MAIL FROM:<sender@example.com> BODY=8BITMIME\r\n');
    } else if (currentStep === 'mail_from_8bitmime' && receivedData.includes('250')) {
      currentStep = 'rset';
      receivedData = '';
      socket.write('RSET\r\n');
    } else if (currentStep === 'rset' && receivedData.includes('250')) {
      currentStep = 'mail_from_unknown';
      receivedData = '';
      // Test unknown parameter (should be ignored or rejected)
      socket.write('MAIL FROM:<sender@example.com> UNKNOWN=value\r\n');
    } else if (currentStep === 'mail_from_unknown') {
      // Should get either 250 (ignored) or 555 (parameter not recognized)
      expect(receivedData).toMatch(/^(250|555|501)/);
      socket.write('QUIT\r\n');
      setTimeout(() => {
        socket.destroy();
        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;
});

tap.test('CMD-02: MAIL FROM sequence violations', 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 = 'mail_without_ehlo';
      receivedData = '';
      // Try MAIL FROM without EHLO/HELO first
      socket.write('MAIL FROM:<sender@example.com>\r\n');
    } else if (currentStep === 'mail_without_ehlo' && receivedData.includes('503')) {
      // Should get 503 (bad sequence)
      currentStep = 'ehlo';
      receivedData = '';
      socket.write('EHLO test.example.com\r\n');
    } else if (currentStep === 'ehlo' && receivedData.includes('250 ')) {
      currentStep = 'first_mail';
      receivedData = '';
      socket.write('MAIL FROM:<first@example.com>\r\n');
    } else if (currentStep === 'first_mail' && receivedData.includes('250')) {
      currentStep = 'second_mail';
      receivedData = '';
      // Try second MAIL FROM without RSET
      socket.write('MAIL FROM:<second@example.com>\r\n');
    } else if (currentStep === 'second_mail' && (receivedData.includes('503') || receivedData.includes('250'))) {
      // Server might accept or reject the second MAIL FROM
      // Some servers allow resetting the sender, others require RSET
      console.log(`Second MAIL FROM response: ${receivedData.trim()}`);
      socket.write('QUIT\r\n');
      setTimeout(() => {
        socket.destroy();
        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;
});

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

export default tap.start();