import { tap, expect } from '@git.zone/tstest/tapbundle';
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js';
import { connectToSmtp, waitForGreeting, sendSmtpCommand, closeSmtpConnection, generateRandomEmail } from '../../helpers/utils.js';

let testServer: ITestServer;

tap.test('setup - start SMTP server with large size limit', async () => {
  testServer = await startTestServer({
    port: 2532,
    hostname: 'localhost',
    size: 100 * 1024 * 1024 // 100MB limit for testing
  });
  expect(testServer).toBeInstanceOf(Object);
});

tap.test('EDGE-01: Very Large Email - test size limits and handling', async () => {
  const testCases = [
    { size: 1 * 1024 * 1024, label: '1MB', shouldPass: true },
    { size: 10 * 1024 * 1024, label: '10MB', shouldPass: true },
    { size: 50 * 1024 * 1024, label: '50MB', shouldPass: true },
    { size: 101 * 1024 * 1024, label: '101MB', shouldPass: false } // Over limit
  ];
  
  for (const testCase of testCases) {
    console.log(`\n📧 Testing ${testCase.label} email...`);
    const socket = await connectToSmtp(testServer.hostname, testServer.port);
    
    try {
      await waitForGreeting(socket);
      await sendSmtpCommand(socket, 'EHLO test.example.com', '250');
      
      // Check SIZE extension
      await sendSmtpCommand(socket, `MAIL FROM:<large@example.com> SIZE=${testCase.size}`, 
        testCase.shouldPass ? '250' : '552');
      
      if (testCase.shouldPass) {
        // Continue with transaction
        await sendSmtpCommand(socket, 'RCPT TO:<recipient@example.com>', '250');
        await sendSmtpCommand(socket, 'DATA', '354');
        
        // Send large content in chunks
        const chunkSize = 65536; // 64KB chunks
        const totalChunks = Math.ceil(testCase.size / chunkSize);
        
        console.log(`   Sending ${totalChunks} chunks...`);
        
        // Headers
        socket.write('From: large@example.com\r\n');
        socket.write('To: recipient@example.com\r\n');
        socket.write(`Subject: ${testCase.label} Test Email\r\n`);
        socket.write('Content-Type: text/plain\r\n');
        socket.write('\r\n');
        
        // Body in chunks
        let bytesSent = 100; // Approximate header size
        const startTime = Date.now();
        
        for (let i = 0; i < totalChunks; i++) {
          const chunk = generateRandomEmail(Math.min(chunkSize, testCase.size - bytesSent));
          socket.write(chunk);
          bytesSent += chunk.length;
          
          // Progress indicator every 10%
          if (i % Math.floor(totalChunks / 10) === 0) {
            const progress = (i / totalChunks * 100).toFixed(0);
            console.log(`   Progress: ${progress}%`);
          }
          
          // Small delay to avoid overwhelming
          if (i % 100 === 0) {
            await new Promise(resolve => setTimeout(resolve, 10));
          }
        }
        
        // End of data
        socket.write('\r\n.\r\n');
        
        // Wait for response with longer timeout for large emails
        const response = await new Promise<string>((resolve, reject) => {
          let buffer = '';
          const timeout = setTimeout(() => reject(new Error('Timeout')), 60000);
          
          const onData = (data: Buffer) => {
            buffer += data.toString();
            if (buffer.includes('250') || buffer.includes('5')) {
              clearTimeout(timeout);
              socket.removeListener('data', onData);
              resolve(buffer);
            }
          };
          
          socket.on('data', onData);
        });
        
        const duration = Date.now() - startTime;
        const throughputMBps = (testCase.size / 1024 / 1024) / (duration / 1000);
        
        expect(response).toInclude('250');
        console.log(`   ✅ ${testCase.label} email accepted in ${duration}ms`);
        console.log(`   Throughput: ${throughputMBps.toFixed(2)} MB/s`);
        
      } else {
        console.log(`   ✅ ${testCase.label} email properly rejected (over size limit)`);
      }
      
    } catch (error) {
      if (!testCase.shouldPass && error.message.includes('552')) {
        console.log(`   ✅ ${testCase.label} email properly rejected: ${error.message}`);
      } else {
        throw error;
      }
    } finally {
      await closeSmtpConnection(socket).catch(() => {});
    }
  }
});

tap.test('EDGE-01: Email size enforcement - SIZE parameter', async () => {
  const socket = await connectToSmtp(testServer.hostname, testServer.port);
  
  try {
    await waitForGreeting(socket);
    const ehloResponse = await sendSmtpCommand(socket, 'EHLO test.example.com', '250');
    
    // Extract SIZE limit from capabilities
    const sizeMatch = ehloResponse.match(/250[- ]SIZE (\d+)/);
    const sizeLimit = sizeMatch ? parseInt(sizeMatch[1]) : 0;
    
    console.log(`📏 Server advertises SIZE limit: ${sizeLimit} bytes`);
    expect(sizeLimit).toBeGreaterThan(0);
    
    // Test SIZE parameter enforcement
    const testSizes = [
      { size: 1000, shouldPass: true },
      { size: sizeLimit - 1000, shouldPass: true },
      { size: sizeLimit + 1000, shouldPass: false }
    ];
    
    for (const test of testSizes) {
      try {
        const response = await sendSmtpCommand(
          socket, 
          `MAIL FROM:<test@example.com> SIZE=${test.size}`
        );
        
        if (test.shouldPass) {
          expect(response).toInclude('250');
          console.log(`   ✅ SIZE=${test.size} accepted`);
          await sendSmtpCommand(socket, 'RSET', '250');
        } else {
          expect(response).toInclude('552');
          console.log(`   ✅ SIZE=${test.size} rejected`);
        }
      } catch (error) {
        if (!test.shouldPass) {
          console.log(`   ✅ SIZE=${test.size} rejected: ${error.message}`);
        } else {
          throw error;
        }
      }
    }
    
  } finally {
    await closeSmtpConnection(socket);
  }
});

tap.test('EDGE-01: Memory efficiency with large emails', async () => {
  // Get initial memory usage
  const initialMemory = process.memoryUsage();
  console.log('📊 Initial memory usage:', {
    heapUsed: `${(initialMemory.heapUsed / 1024 / 1024).toFixed(2)} MB`,
    rss: `${(initialMemory.rss / 1024 / 1024).toFixed(2)} MB`
  });
  
  // Send a moderately large email
  const socket = await connectToSmtp(testServer.hostname, testServer.port);
  
  try {
    await waitForGreeting(socket);
    await sendSmtpCommand(socket, 'EHLO test.example.com', '250');
    await sendSmtpCommand(socket, 'MAIL FROM:<memory@test.com>', '250');
    await sendSmtpCommand(socket, 'RCPT TO:<recipient@example.com>', '250');
    await sendSmtpCommand(socket, 'DATA', '354');
    
    // Send 20MB email
    const size = 20 * 1024 * 1024;
    const chunkSize = 1024 * 1024; // 1MB chunks
    
    socket.write('From: memory@test.com\r\n');
    socket.write('To: recipient@example.com\r\n');
    socket.write('Subject: Memory Test\r\n\r\n');
    
    for (let i = 0; i < size / chunkSize; i++) {
      socket.write(generateRandomEmail(chunkSize));
      // Force garbage collection if available
      if (global.gc) {
        global.gc();
      }
    }
    
    socket.write('\r\n.\r\n');
    
    // Wait for response
    await new Promise<void>((resolve) => {
      const onData = (data: Buffer) => {
        if (data.toString().includes('250')) {
          socket.removeListener('data', onData);
          resolve();
        }
      };
      socket.on('data', onData);
    });
    
    // Check memory after processing
    const finalMemory = process.memoryUsage();
    const memoryIncrease = (finalMemory.heapUsed - initialMemory.heapUsed) / 1024 / 1024;
    
    console.log('📊 Final memory usage:', {
      heapUsed: `${(finalMemory.heapUsed / 1024 / 1024).toFixed(2)} MB`,
      rss: `${(finalMemory.rss / 1024 / 1024).toFixed(2)} MB`,
      increase: `${memoryIncrease.toFixed(2)} MB`
    });
    
    // Memory increase should be reasonable (not storing entire email in memory)
    expect(memoryIncrease).toBeLessThan(50); // Less than 50MB increase for 20MB email
    console.log('✅ Memory efficiency test passed');
    
  } finally {
    await closeSmtpConnection(socket);
  }
});

tap.test('cleanup - stop SMTP server', async () => {
  await stopTestServer(testServer);
  console.log('✅ Test server stopped');
});

export default tap.start();