import { tap, expect } from '@git.zone/tstest/tapbundle';
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js';
import { createSmtpClient } from '../../../ts/mail/delivery/smtpclient/index.js';
import { Email } from '../../../ts/mail/core/classes.email.js';
import * as net from 'net';

let testServer: ITestServer;

tap.test('setup test SMTP server', async () => {
  testServer = await startTestServer({
    port: 2572,
    tlsEnabled: false,
    authRequired: false
  });
  expect(testServer).toBeTruthy();
  expect(testServer.port).toEqual(2572);
});

tap.test('CEDGE-03: Server closes connection during MAIL FROM', async () => {
  // Create server that abruptly closes during MAIL FROM
  const abruptServer = net.createServer((socket) => {
    socket.write('220 mail.example.com ESMTP\r\n');
    let commandCount = 0;
    
    socket.on('data', (data) => {
      const lines = data.toString().split('\r\n');
      
      lines.forEach(line => {
        if (!line && lines[lines.length - 1] === '') return;
        
        commandCount++;
        console.log(`Server received command ${commandCount}: "${line}"`);
        
        if (line.startsWith('EHLO')) {
          socket.write('250-mail.example.com\r\n');
          socket.write('250 OK\r\n');
        } else if (line.startsWith('MAIL FROM:')) {
          // Abruptly close connection
          console.log('Server closing connection unexpectedly');
          socket.destroy();
        }
      });
    });
  });

  await new Promise<void>((resolve) => {
    abruptServer.listen(0, '127.0.0.1', () => resolve());
  });

  const abruptPort = (abruptServer.address() as net.AddressInfo).port;

  const smtpClient = createSmtpClient({
    host: '127.0.0.1',
    port: abruptPort,
    secure: false,
    connectionTimeout: 5000,
    debug: true
  });

  const email = new Email({
    from: 'sender@example.com',
    to: ['recipient@example.com'],
    subject: 'Connection closure test',
    text: 'Testing unexpected disconnection'
  });

  try {
    const result = await smtpClient.sendMail(email);
    // Should not succeed due to connection closure
    expect(result.success).toBeFalse();
    console.log('✅ Client handled abrupt connection closure gracefully');
  } catch (error) {
    // Expected to fail due to connection closure
    console.log('✅ Client threw expected error for connection closure:', error.message);
    expect(error.message).toMatch(/closed|reset|abort|end|timeout/i);
  }

  await smtpClient.close();
  abruptServer.close();
});

tap.test('CEDGE-03: Server sends invalid response codes', async () => {
  // Create server that sends non-standard response codes
  const invalidServer = net.createServer((socket) => {
    socket.write('220 mail.example.com ESMTP\r\n');
    let inData = false;
    
    socket.on('data', (data) => {
      const lines = data.toString().split('\r\n');
      
      lines.forEach(line => {
        if (!line && lines[lines.length - 1] === '') return;
        
        console.log(`Server received: "${line}"`);
        
        if (inData) {
          if (line === '.') {
            socket.write('999 Invalid response code\r\n'); // Invalid 9xx code
            inData = false;
          }
        } else if (line.startsWith('EHLO')) {
          socket.write('150 Intermediate response\r\n'); // Invalid for EHLO
        } else if (line.startsWith('MAIL FROM:')) {
          socket.write('250 OK\r\n');
        } else if (line.startsWith('RCPT TO:')) {
          socket.write('250 OK\r\n');
        } else if (line === 'DATA') {
          socket.write('354 Start mail input\r\n');
          inData = true;
        } else if (line === 'QUIT') {
          socket.write('221 Bye\r\n');
          socket.end();
        }
      });
    });
  });

  await new Promise<void>((resolve) => {
    invalidServer.listen(0, '127.0.0.1', () => resolve());
  });

  const invalidPort = (invalidServer.address() as net.AddressInfo).port;

  const smtpClient = createSmtpClient({
    host: '127.0.0.1',
    port: invalidPort,
    secure: false,
    connectionTimeout: 5000,
    debug: true
  });

  try {
    // This will likely fail due to invalid EHLO response
    const verified = await smtpClient.verify();
    expect(verified).toBeFalse();
    console.log('✅ Client rejected invalid response codes');
  } catch (error) {
    console.log('✅ Client properly handled invalid response codes:', error.message);
  }

  await smtpClient.close();
  invalidServer.close();
});

tap.test('CEDGE-03: Server sends malformed multi-line responses', async () => {
  // Create server with malformed multi-line responses
  const malformedServer = net.createServer((socket) => {
    socket.write('220 mail.example.com ESMTP\r\n');
    
    socket.on('data', (data) => {
      const lines = data.toString().split('\r\n');
      
      lines.forEach(line => {
        if (!line && lines[lines.length - 1] === '') return;
        
        console.log(`Server received: "${line}"`);
        
        if (line.startsWith('EHLO')) {
          // Malformed multi-line response (missing final line)
          socket.write('250-mail.example.com\r\n');
          socket.write('250-PIPELINING\r\n');
          // Missing final 250 line - this violates RFC
        } else if (line === 'QUIT') {
          socket.write('221 Bye\r\n');
          socket.end();
        }
      });
    });
  });

  await new Promise<void>((resolve) => {
    malformedServer.listen(0, '127.0.0.1', () => resolve());
  });

  const malformedPort = (malformedServer.address() as net.AddressInfo).port;

  const smtpClient = createSmtpClient({
    host: '127.0.0.1',
    port: malformedPort,
    secure: false,
    connectionTimeout: 3000, // Shorter timeout for faster test
    debug: true
  });

  try {
    // Should timeout due to incomplete EHLO response
    const verified = await smtpClient.verify();
    
    // If we get here, the client accepted the malformed response
    // This is acceptable if the client can work around it
    if (verified === false) {
      console.log('✅ Client rejected malformed multi-line response');
    } else {
      console.log('⚠️ Client accepted malformed multi-line response');
    }
  } catch (error) {
    console.log('✅ Client handled malformed response with error:', error.message);
    // Should timeout or error on malformed response
    expect(error.message).toMatch(/timeout|Command timeout|Greeting timeout|response|parse/i);
  }

  // Force close since the connection might still be waiting
  try {
    await smtpClient.close();
  } catch (closeError) {
    // Ignore close errors
  }
  
  malformedServer.close();
});

tap.test('CEDGE-03: Server violates command sequence rules', async () => {
  // Create server that accepts commands out of sequence
  const sequenceServer = net.createServer((socket) => {
    socket.write('220 mail.example.com ESMTP\r\n');
    
    socket.on('data', (data) => {
      const lines = data.toString().split('\r\n');
      
      lines.forEach(line => {
        if (!line && lines[lines.length - 1] === '') return;
        
        console.log(`Server received: "${line}"`);
        
        // Accept any command in any order (protocol violation)
        if (line.startsWith('EHLO')) {
          socket.write('250 OK\r\n');
        } else if (line.startsWith('MAIL FROM:')) {
          socket.write('250 OK\r\n');
        } else if (line.startsWith('RCPT TO:')) {
          socket.write('250 OK\r\n');
        } else if (line === 'DATA') {
          socket.write('354 Start mail input\r\n');
        } else if (line === '.') {
          socket.write('250 Message accepted\r\n');
        } else if (line === 'QUIT') {
          socket.write('221 Bye\r\n');
          socket.end();
        }
      });
    });
  });

  await new Promise<void>((resolve) => {
    sequenceServer.listen(0, '127.0.0.1', () => resolve());
  });

  const sequencePort = (sequenceServer.address() as net.AddressInfo).port;

  const smtpClient = createSmtpClient({
    host: '127.0.0.1',
    port: sequencePort,
    secure: false,
    connectionTimeout: 5000,
    debug: true
  });

  // Client should still work correctly despite server violations
  const email = new Email({
    from: 'sender@example.com',
    to: ['recipient@example.com'],
    subject: 'Sequence violation test',
    text: 'Testing command sequence violations'
  });

  const result = await smtpClient.sendMail(email);
  expect(result.success).toBeTrue();
  console.log('✅ Client maintains proper sequence despite server violations');

  await smtpClient.close();
  sequenceServer.close();
});

tap.test('CEDGE-03: Server sends responses without CRLF', async () => {
  // Create server that sends responses with incorrect line endings
  const crlfServer = net.createServer((socket) => {
    socket.write('220 mail.example.com ESMTP\n'); // LF only, not CRLF
    
    socket.on('data', (data) => {
      const lines = data.toString().split('\r\n');
      
      lines.forEach(line => {
        if (!line && lines[lines.length - 1] === '') return;
        
        console.log(`Server received: "${line}"`);
        
        if (line.startsWith('EHLO')) {
          socket.write('250 OK\n'); // LF only
        } else if (line === 'QUIT') {
          socket.write('221 Bye\n'); // LF only
          socket.end();
        } else {
          socket.write('250 OK\n'); // LF only
        }
      });
    });
  });

  await new Promise<void>((resolve) => {
    crlfServer.listen(0, '127.0.0.1', () => resolve());
  });

  const crlfPort = (crlfServer.address() as net.AddressInfo).port;

  const smtpClient = createSmtpClient({
    host: '127.0.0.1',
    port: crlfPort,
    secure: false,
    connectionTimeout: 5000,
    debug: true
  });

  try {
    const verified = await smtpClient.verify();
    if (verified) {
      console.log('✅ Client handled non-CRLF responses gracefully');
    } else {
      console.log('✅ Client rejected non-CRLF responses');
    }
  } catch (error) {
    console.log('✅ Client handled CRLF violation with error:', error.message);
  }

  await smtpClient.close();
  crlfServer.close();
});

tap.test('CEDGE-03: Server sends oversized responses', async () => {
  // Create server that sends very long response lines
  const oversizeServer = net.createServer((socket) => {
    socket.write('220 mail.example.com ESMTP\r\n');
    
    socket.on('data', (data) => {
      const lines = data.toString().split('\r\n');
      
      lines.forEach(line => {
        if (!line && lines[lines.length - 1] === '') return;
        
        console.log(`Server received: "${line}"`);
        
        if (line.startsWith('EHLO')) {
          // Send an extremely long response line (over RFC limit)
          const longResponse = '250 ' + 'x'.repeat(2000) + '\r\n';
          socket.write(longResponse);
        } else if (line === 'QUIT') {
          socket.write('221 Bye\r\n');
          socket.end();
        } else {
          socket.write('250 OK\r\n');
        }
      });
    });
  });

  await new Promise<void>((resolve) => {
    oversizeServer.listen(0, '127.0.0.1', () => resolve());
  });

  const oversizePort = (oversizeServer.address() as net.AddressInfo).port;

  const smtpClient = createSmtpClient({
    host: '127.0.0.1',
    port: oversizePort,
    secure: false,
    connectionTimeout: 5000,
    debug: true
  });

  try {
    const verified = await smtpClient.verify();
    console.log(`Verification with oversized response: ${verified}`);
    console.log('✅ Client handled oversized response');
  } catch (error) {
    console.log('✅ Client handled oversized response with error:', error.message);
  }

  await smtpClient.close();
  oversizeServer.close();
});

tap.test('CEDGE-03: Server violates RFC timing requirements', async () => {
  // Create server that has excessive delays
  const slowServer = net.createServer((socket) => {
    socket.write('220 mail.example.com ESMTP\r\n');
    
    socket.on('data', (data) => {
      const lines = data.toString().split('\r\n');
      
      lines.forEach(line => {
        if (!line && lines[lines.length - 1] === '') return;
        
        console.log(`Server received: "${line}"`);
        
        if (line.startsWith('EHLO')) {
          // Extreme delay (violates RFC timing recommendations)
          setTimeout(() => {
            socket.write('250 OK\r\n');
          }, 2000); // 2 second delay
        } else if (line === 'QUIT') {
          socket.write('221 Bye\r\n');
          socket.end();
        } else {
          socket.write('250 OK\r\n');
        }
      });
    });
  });

  await new Promise<void>((resolve) => {
    slowServer.listen(0, '127.0.0.1', () => resolve());
  });

  const slowPort = (slowServer.address() as net.AddressInfo).port;

  const smtpClient = createSmtpClient({
    host: '127.0.0.1',
    port: slowPort,
    secure: false,
    connectionTimeout: 10000, // Allow time for slow response
    debug: true
  });

  const startTime = Date.now();
  try {
    const verified = await smtpClient.verify();
    const duration = Date.now() - startTime;
    
    console.log(`Verification completed in ${duration}ms`);
    if (verified) {
      console.log('✅ Client handled slow server responses');
    }
  } catch (error) {
    console.log('✅ Client handled timing violation with error:', error.message);
  }

  await smtpClient.close();
  slowServer.close();
});

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

export default tap.start();