import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as net from 'net';
import * as tls from 'tls';
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js';
const TEST_PORT = 2525;
const TEST_TIMEOUT = 30000;

let testServer: ITestServer;

tap.test('setup - start SMTP server with TLS support for version tests', async () => {
  testServer = await startTestServer({ 
    port: TEST_PORT,
    tlsEnabled: true
  });
  
  await new Promise(resolve => setTimeout(resolve, 1000));
  expect(testServer).toBeDefined();
});

tap.test('TLS Versions - should support STARTTLS capability', async (tools) => {
  const done = tools.defer();
  
  try {
    const socket = net.createConnection({
      host: 'localhost',
      port: TEST_PORT,
      timeout: TEST_TIMEOUT
    });
    
    await new Promise<void>((resolve, reject) => {
      socket.once('connect', () => resolve());
      socket.once('error', reject);
    });
    
    // Get banner
    await new Promise<string>((resolve) => {
      socket.once('data', (chunk) => resolve(chunk.toString()));
    });
    
    // Send EHLO
    socket.write('EHLO testhost\r\n');
    
    const ehloResponse = await new Promise<string>((resolve) => {
      let data = '';
      const handler = (chunk: Buffer) => {
        data += chunk.toString();
        if (data.includes('\r\n') && (data.match(/^250 /m) || data.match(/^250-.*\r\n250 /ms))) {
          socket.removeListener('data', handler);
          resolve(data);
        }
      };
      socket.on('data', handler);
    });
    
    console.log('EHLO response:', ehloResponse);
    
    // Check for STARTTLS support
    const supportsStarttls = ehloResponse.includes('250-STARTTLS') || ehloResponse.includes('250 STARTTLS');
    console.log('STARTTLS supported:', supportsStarttls);
    
    if (supportsStarttls) {
      // Test STARTTLS upgrade
      socket.write('STARTTLS\r\n');
      
      const starttlsResponse = await new Promise<string>((resolve) => {
        socket.once('data', (chunk) => resolve(chunk.toString()));
      });
      
      expect(starttlsResponse).toInclude('220');
      console.log('STARTTLS ready response received');
      
      // Would upgrade to TLS here in a real implementation
      // For testing, we just verify the capability
    }
    
    // Clean up
    socket.write('QUIT\r\n');
    socket.end();
    
    // STARTTLS is optional but common
    expect(true).toEqual(true);
    
  } finally {
    done.resolve();
  }
});

tap.test('TLS Versions - should support modern TLS versions via STARTTLS', async (tools) => {
  const done = tools.defer();
  
  try {
    // Test TLS 1.2 via STARTTLS
    console.log('Testing TLS 1.2 support via STARTTLS...');
    const tls12Result = await testTlsVersionViaStartTls('TLSv1.2', TEST_PORT);
    console.log('TLS 1.2 result:', tls12Result);
    
    // Test TLS 1.3 via STARTTLS
    console.log('Testing TLS 1.3 support via STARTTLS...');
    const tls13Result = await testTlsVersionViaStartTls('TLSv1.3', TEST_PORT);
    console.log('TLS 1.3 result:', tls13Result);
    
    // At least one modern version should be supported
    const supportsModernTls = tls12Result.success || tls13Result.success;
    expect(supportsModernTls).toEqual(true);
    
    if (tls12Result.success) {
      console.log('TLS 1.2 supported with cipher:', tls12Result.cipher);
    }
    if (tls13Result.success) {
      console.log('TLS 1.3 supported with cipher:', tls13Result.cipher);
    }
    
  } finally {
    done.resolve();
  }
});

tap.test('TLS Versions - should reject obsolete TLS versions via STARTTLS', async (tools) => {
  const done = tools.defer();
  
  try {
    // Test TLS 1.0 (should be rejected by modern servers)
    console.log('Testing TLS 1.0 (obsolete) via STARTTLS...');
    const tls10Result = await testTlsVersionViaStartTls('TLSv1', TEST_PORT);
    
    // Test TLS 1.1 (should be rejected by modern servers)
    console.log('Testing TLS 1.1 (obsolete) via STARTTLS...');
    const tls11Result = await testTlsVersionViaStartTls('TLSv1.1', TEST_PORT);
    
    // Modern servers should reject these old versions
    // But some might still support them for compatibility
    console.log(`TLS 1.0 ${tls10Result.success ? 'accepted (legacy support)' : 'rejected (good)'}`);
    console.log(`TLS 1.1 ${tls11Result.success ? 'accepted (legacy support)' : 'rejected (good)'}`);
    
    // Either behavior is acceptable - log the results
    expect(true).toEqual(true);
    
  } finally {
    done.resolve();
  }
});

tap.test('TLS Versions - should provide cipher information via STARTTLS', async (tools) => {
  const done = tools.defer();
  
  try {
    // Connect to plain SMTP port
    const socket = net.createConnection({
      host: 'localhost',
      port: TEST_PORT,
      timeout: TEST_TIMEOUT
    });
    
    await new Promise<void>((resolve, reject) => {
      socket.once('connect', () => resolve());
      socket.once('error', reject);
    });
    
    // Get banner
    await new Promise<string>((resolve) => {
      socket.once('data', (chunk) => resolve(chunk.toString()));
    });
    
    // Send EHLO
    socket.write('EHLO testhost\r\n');
    
    const ehloResponse = await new Promise<string>((resolve) => {
      let data = '';
      const handler = (chunk: Buffer) => {
        data += chunk.toString();
        if (data.includes('\r\n') && (data.match(/^250 /m) || data.match(/^250-.*\r\n250 /ms))) {
          socket.removeListener('data', handler);
          resolve(data);
        }
      };
      socket.on('data', handler);
    });
    
    // Check for STARTTLS
    if (!ehloResponse.includes('STARTTLS')) {
      console.log('Server does not support STARTTLS - skipping cipher info test');
      socket.write('QUIT\r\n');
      socket.end();
      done.resolve();
      return;
    }
    
    // Send STARTTLS
    socket.write('STARTTLS\r\n');
    
    await new Promise<string>((resolve) => {
      socket.once('data', (chunk) => resolve(chunk.toString()));
    });
    
    // Upgrade to TLS
    const tlsSocket = tls.connect({
      socket: socket,
      servername: 'localhost',
      rejectUnauthorized: false
    });
    
    await new Promise<void>((resolve, reject) => {
      tlsSocket.once('secureConnect', () => resolve());
      tlsSocket.once('error', reject);
    });
    
    // Get connection details
    const cipher = tlsSocket.getCipher();
    const protocol = tlsSocket.getProtocol();
    const authorized = tlsSocket.authorized;
    
    console.log('TLS connection established via STARTTLS:');
    console.log('- Protocol:', protocol);
    console.log('- Cipher:', cipher?.name);
    console.log('- Key exchange:', cipher?.standardName);
    console.log('- Authorized:', authorized);
    
    if (protocol) {
      expect(typeof protocol).toEqual('string');
    }
    if (cipher) {
      expect(cipher.name).toBeDefined();
    }
    
    // Clean up
    tlsSocket.write('QUIT\r\n');
    tlsSocket.end();
    
  } finally {
    done.resolve();
  }
});

// Helper function to test specific TLS version via STARTTLS
async function testTlsVersionViaStartTls(version: string, port: number): Promise<{success: boolean, cipher?: any, error?: string}> {
  return new Promise(async (resolve) => {
    try {
      // Connect to plain SMTP port
      const socket = net.createConnection({
        host: 'localhost',
        port: port,
        timeout: 5000
      });
      
      await new Promise<void>((socketResolve, socketReject) => {
        socket.once('connect', () => socketResolve());
        socket.once('error', socketReject);
      });
      
      // Get banner
      await new Promise<string>((bannerResolve) => {
        socket.once('data', (chunk) => bannerResolve(chunk.toString()));
      });
      
      // Send EHLO
      socket.write('EHLO testhost\r\n');
      
      const ehloResponse = await new Promise<string>((ehloResolve) => {
        let data = '';
        const handler = (chunk: Buffer) => {
          data += chunk.toString();
          if (data.includes('\r\n') && (data.match(/^250 /m) || data.match(/^250-.*\r\n250 /ms))) {
            socket.removeListener('data', handler);
            ehloResolve(data);
          }
        };
        socket.on('data', handler);
      });
      
      // Check for STARTTLS
      if (!ehloResponse.includes('STARTTLS')) {
        socket.destroy();
        resolve({
          success: false,
          error: 'STARTTLS not supported'
        });
        return;
      }
      
      // Send STARTTLS
      socket.write('STARTTLS\r\n');
      
      await new Promise<string>((starttlsResolve) => {
        socket.once('data', (chunk) => starttlsResolve(chunk.toString()));
      });
      
      // Set up TLS options with version constraints
      const tlsOptions: any = {
        socket: socket,
        servername: 'localhost',
        rejectUnauthorized: false
      };
      
      // Set version constraints based on requested version
      switch (version) {
        case 'TLSv1':
          tlsOptions.minVersion = 'TLSv1';
          tlsOptions.maxVersion = 'TLSv1';
          break;
        case 'TLSv1.1':
          tlsOptions.minVersion = 'TLSv1.1';
          tlsOptions.maxVersion = 'TLSv1.1';
          break;
        case 'TLSv1.2':
          tlsOptions.minVersion = 'TLSv1.2';
          tlsOptions.maxVersion = 'TLSv1.2';
          break;
        case 'TLSv1.3':
          tlsOptions.minVersion = 'TLSv1.3';
          tlsOptions.maxVersion = 'TLSv1.3';
          break;
      }
      
      // Upgrade to TLS
      const tlsSocket = tls.connect(tlsOptions);
      
      tlsSocket.once('secureConnect', () => {
        const cipher = tlsSocket.getCipher();
        const protocol = tlsSocket.getProtocol();
        
        tlsSocket.destroy();
        resolve({
          success: true,
          cipher: {
            name: cipher?.name,
            standardName: cipher?.standardName,
            protocol: protocol
          }
        });
      });
      
      tlsSocket.once('error', (error) => {
        resolve({
          success: false,
          error: error.message
        });
      });
      
      setTimeout(() => {
        tlsSocket.destroy();
        resolve({
          success: false,
          error: 'TLS handshake timeout'
        });
      }, 5000);
      
    } catch (error) {
      resolve({
        success: false,
        error: error instanceof Error ? error.message : 'Unknown error'
      });
    }
  });
}

tap.test('cleanup - stop SMTP server', async () => {
  await stopTestServer(testServer);
  expect(true).toEqual(true);
});

export default tap.start();
