import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as net from 'net';
import * as path from 'path';
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js';

// Test configuration
const TEST_PORT = 2525;

let testServer;
const TEST_TIMEOUT = 10000;

// Setup
tap.test('prepare server', async () => {
  testServer = await startTestServer({ port: TEST_PORT });
  await new Promise(resolve => setTimeout(resolve, 100));
});

// Test: Basic HELP command
tap.test('HELP - should respond to general HELP command', 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 = 'help';
      receivedData = ''; // Clear buffer before sending HELP
      socket.write('HELP\r\n');
    } else if (currentStep === 'help' && receivedData.includes('214')) {
      const lines = receivedData.split('\r\n');
      const helpResponse = lines.find(line => line.match(/^\d{3}/));
      const responseCode = helpResponse?.substring(0, 3);
      
      socket.write('QUIT\r\n');
      setTimeout(() => {
        socket.destroy();
        
        // HELP may return:
        // 214 - Help message
        // 502 - Command not implemented
        // 504 - Command parameter not implemented
        expect(responseCode).toMatch(/^(214|502|504)$/);
        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: HELP with specific topics
tap.test('HELP - should respond to HELP with specific command topics', async (tools) => {
  const done = tools.defer();
  
  const socket = net.createConnection({
    host: 'localhost',
    port: TEST_PORT,
    timeout: TEST_TIMEOUT
  });
  
  let receivedData = '';
  let currentStep = 'connecting';
  const helpTopics = ['EHLO', 'MAIL', 'RCPT', 'DATA', 'QUIT'];
  let currentTopicIndex = 0;
  const helpResults: Array<{ topic: string; responseCode: string; supported: boolean }> = [];
  
  const getLastResponse = (data: string): string => {
    const lines = data.split('\r\n');
    for (let i = lines.length - 1; i >= 0; i--) {
      const line = lines[i].trim();
      if (line && /^\d{3}/.test(line)) {
        return line;
      }
    }
    return '';
  };
  
  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 = 'help_topics';
      receivedData = ''; // Clear buffer before sending first HELP topic
      socket.write(`HELP ${helpTopics[currentTopicIndex]}\r\n`);
    } else if (currentStep === 'help_topics' && (receivedData.includes('214') || receivedData.includes('502') || receivedData.includes('504'))) {
      const lastResponse = getLastResponse(receivedData);
      
      if (lastResponse && lastResponse.match(/^\d{3}/)) {
        const responseCode = lastResponse.substring(0, 3);
        helpResults.push({
          topic: helpTopics[currentTopicIndex],
          responseCode: responseCode,
          supported: responseCode === '214'
        });
        
        currentTopicIndex++;
        
        if (currentTopicIndex < helpTopics.length) {
          receivedData = ''; // Clear buffer
          socket.write(`HELP ${helpTopics[currentTopicIndex]}\r\n`);
        } else {
          currentStep = 'done'; // Change state to prevent processing QUIT response
          socket.write('QUIT\r\n');
          setTimeout(() => {
            socket.destroy();
            
            // Should have results for all topics
            expect(helpResults.length).toEqual(helpTopics.length);
            
            // All responses should be valid
            helpResults.forEach(result => {
              expect(result.responseCode).toMatch(/^(214|502|504)$/);
            });
            
            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: HELP response format
tap.test('HELP - should return properly formatted help text', async (tools) => {
  const done = tools.defer();
  
  const socket = net.createConnection({
    host: 'localhost',
    port: TEST_PORT,
    timeout: TEST_TIMEOUT
  });
  
  let receivedData = '';
  let currentStep = 'connecting';
  let helpResponse = '';
  
  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 = 'help';
      receivedData = ''; // Clear to capture only HELP response
      socket.write('HELP\r\n');
    } else if (currentStep === 'help') {
      helpResponse = receivedData;
      const responseCode = receivedData.match(/(\d{3})/)?.[1];
      
      if (responseCode === '214') {
        // Help is supported - check format
        const lines = receivedData.split('\r\n');
        const helpLines = lines.filter(l => l.startsWith('214'));
        
        // Should have at least one help line
        expect(helpLines.length).toBeGreaterThan(0);
        
        // Multi-line help should use 214- prefix
        if (helpLines.length > 1) {
          const hasMultilineFormat = helpLines.some(l => l.startsWith('214-'));
          expect(hasMultilineFormat).toEqual(true);
        }
      }
      
      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: HELP during transaction
tap.test('HELP - should work during mail transaction', 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:<sender@example.com>\r\n');
    } else if (currentStep === 'mail_from' && receivedData.includes('250')) {
      currentStep = 'help_during_transaction';
      receivedData = ''; // Clear buffer before sending HELP
      socket.write('HELP RCPT\r\n');
    } else if (currentStep === 'help_during_transaction' && receivedData.includes('214')) {
      const responseCode = '214'; // We know HELP works on this server
      
      // HELP should work even during transaction
      expect(responseCode).toMatch(/^(214|502|504)$/);
      
      currentStep = 'rcpt_to';
      socket.write('RCPT TO:<recipient@example.com>\r\n');
    } else if (currentStep === 'rcpt_to' && receivedData.includes('250')) {
      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: HELP with invalid topic
tap.test('HELP - should handle HELP with invalid topic', 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 = 'help_invalid';
      receivedData = ''; // Clear buffer before sending HELP
      socket.write('HELP INVALID_COMMAND_XYZ\r\n');
    } else if (currentStep === 'help_invalid' && receivedData.includes(' ')) {
      const lines = receivedData.split('\r\n');
      const helpResponse = lines.find(line => line.match(/^\d{3}/));
      const responseCode = helpResponse?.substring(0, 3);
      
      socket.write('QUIT\r\n');
      setTimeout(() => {
        socket.destroy();
        
        // Should return 504 (command parameter not implemented) or
        // 214 (general help) or 502 (not implemented)
        expect(responseCode).toMatch(/^(214|502|504)$/);
        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: HELP availability check
tap.test('HELP - verify HELP command optional status', async (tools) => {
  const done = tools.defer();
  
  const socket = net.createConnection({
    host: 'localhost',
    port: TEST_PORT,
    timeout: TEST_TIMEOUT
  });
  
  let receivedData = '';
  let currentStep = 'connecting';
  let helpSupported = 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')) {
      // Check if HELP is advertised in EHLO response
      if (receivedData.includes('HELP')) {
        console.log('HELP command advertised in EHLO response');
      }
      
      currentStep = 'help_test';
      receivedData = ''; // Clear buffer before sending HELP
      socket.write('HELP\r\n');
    } else if (currentStep === 'help_test' && receivedData.includes(' ')) {
      const lines = receivedData.split('\r\n');
      const helpResponse = lines.find(line => line.match(/^\d{3}/));
      const responseCode = helpResponse?.substring(0, 3);
      
      if (responseCode === '214') {
        helpSupported = true;
        console.log('HELP command is supported');
      } else if (responseCode === '502') {
        console.log('HELP command not implemented (optional per RFC 5321)');
      }
      
      socket.write('QUIT\r\n');
      setTimeout(() => {
        socket.destroy();
        
        // Both supported and not supported are valid
        expect(responseCode).toMatch(/^(214|502)$/);
        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: HELP content usefulness
tap.test('HELP - check if help content is useful when supported', 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 = 'help_data';
      receivedData = ''; // Clear buffer before sending HELP
      socket.write('HELP DATA\r\n');
    } else if (currentStep === 'help_data' && receivedData.includes(' ')) {
      const lines = receivedData.split('\r\n');
      const helpResponse = lines.find(line => line.match(/^\d{3}/));
      const responseCode = helpResponse?.substring(0, 3);
      
      if (responseCode === '214') {
        // Check if help text mentions relevant DATA command info
        const helpText = receivedData.toLowerCase();
        if (helpText.includes('data') || helpText.includes('message') || helpText.includes('354')) {
          console.log('HELP provides relevant information about DATA command');
        }
      }
      
      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;
});

// Teardown
tap.test('cleanup server', async () => {
  await stopTestServer(testServer);
});

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