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

// Test configuration
const TEST_PORT = 30049;
const TEST_TIMEOUT = 15000;

let testServer: ITestServer;

// Setup
tap.test('setup - start SMTP server', async () => {
  testServer = await startTestServer({ port: TEST_PORT, hostname: 'localhost' });
  
  expect(testServer).toBeDefined();
  expect(testServer.port).toEqual(TEST_PORT);
});

// Test: Basic multiple recipients
tap.test('Multiple Recipients - should accept multiple valid 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;
  const recipients = [
    'recipient1@example.com',
    'recipient2@example.com',
    'recipient3@example.com'
  ];
  let acceptedRecipients = 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 = 'rcpt_to';
      socket.write(`RCPT TO:<${recipients[recipientCount]}>\r\n`);
    } else if (currentStep === 'rcpt_to') {
      if (receivedData.includes('250')) {
        acceptedRecipients++;
        recipientCount++;
        
        if (recipientCount < recipients.length) {
          receivedData = ''; // Clear buffer for next response
          socket.write(`RCPT TO:<${recipients[recipientCount]}>\r\n`);
        } else {
          currentStep = 'data';
          socket.write('DATA\r\n');
        }
      }
    } else if (currentStep === 'data' && receivedData.includes('354')) {
      currentStep = 'email_content';
      const emailContent = `Subject: Multiple Recipients Test\r\nFrom: sender@example.com\r\nTo: ${recipients.join(', ')}\r\n\r\nThis email was sent to ${acceptedRecipients} recipients.\r\n`;
      socket.write(emailContent);
      socket.write('\r\n.\r\n');
    } else if (currentStep === 'email_content' && receivedData.includes('250')) {
      socket.write('QUIT\r\n');
      setTimeout(() => {
        socket.destroy();
        expect(acceptedRecipients).toEqual(recipients.length);
        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: Mixed valid and invalid recipients
tap.test('Multiple Recipients - should handle mix of valid and invalid 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 recipientIndex = 0;
  const recipients = [
    'valid@example.com',
    'invalid-email',  // Invalid format
    'another.valid@example.com',
    '@example.com',   // Invalid format
    'third.valid@example.com'
  ];
  const recipientResults: Array<{ email: string, accepted: boolean }> = [];
  
  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:<${recipients[recipientIndex]}>\r\n`);
    } else if (currentStep === 'rcpt_to') {
      const lines = receivedData.split('\r\n');
      const lastLine = lines[lines.length - 2] || lines[lines.length - 1];
      
      if (lastLine.match(/^\d{3}/)) {
        const accepted = lastLine.startsWith('250');
        recipientResults.push({
          email: recipients[recipientIndex],
          accepted: accepted
        });
        
        recipientIndex++;
        
        if (recipientIndex < recipients.length) {
          receivedData = ''; // Clear buffer
          socket.write(`RCPT TO:<${recipients[recipientIndex]}>\r\n`);
        } else {
          const acceptedCount = recipientResults.filter(r => r.accepted).length;
          
          if (acceptedCount > 0) {
            currentStep = 'data';
            socket.write('DATA\r\n');
          } else {
            socket.write('QUIT\r\n');
            setTimeout(() => {
              socket.destroy();
              expect(acceptedCount).toEqual(0);
              done.resolve();
            }, 100);
          }
        }
      }
    } else if (currentStep === 'data' && receivedData.includes('354')) {
      currentStep = 'email_content';
      const acceptedEmails = recipientResults.filter(r => r.accepted).map(r => r.email);
      const emailContent = `Subject: Mixed Recipients Test\r\nFrom: sender@example.com\r\nTo: ${acceptedEmails.join(', ')}\r\n\r\nDelivered to ${acceptedEmails.length} valid recipients.\r\n`;
      socket.write(emailContent);
      socket.write('\r\n.\r\n');
    } else if (currentStep === 'email_content' && receivedData.includes('250')) {
      socket.write('QUIT\r\n');
      setTimeout(() => {
        socket.destroy();
        const acceptedCount = recipientResults.filter(r => r.accepted).length;
        const rejectedCount = recipientResults.filter(r => !r.accepted).length;
        expect(acceptedCount).toEqual(3); // 3 valid recipients
        expect(rejectedCount).toEqual(2); // 2 invalid recipients
        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: Large number of recipients
tap.test('Multiple Recipients - should handle many 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;
  const totalRecipients = 10;
  const recipients: string[] = [];
  for (let i = 1; i <= totalRecipients; i++) {
    recipients.push(`recipient${i}@example.com`);
  }
  let acceptedCount = 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 = 'rcpt_to';
      socket.write(`RCPT TO:<${recipients[recipientCount]}>\r\n`);
    } else if (currentStep === 'rcpt_to') {
      if (receivedData.includes('250')) {
        acceptedCount++;
      }
      
      recipientCount++;
      
      if (recipientCount < recipients.length) {
        receivedData = ''; // Clear buffer
        socket.write(`RCPT TO:<${recipients[recipientCount]}>\r\n`);
      } else {
        currentStep = 'data';
        socket.write('DATA\r\n');
      }
    } else if (currentStep === 'data' && receivedData.includes('354')) {
      currentStep = 'email_content';
      const emailContent = `Subject: Large Recipients Test\r\nFrom: sender@example.com\r\n\r\nSent to ${acceptedCount} recipients.\r\n`;
      socket.write(emailContent);
      socket.write('\r\n.\r\n');
    } else if (currentStep === 'email_content' && receivedData.includes('250')) {
      socket.write('QUIT\r\n');
      setTimeout(() => {
        socket.destroy();
        expect(acceptedCount).toBeGreaterThan(0);
        expect(acceptedCount).toBeLessThan(totalRecipients + 1);
        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: Duplicate recipients
tap.test('Multiple Recipients - should handle duplicate 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;
  const recipients = [
    'duplicate@example.com',
    'unique@example.com',
    'duplicate@example.com',  // Duplicate
    'another@example.com',
    'duplicate@example.com'   // Another duplicate
  ];
  const results: boolean[] = [];
  
  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:<${recipients[recipientCount]}>\r\n`);
    } else if (currentStep === 'rcpt_to') {
      if (receivedData.match(/[245]\d{2}/)) {
        results.push(receivedData.includes('250'));
        recipientCount++;
        
        if (recipientCount < recipients.length) {
          receivedData = ''; // Clear buffer
          socket.write(`RCPT TO:<${recipients[recipientCount]}>\r\n`);
        } else {
          currentStep = 'data';
          socket.write('DATA\r\n');
        }
      }
    } else if (currentStep === 'data' && receivedData.includes('354')) {
      currentStep = 'email_content';
      const emailContent = `Subject: Duplicate Recipients Test\r\nFrom: sender@example.com\r\n\r\nTesting duplicate recipient handling.\r\n`;
      socket.write(emailContent);
      socket.write('\r\n.\r\n');
    } else if (currentStep === 'email_content' && receivedData.includes('250')) {
      socket.write('QUIT\r\n');
      setTimeout(() => {
        socket.destroy();
        expect(results.length).toEqual(recipients.length);
        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: No recipients (should fail DATA)
tap.test('Multiple Recipients - DATA should fail with no recipients', 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')) {
      // Skip RCPT TO, go directly to DATA
      currentStep = 'data_no_recipients';
      socket.write('DATA\r\n');
    } else if (currentStep === 'data_no_recipients') {
      if (receivedData.includes('503')) {
        // Expected: bad sequence error
        socket.write('QUIT\r\n');
        setTimeout(() => {
          socket.destroy();
          expect(receivedData).toInclude('503'); // Bad sequence
          done.resolve();
        }, 100);
      } else if (receivedData.includes('354')) {
        // Some servers accept DATA without recipients and fail later
        // Send empty data to trigger the error
        socket.write('.\r\n');
        currentStep = 'data_sent';
      }
    } else if (currentStep === 'data_sent' && receivedData.match(/[45]\d{2}/)) {
      socket.write('QUIT\r\n');
      setTimeout(() => {
        socket.destroy();
        // Should get an error when trying to send without recipients
        expect(receivedData).toMatch(/[45]\d{2}/);
        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: Recipients with different domains
tap.test('Multiple Recipients - should handle recipients from different domains', 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;
  const recipients = [
    'user1@example.com',
    'user2@test.com',
    'user3@localhost',
    'user4@example.org',
    'user5@subdomain.example.com'
  ];
  let acceptedCount = 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 = 'rcpt_to';
      socket.write(`RCPT TO:<${recipients[recipientCount]}>\r\n`);
    } else if (currentStep === 'rcpt_to') {
      if (receivedData.includes('250')) {
        acceptedCount++;
      }
      
      recipientCount++;
      
      if (recipientCount < recipients.length) {
        receivedData = ''; // Clear buffer
        socket.write(`RCPT TO:<${recipients[recipientCount]}>\r\n`);
      } else {
        if (acceptedCount > 0) {
          currentStep = 'data';
          socket.write('DATA\r\n');
        } else {
          socket.write('QUIT\r\n');
          setTimeout(() => {
            socket.destroy();
            done.resolve();
          }, 100);
        }
      }
    } else if (currentStep === 'data' && receivedData.includes('354')) {
      currentStep = 'email_content';
      const emailContent = `Subject: Multi-domain Test\r\nFrom: sender@example.com\r\n\r\nDelivered to ${acceptedCount} recipients across different domains.\r\n`;
      socket.write(emailContent);
      socket.write('\r\n.\r\n');
    } else if (currentStep === 'email_content' && receivedData.includes('250')) {
      socket.write('QUIT\r\n');
      setTimeout(() => {
        socket.destroy();
        expect(acceptedCount).toBeGreaterThan(0);
        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('teardown - stop SMTP server', async () => {
  if (testServer) {
    await stopTestServer(testServer);
  }
  expect(true).toEqual(true);
});

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