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 = 30048;
const TEST_TIMEOUT = 60000; // Increased for large email handling

let testServer: ITestServer;

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

// Test: Moderately large email (1MB)
tap.test('Large Email - should handle 1MB email', async (tools) => {
  const done = tools.defer();
  
  const socket = net.createConnection({
    host: 'localhost',
    port: TEST_PORT,
    timeout: TEST_TIMEOUT
  });
  
  let receivedData = '';
  let currentStep = 'connecting';
  let completed = false;
  
  // Generate 1MB of content
  const largeBody = 'X'.repeat(1024 * 1024); // 1MB
  const emailContent = `Subject: 1MB Email Test\r\nFrom: sender@example.com\r\nTo: recipient@example.com\r\n\r\n${largeBody}\r\n`;
  
  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')) {
      currentStep = 'sending_large_email';
      
      // Send in chunks to avoid overwhelming
      const chunkSize = 64 * 1024; // 64KB chunks
      let sent = 0;
      
      const sendChunk = () => {
        if (sent < emailContent.length) {
          const chunk = emailContent.slice(sent, sent + chunkSize);
          socket.write(chunk);
          sent += chunk.length;
          
          // Small delay between chunks
          if (sent < emailContent.length) {
            setTimeout(sendChunk, 10);
          } else {
            // End of data
            socket.write('.\r\n');
            currentStep = 'sent';
          }
        }
      };
      
      sendChunk();
    } else if (currentStep === 'sent' && (receivedData.includes('250') || receivedData.includes('552'))) {
      if (!completed) {
        completed = true;
        socket.write('QUIT\r\n');
        setTimeout(() => {
          socket.destroy();
          // Either accepted (250) or size exceeded (552)
          expect(receivedData).toMatch(/250|552/);
          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 email with MIME attachments
tap.test('Large Email - should handle multi-part MIME message', async (tools) => {
  const done = tools.defer();
  
  const socket = net.createConnection({
    host: 'localhost',
    port: TEST_PORT,
    timeout: TEST_TIMEOUT
  });
  
  let receivedData = '';
  let currentStep = 'connecting';
  let completed = false;
  
  const boundary = '----=_Part_0_123456789';
  const attachment1 = 'A'.repeat(500 * 1024); // 500KB
  const attachment2 = 'B'.repeat(300 * 1024); // 300KB
  
  const emailContent = [
    'Subject: Large MIME Email Test',
    'From: sender@example.com',
    'To: recipient@example.com',
    'MIME-Version: 1.0',
    `Content-Type: multipart/mixed; boundary="${boundary}"`,
    '',
    'This is a multi-part message in MIME format.',
    '',
    `--${boundary}`,
    'Content-Type: text/plain; charset=utf-8',
    '',
    'This email contains large attachments.',
    '',
    `--${boundary}`,
    'Content-Type: text/plain; charset=utf-8',
    'Content-Disposition: attachment; filename="file1.txt"',
    '',
    attachment1,
    '',
    `--${boundary}`,
    'Content-Type: application/octet-stream',
    'Content-Disposition: attachment; filename="file2.bin"',
    'Content-Transfer-Encoding: base64',
    '',
    Buffer.from(attachment2).toString('base64'),
    '',
    `--${boundary}--`,
    ''
  ].join('\r\n');
  
  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')) {
      currentStep = 'sending_mime';
      socket.write(emailContent);
      socket.write('\r\n.\r\n');
      currentStep = 'sent';
    } else if (currentStep === 'sent' && (receivedData.includes('250') || receivedData.includes('552'))) {
      if (!completed) {
        completed = true;
        socket.write('QUIT\r\n');
        setTimeout(() => {
          socket.destroy();
          expect(receivedData).toMatch(/250|552/);
          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: Email size limits with SIZE extension
tap.test('Large Email - should respect SIZE limits if advertised', async (tools) => {
  const done = tools.defer();
  
  const socket = net.createConnection({
    host: 'localhost',
    port: TEST_PORT,
    timeout: TEST_TIMEOUT
  });
  
  let receivedData = '';
  let currentStep = 'connecting';
  let maxSize: number | null = null;
  
  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')) {
      // Check for SIZE extension
      const sizeMatch = receivedData.match(/SIZE\s+(\d+)/);
      if (sizeMatch) {
        maxSize = parseInt(sizeMatch[1]);
        console.log(`Server advertises max size: ${maxSize} bytes`);
      }
      
      currentStep = 'mail_from';
      const emailSize = maxSize ? maxSize + 1000 : 5000000; // Over limit or 5MB
      socket.write(`MAIL FROM:<sender@example.com> SIZE=${emailSize}\r\n`);
    } else if (currentStep === 'mail_from') {
      if (maxSize && receivedData.includes('552')) {
        // Size rejected - expected
        socket.write('QUIT\r\n');
        setTimeout(() => {
          socket.destroy();
          expect(receivedData).toInclude('552');
          done.resolve();
        }, 100);
      } else if (receivedData.includes('250')) {
        // Size accepted or no limit
        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;
});

// Test: Very large email handling (5MB)
tap.test('Large Email - should handle or reject very large emails gracefully', async (tools) => {
  const done = tools.defer();
  
  const socket = net.createConnection({
    host: 'localhost',
    port: TEST_PORT,
    timeout: TEST_TIMEOUT
  });
  
  let receivedData = '';
  let currentStep = 'connecting';
  let completed = false;
  
  // Generate 5MB email
  const largeContent = 'X'.repeat(5 * 1024 * 1024); // 5MB
  const emailContent = `Subject: 5MB Email Test\r\nFrom: sender@example.com\r\nTo: recipient@example.com\r\n\r\n${largeContent}\r\n`;
  
  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')) {
      currentStep = 'sending_5mb';
      
      console.log('Sending 5MB email...');
      
      // Send in larger chunks for efficiency
      const chunkSize = 256 * 1024; // 256KB chunks
      let sent = 0;
      
      const sendChunk = () => {
        if (sent < emailContent.length) {
          const chunk = emailContent.slice(sent, sent + chunkSize);
          socket.write(chunk);
          sent += chunk.length;
          
          if (sent < emailContent.length) {
            setImmediate(sendChunk); // Use setImmediate for better performance
          } else {
            socket.write('.\r\n');
            currentStep = 'sent';
          }
        }
      };
      
      sendChunk();
    } else if (currentStep === 'sent' && receivedData.match(/[245]\d{2}/)) {
      if (!completed) {
        completed = true;
        // Extract the last response code
        const lines = receivedData.split('\r\n');
        let responseCode = '';
        
        // Look for the most recent response code
        for (let i = lines.length - 1; i >= 0; i--) {
          const match = lines[i].match(/^([245]\d{2})[\s-]/);
          if (match) {
            responseCode = match[1];
            break;
          }
        }
        
        // If we couldn't extract, but we know there's a response, default to 250
        if (!responseCode && receivedData.includes('250 OK message queued')) {
          responseCode = '250';
        }
        
        socket.write('QUIT\r\n');
        setTimeout(() => {
          socket.destroy();
          // Accept various responses: 250 (accepted), 552 (size exceeded), 554 (failed)
          expect(responseCode).toMatch(/^(250|552|554|451|452)$/);
          done.resolve();
        }, 100);
      }
    }
  });
  
  socket.on('error', (error) => {
    // Connection errors during large transfers are acceptable
    if (currentStep === 'sending_5mb' || currentStep === 'sent') {
      done.resolve();
    } else {
      done.reject(error);
    }
  });
  
  socket.on('timeout', () => {
    socket.destroy();
    done.reject(new Error(`Connection timeout at step: ${currentStep}`));
  });
  
  await done.promise;
});

// Test: Chunked transfer handling
tap.test('Large Email - should handle chunked transfers properly', async (tools) => {
  const done = tools.defer();
  
  const socket = net.createConnection({
    host: 'localhost',
    port: TEST_PORT,
    timeout: TEST_TIMEOUT
  });
  
  let receivedData = '';
  let currentStep = 'connecting';
  let chunksSent = 0;
  let completed = false;
  
  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')) {
      currentStep = 'chunked_sending';
      
      // Send headers
      socket.write('Subject: Chunked Transfer Test\r\n');
      socket.write('From: sender@example.com\r\n');
      socket.write('To: recipient@example.com\r\n');
      socket.write('\r\n');
      
      // Send body in multiple chunks with delays
      const chunks = [
        'First chunk of data\r\n',
        'Second chunk of data\r\n',
        'Third chunk of data\r\n',
        'Fourth chunk of data\r\n',
        'Final chunk of data\r\n'
      ];
      
      const sendNextChunk = () => {
        if (chunksSent < chunks.length) {
          socket.write(chunks[chunksSent]);
          chunksSent++;
          setTimeout(sendNextChunk, 100); // 100ms delay between chunks
        } else {
          socket.write('.\r\n');
        }
      };
      
      sendNextChunk();
    } else if (currentStep === 'chunked_sending' && receivedData.includes('250')) {
      if (!completed) {
        completed = true;
        socket.write('QUIT\r\n');
        setTimeout(() => {
          socket.destroy();
          expect(chunksSent).toEqual(5);
          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: Email with very long lines
tap.test('Large Email - should handle emails with very long lines', async (tools) => {
  const done = tools.defer();
  
  const socket = net.createConnection({
    host: 'localhost',
    port: TEST_PORT,
    timeout: TEST_TIMEOUT
  });
  
  let receivedData = '';
  let currentStep = 'connecting';
  let completed = false;
  
  // Create a very long line (10KB)
  const veryLongLine = 'A'.repeat(10 * 1024);
  const emailContent = `Subject: Long Line Test\r\nFrom: sender@example.com\r\nTo: recipient@example.com\r\n\r\n${veryLongLine}\r\nNormal line after long line.\r\n`;
  
  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')) {
      currentStep = 'long_line';
      socket.write(emailContent);
      socket.write('.\r\n');
      currentStep = 'sent';
    } else if (currentStep === 'sent') {
      // Extract the last response code from the received data
      // Look for response codes that are at the beginning of a line
      const responseMatches = receivedData.split('\r\n').filter(line => /^\d{3}\s/.test(line));
      const lastResponseLine = responseMatches[responseMatches.length - 1];
      const responseCode = lastResponseLine?.match(/^(\d{3})/)?.[1];
      if (responseCode && !completed) {
        completed = true;
        socket.write('QUIT\r\n');
        setTimeout(() => {
          socket.destroy();
          // May accept or reject based on line length limits
          expect(responseCode).toMatch(/^(250|500|501|552)$/);
          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();