import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as net from 'net';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.ts'
import type { ITestServer } from '../../helpers/server.loader.js';
// Test configuration
const TEST_PORT = 2525;
const TEST_TIMEOUT = 20000;

let testServer: ITestServer;

// Setup
tap.test('setup - start SMTP server', async () => {
  testServer = await startTestServer({ port: TEST_PORT });
  await new Promise(resolve => setTimeout(resolve, 1000));
});

// Test: Invalid email address validation
tap.test('Invalid Email Addresses - should reject various invalid email formats', async (tools) => {
  const done = tools.defer();
  
  const invalidAddresses = [
    'invalid-email',
    '@example.com',
    'user@',
    'user..name@example.com',
    'user@.example.com',
    'user@example..com',
    'user@example.',
    'user name@example.com',
    'user@exam ple.com',
    'user@[invalid]',
    'a'.repeat(65) + '@example.com', // Local part too long
    'user@' + 'a'.repeat(250) + '.com' // Domain too long
  ];
  
  const results: Array<{
    address: string;
    response: string;
    responseCode: string;
    properlyRejected: boolean;
    accepted: boolean;
  }> = [];
  
  const socket = net.createConnection({
    host: 'localhost',
    port: TEST_PORT,
    timeout: TEST_TIMEOUT
  });
  
  let currentIndex = 0;
  let state = 'connecting';
  let buffer = '';
  let lastResponseCode = '';
  const fromAddress = 'test@example.com';
  
  const processNextAddress = () => {
    if (currentIndex < invalidAddresses.length) {
      socket.write(`RCPT TO:<${invalidAddresses[currentIndex]}>\r\n`);
      state = 'rcpt';
    } else {
      socket.write('QUIT\r\n');
      state = 'quit';
    }
  };
  
  socket.on('data', (data) => {
    buffer += data.toString();
    const lines = buffer.split('\r\n');
    
    // Process complete lines
    for (let i = 0; i < lines.length - 1; i++) {
      const line = lines[i];
      if (line.match(/^\d{3}/)) {
        lastResponseCode = line.substring(0, 3);
        
        if (state === 'connecting' && line.startsWith('220')) {
          socket.write('EHLO test.example.com\r\n');
          state = 'ehlo';
        } else if (state === 'ehlo' && line.startsWith('250') && !line.includes('250-')) {
          socket.write(`MAIL FROM:<${fromAddress}>\r\n`);
          state = 'mail';
        } else if (state === 'mail' && line.startsWith('250')) {
          processNextAddress();
        } else if (state === 'rcpt') {
          // Record result
          const rejected = lastResponseCode.startsWith('5') || lastResponseCode.startsWith('4');
          results.push({
            address: invalidAddresses[currentIndex],
            response: line,
            responseCode: lastResponseCode,
            properlyRejected: rejected,
            accepted: lastResponseCode.startsWith('2')
          });
          
          currentIndex++;
          
          if (currentIndex < invalidAddresses.length) {
            // Reset and test next
            socket.write('RSET\r\n');
            state = 'rset';
          } else {
            socket.write('QUIT\r\n');
            state = 'quit';
          }
        } else if (state === 'rset' && line.startsWith('250')) {
          socket.write(`MAIL FROM:<${fromAddress}>\r\n`);
          state = 'mail';
        } else if (state === 'quit' && line.startsWith('221')) {
          socket.destroy();
          
          // Analyze results
          const rejected = results.filter(r => r.properlyRejected).length;
          const rate = results.length > 0 ? rejected / results.length : 0;
          
          // Log results for debugging
          results.forEach(r => {
            if (!r.properlyRejected) {
              console.log(`WARNING: Invalid address accepted: ${r.address}`);
            }
          });
          
          // We expect at least 70% rejection rate for invalid addresses
          expect(rate).toBeGreaterThan(0.7);
          expect(results.length).toEqual(invalidAddresses.length);
          
          done.resolve();
        }
      }
    }
    
    // Keep incomplete line in buffer
    buffer = lines[lines.length - 1];
  });
  
  socket.on('timeout', () => {
    socket.destroy();
    done.reject(new Error('Test timeout'));
  });
  
  socket.on('error', (err) => {
    done.reject(err);
  });
  
  await done.promise;
});

// Test: Edge case email addresses that might be valid
tap.test('Invalid Email Addresses - should handle edge case addresses', async (tools) => {
  const done = tools.defer();
  
  const edgeCaseAddresses = [
    'user+tag@example.com', // Valid - with plus addressing
    'user.name@example.com', // Valid - with dot
    'user@sub.example.com', // Valid - subdomain
    'user@192.168.1.1', // Valid - IP address
    'user@[192.168.1.1]', // Valid - IP in brackets
    '"user name"@example.com', // Valid - quoted local part
    'user\\@name@example.com', // Valid - escaped character
    'user@localhost', // Might be valid depending on server config
  ];
  
  const results: Array<{
    address: string;
    accepted: boolean;
  }> = [];
  
  const socket = net.createConnection({
    host: 'localhost',
    port: TEST_PORT,
    timeout: TEST_TIMEOUT
  });
  
  let currentIndex = 0;
  let state = 'connecting';
  let buffer = '';
  const fromAddress = 'test@example.com';
  
  socket.on('data', (data) => {
    buffer += data.toString();
    const lines = buffer.split('\r\n');
    
    for (let i = 0; i < lines.length - 1; i++) {
      const line = lines[i];
      if (line.match(/^\d{3}/)) {
        const responseCode = line.substring(0, 3);
        
        if (state === 'connecting' && line.startsWith('220')) {
          socket.write('EHLO test.example.com\r\n');
          state = 'ehlo';
        } else if (state === 'ehlo' && line.startsWith('250') && !line.includes('250-')) {
          socket.write(`MAIL FROM:<${fromAddress}>\r\n`);
          state = 'mail';
        } else if (state === 'mail' && line.startsWith('250')) {
          if (currentIndex < edgeCaseAddresses.length) {
            socket.write(`RCPT TO:<${edgeCaseAddresses[currentIndex]}>\r\n`);
            state = 'rcpt';
          } else {
            socket.write('QUIT\r\n');
            state = 'quit';
          }
        } else if (state === 'rcpt') {
          results.push({
            address: edgeCaseAddresses[currentIndex],
            accepted: responseCode.startsWith('2')
          });
          
          currentIndex++;
          
          if (currentIndex < edgeCaseAddresses.length) {
            socket.write('RSET\r\n');
            state = 'rset';
          } else {
            socket.write('QUIT\r\n');
            state = 'quit';
          }
        } else if (state === 'rset' && line.startsWith('250')) {
          socket.write(`MAIL FROM:<${fromAddress}>\r\n`);
          state = 'mail';
        } else if (state === 'quit' && line.startsWith('221')) {
          socket.destroy();
          
          // Just verify we tested all addresses
          expect(results.length).toEqual(edgeCaseAddresses.length);
          
          done.resolve();
        }
      }
    }
    
    buffer = lines[lines.length - 1];
  });
  
  socket.on('timeout', () => {
    socket.destroy();
    done.reject(new Error('Test timeout'));
  });
  
  socket.on('error', (err) => {
    done.reject(err);
  });
  
  await done.promise;
});

// Test: Empty and null addresses
tap.test('Invalid Email Addresses - should handle empty addresses', 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:<test@example.com>\r\n');
    } else if (currentStep === 'mail_from' && receivedData.includes('250')) {
      currentStep = 'rcpt_empty';
      socket.write('RCPT TO:<>\r\n'); // Empty address
    } else if (currentStep === 'rcpt_empty') {
      if (receivedData.includes('250')) {
        // Empty recipient allowed (for bounces)
        currentStep = 'rset';
        socket.write('RSET\r\n');
      } else if (receivedData.match(/[45]\d{2}/)) {
        // Empty recipient rejected
        currentStep = 'rset';
        socket.write('RSET\r\n');
      }
    } else if (currentStep === 'rset' && receivedData.includes('250')) {
      currentStep = 'mail_empty';
      socket.write('MAIL FROM:<>\r\n'); // Empty sender (bounce)
    } else if (currentStep === 'mail_empty' && receivedData.includes('250')) {
      currentStep = 'rcpt_after_empty';
      socket.write('RCPT TO:<recipient@example.com>\r\n');
    } else if (currentStep === 'rcpt_after_empty' && receivedData.includes('250')) {
      socket.write('QUIT\r\n');
      setTimeout(() => {
        socket.destroy();
        // Empty MAIL FROM should be accepted for bounces
        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('teardown - stop SMTP server', async () => {
  await stopTestServer(testServer);
});

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