import { expect, tap } from '@git.zone/tstest/tapbundle';
import { createTestServer } from '../../helpers/server.loader.js';
import { createTestSmtpClient } from '../../helpers/smtp.client.js';
import { Email } from '../../../ts/index.js';

tap.test('CRFC-08: should handle SMTP extensions correctly (Various RFCs)', async (tools) => {
  const testId = 'CRFC-08-smtp-extensions';
  console.log(`\n${testId}: Testing SMTP extensions compliance...`);

  let scenarioCount = 0;

  // Scenario 1: CHUNKING extension (RFC 3030)
  await (async () => {
    scenarioCount++;
    console.log(`\nScenario ${scenarioCount}: Testing CHUNKING extension (RFC 3030)`);
    
    const testServer = await createTestServer({
      onConnection: async (socket) => {
        console.log('  [Server] Client connected');
        socket.write('220 chunking.example.com ESMTP\r\n');
        
        let chunkingMode = false;
        let totalChunks = 0;
        let totalBytes = 0;
        
        socket.on('data', (data) => {
          const text = data.toString();
          
          if (chunkingMode) {
            // In chunking mode, all data is message content
            totalBytes += data.length;
            console.log(`  [Server] Received chunk: ${data.length} bytes`);
            return;
          }
          
          const command = text.trim();
          console.log(`  [Server] Received: ${command}`);
          
          if (command.startsWith('EHLO')) {
            socket.write('250-chunking.example.com\r\n');
            socket.write('250-CHUNKING\r\n');
            socket.write('250-8BITMIME\r\n');
            socket.write('250-BINARYMIME\r\n');
            socket.write('250 OK\r\n');
          } else if (command.startsWith('MAIL FROM:')) {
            if (command.includes('BODY=BINARYMIME')) {
              console.log('  [Server] Binary MIME body declared');
            }
            socket.write('250 OK\r\n');
          } else if (command.startsWith('RCPT TO:')) {
            socket.write('250 OK\r\n');
          } else if (command.startsWith('BDAT ')) {
            // BDAT command format: BDAT <size> [LAST]
            const parts = command.split(' ');
            const chunkSize = parseInt(parts[1]);
            const isLast = parts.includes('LAST');
            
            totalChunks++;
            console.log(`  [Server] BDAT chunk ${totalChunks}: ${chunkSize} bytes${isLast ? ' (LAST)' : ''}`);
            
            if (isLast) {
              socket.write(`250 OK: Message accepted, ${totalChunks} chunks, ${totalBytes} total bytes\r\n`);
              chunkingMode = false;
              totalChunks = 0;
              totalBytes = 0;
            } else {
              socket.write('250 OK: Chunk accepted\r\n');
              chunkingMode = true;
            }
          } else if (command === 'DATA') {
            // DATA not allowed when CHUNKING is available
            socket.write('503 5.5.1 Use BDAT instead of DATA\r\n');
          } else if (command === 'QUIT') {
            socket.write('221 Bye\r\n');
            socket.end();
          }
        });
      }
    });

    const smtpClient = createTestSmtpClient({
      host: testServer.hostname,
      port: testServer.port,
      secure: false
    });

    // Test with binary content that would benefit from chunking
    const binaryContent = Buffer.alloc(1024);
    for (let i = 0; i < binaryContent.length; i++) {
      binaryContent[i] = i % 256;
    }

    const email = new Email({
      from: 'sender@example.com',
      to: ['recipient@example.com'],
      subject: 'CHUNKING test',
      text: 'Testing CHUNKING extension with binary data',
      attachments: [{
        filename: 'binary-data.bin',
        content: binaryContent
      }]
    });

    const result = await smtpClient.sendMail(email);
    console.log('  CHUNKING extension handled (if supported by client)');
    expect(result).toBeDefined();
    expect(result.messageId).toBeDefined();

    await testServer.server.close();
  })();

  // Scenario 2: DELIVERBY extension (RFC 2852)
  await (async () => {
    scenarioCount++;
    console.log(`\nScenario ${scenarioCount}: Testing DELIVERBY extension (RFC 2852)`);
    
    const testServer = await createTestServer({
      onConnection: async (socket) => {
        console.log('  [Server] Client connected');
        socket.write('220 deliverby.example.com ESMTP\r\n');
        
        socket.on('data', (data) => {
          const command = data.toString().trim();
          console.log(`  [Server] Received: ${command}`);
          
          if (command.startsWith('EHLO')) {
            socket.write('250-deliverby.example.com\r\n');
            socket.write('250-DELIVERBY 86400\r\n');  // 24 hours max
            socket.write('250 OK\r\n');
          } else if (command.startsWith('MAIL FROM:')) {
            // Check for DELIVERBY parameter
            const deliverByMatch = command.match(/DELIVERBY=(\d+)([RN]?)/i);
            if (deliverByMatch) {
              const seconds = parseInt(deliverByMatch[1]);
              const mode = deliverByMatch[2] || 'R';  // R=return, N=notify
              
              console.log(`  [Server] DELIVERBY: ${seconds} seconds, mode: ${mode}`);
              
              if (seconds > 86400) {
                socket.write('501 5.5.4 DELIVERBY time exceeds maximum\r\n');
              } else if (seconds < 0) {
                socket.write('501 5.5.4 Invalid DELIVERBY time\r\n');
              } else {
                socket.write('250 OK: Delivery deadline accepted\r\n');
              }
            } else {
              socket.write('250 OK\r\n');
            }
          } else if (command.startsWith('RCPT TO:')) {
            socket.write('250 OK\r\n');
          } else if (command === 'DATA') {
            socket.write('354 Start mail input\r\n');
          } else if (command === '.') {
            socket.write('250 OK: Message queued with delivery deadline\r\n');
          } else if (command === 'QUIT') {
            socket.write('221 Bye\r\n');
            socket.end();
          }
        });
      }
    });

    const smtpClient = createTestSmtpClient({
      host: testServer.hostname,
      port: testServer.port,
      secure: false
    });

    // Test with delivery deadline
    const email = new Email({
      from: 'sender@example.com',
      to: ['urgent@example.com'],
      subject: 'Urgent delivery test',
      text: 'This message has a delivery deadline',
      // Note: Most SMTP clients don't expose DELIVERBY directly
      // but we can test server handling
    });

    const result = await smtpClient.sendMail(email);
    console.log('  DELIVERBY extension supported by server');
    expect(result).toBeDefined();

    await testServer.server.close();
  })();

  // Scenario 3: ETRN extension (RFC 1985)
  await (async () => {
    scenarioCount++;
    console.log(`\nScenario ${scenarioCount}: Testing ETRN extension (RFC 1985)`);
    
    const testServer = await createTestServer({
      onConnection: async (socket) => {
        console.log('  [Server] Client connected');
        socket.write('220 etrn.example.com ESMTP\r\n');
        
        socket.on('data', (data) => {
          const command = data.toString().trim();
          console.log(`  [Server] Received: ${command}`);
          
          if (command.startsWith('EHLO')) {
            socket.write('250-etrn.example.com\r\n');
            socket.write('250-ETRN\r\n');
            socket.write('250 OK\r\n');
          } else if (command.startsWith('ETRN ')) {
            const domain = command.substring(5);
            console.log(`  [Server] ETRN request for domain: ${domain}`);
            
            if (domain === '@example.com') {
              socket.write('250 OK: Queue processing started for example.com\r\n');
            } else if (domain === '#urgent') {
              socket.write('250 OK: Urgent queue processing started\r\n');
            } else if (domain.includes('unknown')) {
              socket.write('458 Unable to queue messages for node\r\n');
            } else {
              socket.write('250 OK: Queue processing started\r\n');
            }
          } else if (command.startsWith('MAIL FROM:')) {
            socket.write('250 OK\r\n');
          } else if (command.startsWith('RCPT TO:')) {
            socket.write('250 OK\r\n');
          } else if (command === 'DATA') {
            socket.write('354 Start mail input\r\n');
          } else if (command === '.') {
            socket.write('250 OK\r\n');
          } else if (command === 'QUIT') {
            socket.write('221 Bye\r\n');
            socket.end();
          }
        });
      }
    });

    // ETRN is typically used by mail servers, not clients
    // We'll test the server's ETRN capability manually
    const net = await import('net');
    const client = net.createConnection(testServer.port, testServer.hostname);
    
    const commands = [
      'EHLO client.example.com',
      'ETRN @example.com',       // Request queue processing for domain
      'ETRN #urgent',            // Request urgent queue processing
      'ETRN unknown.domain.com', // Test error handling
      'QUIT'
    ];
    
    let commandIndex = 0;
    
    client.on('data', (data) => {
      const response = data.toString().trim();
      console.log(`  [Client] Response: ${response}`);
      
      if (commandIndex < commands.length) {
        setTimeout(() => {
          const command = commands[commandIndex];
          console.log(`  [Client] Sending: ${command}`);
          client.write(command + '\r\n');
          commandIndex++;
        }, 100);
      } else {
        client.end();
      }
    });
    
    await new Promise((resolve, reject) => {
      client.on('end', () => {
        console.log('  ETRN extension testing completed');
        resolve(void 0);
      });
      client.on('error', reject);
    });

    await testServer.server.close();
  })();

  // Scenario 4: VRFY and EXPN extensions (RFC 5321)
  await (async () => {
    scenarioCount++;
    console.log(`\nScenario ${scenarioCount}: Testing VRFY and EXPN extensions`);
    
    const testServer = await createTestServer({
      onConnection: async (socket) => {
        console.log('  [Server] Client connected');
        socket.write('220 verify.example.com ESMTP\r\n');
        
        // Simulated user database
        const users = new Map([
          ['admin', { email: 'admin@example.com', fullName: 'Administrator' }],
          ['john', { email: 'john.doe@example.com', fullName: 'John Doe' }],
          ['support', { email: 'support@example.com', fullName: 'Support Team' }]
        ]);
        
        const mailingLists = new Map([
          ['staff', ['admin@example.com', 'john.doe@example.com']],
          ['support-team', ['support@example.com', 'admin@example.com']]
        ]);
        
        socket.on('data', (data) => {
          const command = data.toString().trim();
          console.log(`  [Server] Received: ${command}`);
          
          if (command.startsWith('EHLO')) {
            socket.write('250-verify.example.com\r\n');
            socket.write('250-VRFY\r\n');
            socket.write('250-EXPN\r\n');
            socket.write('250 OK\r\n');
          } else if (command.startsWith('VRFY ')) {
            const query = command.substring(5);
            console.log(`  [Server] VRFY query: ${query}`);
            
            // Look up user
            const user = users.get(query.toLowerCase());
            if (user) {
              socket.write(`250 ${user.fullName} <${user.email}>\r\n`);
            } else {
              // Check if it's an email address
              const emailMatch = Array.from(users.values()).find(u => 
                u.email.toLowerCase() === query.toLowerCase()
              );
              if (emailMatch) {
                socket.write(`250 ${emailMatch.fullName} <${emailMatch.email}>\r\n`);
              } else {
                socket.write('550 5.1.1 User unknown\r\n');
              }
            }
          } else if (command.startsWith('EXPN ')) {
            const listName = command.substring(5);
            console.log(`  [Server] EXPN query: ${listName}`);
            
            const list = mailingLists.get(listName.toLowerCase());
            if (list) {
              socket.write(`250-Mailing list ${listName}:\r\n`);
              list.forEach((email, index) => {
                const prefix = index < list.length - 1 ? '250-' : '250 ';
                socket.write(`${prefix}${email}\r\n`);
              });
            } else {
              socket.write('550 5.1.1 Mailing list not found\r\n');
            }
          } else if (command.startsWith('MAIL FROM:')) {
            socket.write('250 OK\r\n');
          } else if (command.startsWith('RCPT TO:')) {
            socket.write('250 OK\r\n');
          } else if (command === 'DATA') {
            socket.write('354 Start mail input\r\n');
          } else if (command === '.') {
            socket.write('250 OK\r\n');
          } else if (command === 'QUIT') {
            socket.write('221 Bye\r\n');
            socket.end();
          }
        });
      }
    });

    // Test VRFY and EXPN commands
    const net = await import('net');
    const client = net.createConnection(testServer.port, testServer.hostname);
    
    const commands = [
      'EHLO client.example.com',
      'VRFY admin',                    // Verify user by username
      'VRFY john.doe@example.com',     // Verify user by email
      'VRFY nonexistent',              // Test unknown user
      'EXPN staff',                    // Expand mailing list
      'EXPN nonexistent-list',         // Test unknown list
      'QUIT'
    ];
    
    let commandIndex = 0;
    
    client.on('data', (data) => {
      const response = data.toString().trim();
      console.log(`  [Client] Response: ${response}`);
      
      if (commandIndex < commands.length) {
        setTimeout(() => {
          const command = commands[commandIndex];
          console.log(`  [Client] Sending: ${command}`);
          client.write(command + '\r\n');
          commandIndex++;
        }, 200);
      } else {
        client.end();
      }
    });
    
    await new Promise((resolve, reject) => {
      client.on('end', () => {
        console.log('  VRFY and EXPN testing completed');
        resolve(void 0);
      });
      client.on('error', reject);
    });

    await testServer.server.close();
  })();

  // Scenario 5: HELP extension
  await (async () => {
    scenarioCount++;
    console.log(`\nScenario ${scenarioCount}: Testing HELP extension`);
    
    const testServer = await createTestServer({
      onConnection: async (socket) => {
        console.log('  [Server] Client connected');
        socket.write('220 help.example.com ESMTP\r\n');
        
        const helpTopics = new Map([
          ['commands', [
            'Available commands:',
            'EHLO <domain>    - Extended HELLO',
            'MAIL FROM:<addr> - Specify sender',
            'RCPT TO:<addr>   - Specify recipient',
            'DATA             - Start message text',
            'QUIT             - Close connection'
          ]],
          ['extensions', [
            'Supported extensions:',
            'SIZE     - Message size declaration',
            '8BITMIME - 8-bit MIME transport',
            'STARTTLS - Start TLS negotiation',
            'AUTH     - SMTP Authentication',
            'DSN      - Delivery Status Notifications'
          ]],
          ['syntax', [
            'Command syntax:',
            'Commands are case-insensitive',
            'Lines end with CRLF',
            'Email addresses must be in <> brackets',
            'Parameters are space-separated'
          ]]
        ]);
        
        socket.on('data', (data) => {
          const command = data.toString().trim();
          console.log(`  [Server] Received: ${command}`);
          
          if (command.startsWith('EHLO')) {
            socket.write('250-help.example.com\r\n');
            socket.write('250-HELP\r\n');
            socket.write('250 OK\r\n');
          } else if (command === 'HELP' || command === 'HELP HELP') {
            socket.write('214-This server provides HELP for the following topics:\r\n');
            socket.write('214-COMMANDS  - List of available commands\r\n');
            socket.write('214-EXTENSIONS - List of supported extensions\r\n');
            socket.write('214-SYNTAX    - Command syntax rules\r\n');
            socket.write('214 Use HELP <topic> for specific information\r\n');
          } else if (command.startsWith('HELP ')) {
            const topic = command.substring(5).toLowerCase();
            const helpText = helpTopics.get(topic);
            
            if (helpText) {
              helpText.forEach((line, index) => {
                const prefix = index < helpText.length - 1 ? '214-' : '214 ';
                socket.write(`${prefix}${line}\r\n`);
              });
            } else {
              socket.write('504 5.3.0 HELP topic not available\r\n');
            }
          } else if (command.startsWith('MAIL FROM:')) {
            socket.write('250 OK\r\n');
          } else if (command.startsWith('RCPT TO:')) {
            socket.write('250 OK\r\n');
          } else if (command === 'DATA') {
            socket.write('354 Start mail input\r\n');
          } else if (command === '.') {
            socket.write('250 OK\r\n');
          } else if (command === 'QUIT') {
            socket.write('221 Bye\r\n');
            socket.end();
          }
        });
      }
    });

    // Test HELP command
    const net = await import('net');
    const client = net.createConnection(testServer.port, testServer.hostname);
    
    const commands = [
      'EHLO client.example.com',
      'HELP',                          // General help
      'HELP COMMANDS',                 // Specific topic
      'HELP EXTENSIONS',               // Another topic
      'HELP NONEXISTENT',              // Unknown topic
      'QUIT'
    ];
    
    let commandIndex = 0;
    
    client.on('data', (data) => {
      const response = data.toString().trim();
      console.log(`  [Client] Response: ${response}`);
      
      if (commandIndex < commands.length) {
        setTimeout(() => {
          const command = commands[commandIndex];
          console.log(`  [Client] Sending: ${command}`);
          client.write(command + '\r\n');
          commandIndex++;
        }, 200);
      } else {
        client.end();
      }
    });
    
    await new Promise((resolve, reject) => {
      client.on('end', () => {
        console.log('  HELP extension testing completed');
        resolve(void 0);
      });
      client.on('error', reject);
    });

    await testServer.server.close();
  })();

  // Scenario 6: Extension combination and interaction
  await (async () => {
    scenarioCount++;
    console.log(`\nScenario ${scenarioCount}: Testing extension combinations`);
    
    const testServer = await createTestServer({
      onConnection: async (socket) => {
        console.log('  [Server] Client connected');
        socket.write('220 combined.example.com ESMTP\r\n');
        
        let activeExtensions: string[] = [];
        
        socket.on('data', (data) => {
          const command = data.toString().trim();
          console.log(`  [Server] Received: ${command}`);
          
          if (command.startsWith('EHLO')) {
            socket.write('250-combined.example.com\r\n');
            
            // Announce multiple extensions
            const extensions = [
              'SIZE 52428800',
              '8BITMIME',
              'SMTPUTF8',
              'ENHANCEDSTATUSCODES',
              'PIPELINING',
              'DSN',
              'DELIVERBY 86400',
              'CHUNKING',
              'BINARYMIME',
              'HELP'
            ];
            
            extensions.forEach(ext => {
              socket.write(`250-${ext}\r\n`);
              activeExtensions.push(ext.split(' ')[0]);
            });
            
            socket.write('250 OK\r\n');
            console.log(`  [Server] Active extensions: ${activeExtensions.join(', ')}`);
          } else if (command.startsWith('MAIL FROM:')) {
            // Check for multiple extension parameters
            const params = [];
            
            if (command.includes('SIZE=')) {
              const sizeMatch = command.match(/SIZE=(\d+)/);
              if (sizeMatch) params.push(`SIZE=${sizeMatch[1]}`);
            }
            
            if (command.includes('BODY=')) {
              const bodyMatch = command.match(/BODY=(\w+)/);
              if (bodyMatch) params.push(`BODY=${bodyMatch[1]}`);
            }
            
            if (command.includes('SMTPUTF8')) {
              params.push('SMTPUTF8');
            }
            
            if (command.includes('DELIVERBY=')) {
              const deliverByMatch = command.match(/DELIVERBY=(\d+)/);
              if (deliverByMatch) params.push(`DELIVERBY=${deliverByMatch[1]}`);
            }
            
            if (params.length > 0) {
              console.log(`  [Server] Extension parameters: ${params.join(', ')}`);
            }
            
            socket.write('250 2.1.0 Sender OK\r\n');
          } else if (command.startsWith('RCPT TO:')) {
            // Check for DSN parameters
            if (command.includes('NOTIFY=')) {
              const notifyMatch = command.match(/NOTIFY=([^,\s]+)/);
              if (notifyMatch) {
                console.log(`  [Server] DSN NOTIFY: ${notifyMatch[1]}`);
              }
            }
            
            socket.write('250 2.1.5 Recipient OK\r\n');
          } else if (command === 'DATA') {
            if (activeExtensions.includes('CHUNKING')) {
              socket.write('503 5.5.1 Use BDAT when CHUNKING is available\r\n');
            } else {
              socket.write('354 Start mail input\r\n');
            }
          } else if (command.startsWith('BDAT ')) {
            if (activeExtensions.includes('CHUNKING')) {
              const parts = command.split(' ');
              const size = parts[1];
              const isLast = parts.includes('LAST');
              console.log(`  [Server] BDAT chunk: ${size} bytes${isLast ? ' (LAST)' : ''}`);
              
              if (isLast) {
                socket.write('250 2.0.0 Message accepted\r\n');
              } else {
                socket.write('250 2.0.0 Chunk accepted\r\n');
              }
            } else {
              socket.write('500 5.5.1 CHUNKING not available\r\n');
            }
          } else if (command === '.') {
            socket.write('250 2.0.0 Message accepted\r\n');
          } else if (command === 'QUIT') {
            socket.write('221 2.0.0 Bye\r\n');
            socket.end();
          }
        });
      }
    });

    const smtpClient = createTestSmtpClient({
      host: testServer.hostname,
      port: testServer.port,
      secure: false
    });

    // Test email that could use multiple extensions
    const email = new Email({
      from: 'sender@example.com',
      to: ['recipient@example.com'],
      subject: 'Extension combination test with UTF-8: 测试',
      text: 'Testing multiple SMTP extensions together',
      dsn: {
        notify: ['SUCCESS', 'FAILURE'],
        envid: 'multi-ext-test-123'
      }
    });

    const result = await smtpClient.sendMail(email);
    console.log('  Multiple extension combination handled');
    expect(result).toBeDefined();
    expect(result.messageId).toBeDefined();

    await testServer.server.close();
  })();

  console.log(`\n${testId}: All ${scenarioCount} SMTP extension scenarios tested ✓`);
});

tap.start();