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-06: should handle protocol negotiation correctly (RFC 5321)', async (tools) => {
  const testId = 'CRFC-06-protocol-negotiation';
  console.log(`\n${testId}: Testing SMTP protocol negotiation compliance...`);

  let scenarioCount = 0;

  // Scenario 1: EHLO capability announcement and selection
  await (async () => {
    scenarioCount++;
    console.log(`\nScenario ${scenarioCount}: Testing EHLO capability announcement`);
    
    const testServer = await createTestServer({
      onConnection: async (socket) => {
        console.log('  [Server] Client connected');
        socket.write('220 negotiation.example.com ESMTP Service Ready\r\n');
        
        let negotiatedCapabilities: string[] = [];
        
        socket.on('data', (data) => {
          const command = data.toString().trim();
          console.log(`  [Server] Received: ${command}`);
          
          if (command.startsWith('EHLO')) {
            // Announce available capabilities
            socket.write('250-negotiation.example.com\r\n');
            socket.write('250-SIZE 52428800\r\n');
            socket.write('250-8BITMIME\r\n');
            socket.write('250-STARTTLS\r\n');
            socket.write('250-ENHANCEDSTATUSCODES\r\n');
            socket.write('250-PIPELINING\r\n');
            socket.write('250-CHUNKING\r\n');
            socket.write('250-SMTPUTF8\r\n');
            socket.write('250-DSN\r\n');
            socket.write('250-AUTH PLAIN LOGIN CRAM-MD5\r\n');
            socket.write('250 HELP\r\n');
            
            negotiatedCapabilities = [
              'SIZE', '8BITMIME', 'STARTTLS', 'ENHANCEDSTATUSCODES',
              'PIPELINING', 'CHUNKING', 'SMTPUTF8', 'DSN', 'AUTH', 'HELP'
            ];
            console.log(`  [Server] Announced capabilities: ${negotiatedCapabilities.join(', ')}`);
          } else if (command.startsWith('HELO')) {
            // Basic SMTP mode - no capabilities
            socket.write('250 negotiation.example.com\r\n');
            negotiatedCapabilities = [];
            console.log('  [Server] Basic SMTP mode (no capabilities)');
          } else if (command.startsWith('MAIL FROM:')) {
            // Check for SIZE parameter
            const sizeMatch = command.match(/SIZE=(\d+)/i);
            if (sizeMatch && negotiatedCapabilities.includes('SIZE')) {
              const size = parseInt(sizeMatch[1]);
              console.log(`  [Server] SIZE parameter used: ${size} bytes`);
              if (size > 52428800) {
                socket.write('552 5.3.4 Message size exceeds maximum\r\n');
              } else {
                socket.write('250 2.1.0 Sender OK\r\n');
              }
            } else if (sizeMatch && !negotiatedCapabilities.includes('SIZE')) {
              console.log('  [Server] SIZE parameter used without capability');
              socket.write('501 5.5.4 SIZE not supported\r\n');
            } else {
              socket.write('250 2.1.0 Sender OK\r\n');
            }
          } else if (command.startsWith('RCPT TO:')) {
            // Check for DSN parameters
            if (command.includes('NOTIFY=') && negotiatedCapabilities.includes('DSN')) {
              console.log('  [Server] DSN NOTIFY parameter used');
            } else if (command.includes('NOTIFY=') && !negotiatedCapabilities.includes('DSN')) {
              console.log('  [Server] DSN parameter used without capability');
              socket.write('501 5.5.4 DSN not supported\r\n');
              return;
            }
            socket.write('250 2.1.5 Recipient OK\r\n');
          } else if (command === 'DATA') {
            socket.write('354 Start mail input\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();
          }
        });
      }
    });

    // Test EHLO negotiation
    const esmtpClient = createTestSmtpClient({
      host: testServer.hostname,
      port: testServer.port,
      secure: false
    });

    const email = new Email({
      from: 'sender@example.com',
      to: ['recipient@example.com'],
      subject: 'Capability negotiation test',
      text: 'Testing EHLO capability announcement and usage'
    });

    const result = await esmtpClient.sendMail(email);
    console.log('  EHLO capability negotiation successful');
    expect(result).toBeDefined();

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

  // Scenario 2: Capability-based feature usage
  await (async () => {
    scenarioCount++;
    console.log(`\nScenario ${scenarioCount}: Testing capability-based feature usage`);
    
    const testServer = await createTestServer({
      onConnection: async (socket) => {
        console.log('  [Server] Client connected');
        socket.write('220 features.example.com ESMTP\r\n');
        
        let supportsUTF8 = false;
        let supportsPipelining = false;
        
        socket.on('data', (data) => {
          const command = data.toString().trim();
          console.log(`  [Server] Received: ${command}`);
          
          if (command.startsWith('EHLO')) {
            socket.write('250-features.example.com\r\n');
            socket.write('250-SMTPUTF8\r\n');
            socket.write('250-PIPELINING\r\n');
            socket.write('250-8BITMIME\r\n');
            socket.write('250 SIZE 10485760\r\n');
            
            supportsUTF8 = true;
            supportsPipelining = true;
            console.log('  [Server] UTF8 and PIPELINING capabilities announced');
          } else if (command.startsWith('MAIL FROM:')) {
            // Check for SMTPUTF8 parameter
            if (command.includes('SMTPUTF8') && supportsUTF8) {
              console.log('  [Server] SMTPUTF8 parameter accepted');
              socket.write('250 OK\r\n');
            } else if (command.includes('SMTPUTF8') && !supportsUTF8) {
              console.log('  [Server] SMTPUTF8 used without capability');
              socket.write('555 5.6.7 SMTPUTF8 not supported\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\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 UTF-8 content
    const utf8Email = new Email({
      from: 'sénder@example.com',  // Non-ASCII sender
      to: ['recipient@example.com'],
      subject: 'UTF-8 test: café, naïve, 你好',
      text: 'Testing SMTPUTF8 capability with international characters: émojis 🎉'
    });

    const result = await smtpClient.sendMail(utf8Email);
    console.log('  UTF-8 email sent using SMTPUTF8 capability');
    expect(result).toBeDefined();

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

  // Scenario 3: Extension parameter validation
  await (async () => {
    scenarioCount++;
    console.log(`\nScenario ${scenarioCount}: Testing extension parameter validation`);
    
    const testServer = await createTestServer({
      onConnection: async (socket) => {
        console.log('  [Server] Client connected');
        socket.write('220 validation.example.com ESMTP\r\n');
        
        const supportedExtensions = new Set(['SIZE', 'BODY', 'DSN', '8BITMIME']);
        
        socket.on('data', (data) => {
          const command = data.toString().trim();
          console.log(`  [Server] Received: ${command}`);
          
          if (command.startsWith('EHLO')) {
            socket.write('250-validation.example.com\r\n');
            socket.write('250-SIZE 5242880\r\n');
            socket.write('250-8BITMIME\r\n');
            socket.write('250-DSN\r\n');
            socket.write('250 OK\r\n');
          } else if (command.startsWith('MAIL FROM:')) {
            // Validate all ESMTP parameters
            const params = command.substring(command.indexOf('>') + 1).trim();
            if (params) {
              console.log(`  [Server] Validating parameters: ${params}`);
              
              const paramPairs = params.split(/\s+/).filter(p => p.length > 0);
              let allValid = true;
              
              for (const param of paramPairs) {
                const [key, value] = param.split('=');
                
                if (key === 'SIZE') {
                  const size = parseInt(value || '0');
                  if (isNaN(size) || size < 0) {
                    socket.write('501 5.5.4 Invalid SIZE value\r\n');
                    allValid = false;
                    break;
                  } else if (size > 5242880) {
                    socket.write('552 5.3.4 Message size exceeds limit\r\n');
                    allValid = false;
                    break;
                  }
                  console.log(`  [Server] SIZE=${size} validated`);
                } else if (key === 'BODY') {
                  if (value !== '7BIT' && value !== '8BITMIME') {
                    socket.write('501 5.5.4 Invalid BODY value\r\n');
                    allValid = false;
                    break;
                  }
                  console.log(`  [Server] BODY=${value} validated`);
                } else if (key === 'RET') {
                  if (value !== 'FULL' && value !== 'HDRS') {
                    socket.write('501 5.5.4 Invalid RET value\r\n');
                    allValid = false;
                    break;
                  }
                  console.log(`  [Server] RET=${value} validated`);
                } else if (key === 'ENVID') {
                  // ENVID can be any string, just check format
                  if (!value) {
                    socket.write('501 5.5.4 ENVID requires value\r\n');
                    allValid = false;
                    break;
                  }
                  console.log(`  [Server] ENVID=${value} validated`);
                } else {
                  console.log(`  [Server] Unknown parameter: ${key}`);
                  socket.write(`555 5.5.4 Unsupported parameter: ${key}\r\n`);
                  allValid = false;
                  break;
                }
              }
              
              if (allValid) {
                socket.write('250 OK\r\n');
              }
            } else {
              socket.write('250 OK\r\n');
            }
          } else if (command.startsWith('RCPT TO:')) {
            // Validate DSN parameters
            const params = command.substring(command.indexOf('>') + 1).trim();
            if (params) {
              const paramPairs = params.split(/\s+/).filter(p => p.length > 0);
              let allValid = true;
              
              for (const param of paramPairs) {
                const [key, value] = param.split('=');
                
                if (key === 'NOTIFY') {
                  const notifyValues = value.split(',');
                  const validNotify = ['NEVER', 'SUCCESS', 'FAILURE', 'DELAY'];
                  
                  for (const nv of notifyValues) {
                    if (!validNotify.includes(nv)) {
                      socket.write('501 5.5.4 Invalid NOTIFY value\r\n');
                      allValid = false;
                      break;
                    }
                  }
                  
                  if (allValid) {
                    console.log(`  [Server] NOTIFY=${value} validated`);
                  }
                } else if (key === 'ORCPT') {
                  // ORCPT format: addr-type;addr-value
                  if (!value.includes(';')) {
                    socket.write('501 5.5.4 Invalid ORCPT format\r\n');
                    allValid = false;
                    break;
                  }
                  console.log(`  [Server] ORCPT=${value} validated`);
                } else {
                  socket.write(`555 5.5.4 Unsupported RCPT parameter: ${key}\r\n`);
                  allValid = false;
                  break;
                }
              }
              
              if (allValid) {
                socket.write('250 OK\r\n');
              }
            } else {
              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();
          }
        });
      }
    });

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

    // Test with various valid parameters
    const email = new Email({
      from: 'sender@example.com',
      to: ['recipient@example.com'],
      subject: 'Parameter validation test',
      text: 'Testing ESMTP parameter validation',
      dsn: {
        notify: ['SUCCESS', 'FAILURE'],
        envid: 'test-envelope-id-123',
        ret: 'FULL'
      }
    });

    const result = await smtpClient.sendMail(email);
    console.log('  ESMTP parameter validation successful');
    expect(result).toBeDefined();

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

  // Scenario 4: Service extension discovery
  await (async () => {
    scenarioCount++;
    console.log(`\nScenario ${scenarioCount}: Testing service extension discovery`);
    
    const testServer = await createTestServer({
      onConnection: async (socket) => {
        console.log('  [Server] Client connected');
        socket.write('220 discovery.example.com ESMTP Ready\r\n');
        
        let clientName = '';
        
        socket.on('data', (data) => {
          const command = data.toString().trim();
          console.log(`  [Server] Received: ${command}`);
          
          if (command.startsWith('EHLO ')) {
            clientName = command.substring(5);
            console.log(`  [Server] Client identified as: ${clientName}`);
            
            // Announce extensions in order of preference
            socket.write('250-discovery.example.com\r\n');
            
            // Security extensions first
            socket.write('250-STARTTLS\r\n');
            socket.write('250-AUTH PLAIN LOGIN CRAM-MD5 DIGEST-MD5\r\n');
            
            // Core functionality extensions
            socket.write('250-SIZE 104857600\r\n');
            socket.write('250-8BITMIME\r\n');
            socket.write('250-SMTPUTF8\r\n');
            
            // Delivery extensions
            socket.write('250-DSN\r\n');
            socket.write('250-DELIVERBY 86400\r\n');
            
            // Performance extensions
            socket.write('250-PIPELINING\r\n');
            socket.write('250-CHUNKING\r\n');
            socket.write('250-BINARYMIME\r\n');
            
            // Enhanced status and debugging
            socket.write('250-ENHANCEDSTATUSCODES\r\n');
            socket.write('250-NO-SOLICITING\r\n');
            socket.write('250-MTRK\r\n');
            
            // End with help
            socket.write('250 HELP\r\n');
          } else if (command.startsWith('HELO ')) {
            clientName = command.substring(5);
            console.log(`  [Server] Basic SMTP client: ${clientName}`);
            socket.write('250 discovery.example.com\r\n');
          } else if (command.startsWith('MAIL FROM:')) {
            // Client should use discovered capabilities appropriately
            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 === 'HELP') {
            // Detailed help for discovered extensions
            socket.write('214-This server supports the following features:\r\n');
            socket.write('214-STARTTLS - Start TLS negotiation\r\n');
            socket.write('214-AUTH - SMTP Authentication\r\n');
            socket.write('214-SIZE - Message size declaration\r\n');
            socket.write('214-8BITMIME - 8-bit MIME transport\r\n');
            socket.write('214-SMTPUTF8 - UTF-8 support\r\n');
            socket.write('214-DSN - Delivery Status Notifications\r\n');
            socket.write('214-PIPELINING - Command pipelining\r\n');
            socket.write('214-CHUNKING - BDAT chunking\r\n');
            socket.write('214 For more information, visit our website\r\n');
          } else if (command === 'QUIT') {
            socket.write('221 Thank you for using our service\r\n');
            socket.end();
          }
        });
      }
    });

    const smtpClient = createTestSmtpClient({
      host: testServer.hostname,
      port: testServer.port,
      secure: false,
      name: 'test-client.example.com'
    });

    // Test service discovery
    const email = new Email({
      from: 'sender@example.com',
      to: ['recipient@example.com'],
      subject: 'Service discovery test',
      text: 'Testing SMTP service extension discovery'
    });

    const result = await smtpClient.sendMail(email);
    console.log('  Service extension discovery completed');
    expect(result).toBeDefined();

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

  // Scenario 5: Backward compatibility negotiation
  await (async () => {
    scenarioCount++;
    console.log(`\nScenario ${scenarioCount}: Testing backward compatibility negotiation`);
    
    const testServer = await createTestServer({
      onConnection: async (socket) => {
        console.log('  [Server] Client connected');
        socket.write('220 compat.example.com ESMTP\r\n');
        
        let isESMTP = false;
        
        socket.on('data', (data) => {
          const command = data.toString().trim();
          console.log(`  [Server] Received: ${command}`);
          
          if (command.startsWith('EHLO')) {
            isESMTP = true;
            console.log('  [Server] ESMTP mode enabled');
            socket.write('250-compat.example.com\r\n');
            socket.write('250-SIZE 10485760\r\n');
            socket.write('250-8BITMIME\r\n');
            socket.write('250 ENHANCEDSTATUSCODES\r\n');
          } else if (command.startsWith('HELO')) {
            isESMTP = false;
            console.log('  [Server] Basic SMTP mode (RFC 821 compatibility)');
            socket.write('250 compat.example.com\r\n');
          } else if (command.startsWith('MAIL FROM:')) {
            if (isESMTP) {
              // Accept ESMTP parameters
              if (command.includes('SIZE=') || command.includes('BODY=')) {
                console.log('  [Server] ESMTP parameters accepted');
              }
              socket.write('250 2.1.0 Sender OK\r\n');
            } else {
              // Basic SMTP - reject ESMTP parameters
              if (command.includes('SIZE=') || command.includes('BODY=')) {
                console.log('  [Server] ESMTP parameters rejected in basic mode');
                socket.write('501 5.5.4 Syntax error in parameters\r\n');
              } else {
                socket.write('250 Sender OK\r\n');
              }
            }
          } else if (command.startsWith('RCPT TO:')) {
            if (isESMTP) {
              socket.write('250 2.1.5 Recipient OK\r\n');
            } else {
              socket.write('250 Recipient OK\r\n');
            }
          } else if (command === 'DATA') {
            if (isESMTP) {
              socket.write('354 2.0.0 Start mail input\r\n');
            } else {
              socket.write('354 Start mail input\r\n');
            }
          } else if (command === '.') {
            if (isESMTP) {
              socket.write('250 2.0.0 Message accepted\r\n');
            } else {
              socket.write('250 Message accepted\r\n');
            }
          } else if (command === 'QUIT') {
            if (isESMTP) {
              socket.write('221 2.0.0 Service closing\r\n');
            } else {
              socket.write('221 Service closing\r\n');
            }
            socket.end();
          }
        });
      }
    });

    // Test ESMTP mode
    const esmtpClient = createTestSmtpClient({
      host: testServer.hostname,
      port: testServer.port,
      secure: false
    });

    const esmtpEmail = new Email({
      from: 'sender@example.com',
      to: ['recipient@example.com'],
      subject: 'ESMTP compatibility test',
      text: 'Testing ESMTP mode with extensions'
    });

    const esmtpResult = await esmtpClient.sendMail(esmtpEmail);
    console.log('  ESMTP mode negotiation successful');
    expect(esmtpResult.response).toContain('2.0.0');

    // Test basic SMTP mode (fallback)
    const basicClient = createTestSmtpClient({
      host: testServer.hostname,
      port: testServer.port,
      secure: false,
      disableESMTP: true  // Force HELO instead of EHLO
    });

    const basicEmail = new Email({
      from: 'sender@example.com',
      to: ['recipient@example.com'],
      subject: 'Basic SMTP compatibility test',
      text: 'Testing basic SMTP mode without extensions'
    });

    const basicResult = await basicClient.sendMail(basicEmail);
    console.log('  Basic SMTP mode fallback successful');
    expect(basicResult.response).not.toContain('2.0.0');  // No enhanced status codes

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

  // Scenario 6: Extension interdependencies
  await (async () => {
    scenarioCount++;
    console.log(`\nScenario ${scenarioCount}: Testing extension interdependencies`);
    
    const testServer = await createTestServer({
      onConnection: async (socket) => {
        console.log('  [Server] Client connected');
        socket.write('220 interdep.example.com ESMTP\r\n');
        
        let tlsEnabled = false;
        let authenticated = false;
        
        socket.on('data', (data) => {
          const command = data.toString().trim();
          console.log(`  [Server] Received: ${command} (TLS: ${tlsEnabled}, Auth: ${authenticated})`);
          
          if (command.startsWith('EHLO')) {
            socket.write('250-interdep.example.com\r\n');
            
            if (!tlsEnabled) {
              // Before TLS
              socket.write('250-STARTTLS\r\n');
              socket.write('250-SIZE 1048576\r\n');  // Limited size before TLS
            } else {
              // After TLS
              socket.write('250-SIZE 52428800\r\n');  // Larger size after TLS
              socket.write('250-8BITMIME\r\n');
              socket.write('250-SMTPUTF8\r\n');
              socket.write('250-AUTH PLAIN LOGIN CRAM-MD5\r\n');
              
              if (authenticated) {
                // Additional capabilities after authentication
                socket.write('250-DSN\r\n');
                socket.write('250-DELIVERBY 86400\r\n');
              }
            }
            
            socket.write('250 ENHANCEDSTATUSCODES\r\n');
          } else if (command === 'STARTTLS') {
            if (!tlsEnabled) {
              socket.write('220 2.0.0 Ready to start TLS\r\n');
              tlsEnabled = true;
              console.log('  [Server] TLS enabled (simulated)');
              // In real implementation, would upgrade to TLS here
            } else {
              socket.write('503 5.5.1 TLS already active\r\n');
            }
          } else if (command.startsWith('AUTH')) {
            if (tlsEnabled) {
              authenticated = true;
              console.log('  [Server] Authentication successful (simulated)');
              socket.write('235 2.7.0 Authentication successful\r\n');
            } else {
              console.log('  [Server] AUTH rejected - TLS required');
              socket.write('538 5.7.11 Encryption required for authentication\r\n');
            }
          } else if (command.startsWith('MAIL FROM:')) {
            if (command.includes('SMTPUTF8') && !tlsEnabled) {
              console.log('  [Server] SMTPUTF8 requires TLS');
              socket.write('530 5.7.0 Must issue STARTTLS first\r\n');
            } else {
              socket.write('250 OK\r\n');
            }
          } else if (command.startsWith('RCPT TO:')) {
            if (command.includes('NOTIFY=') && !authenticated) {
              console.log('  [Server] DSN requires authentication');
              socket.write('530 5.7.0 Authentication required for DSN\r\n');
            } else {
              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 extension dependencies
    const smtpClient = createTestSmtpClient({
      host: testServer.hostname,
      port: testServer.port,
      secure: false,
      requireTLS: true,  // This will trigger STARTTLS
      auth: {
        user: 'testuser',
        pass: 'testpass'
      }
    });

    const email = new Email({
      from: 'sender@example.com',
      to: ['recipient@example.com'],
      subject: 'Extension interdependency test',
      text: 'Testing SMTP extension interdependencies',
      dsn: {
        notify: ['SUCCESS'],
        envid: 'interdep-test-123'
      }
    });

    try {
      const result = await smtpClient.sendMail(email);
      console.log('  Extension interdependency handling successful');
      expect(result).toBeDefined();
    } catch (error) {
      console.log(`  Extension dependency error (expected in test): ${error.message}`);
      // In test environment, STARTTLS won't actually work
    }

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

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

tap.start();