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

const TEST_PORT = 30037;
const TEST_TIMEOUT = 30000;

let testServer: ITestServer;

tap.test('setup - start SMTP server for extremely long lines tests', async () => {
  testServer = await startTestServer({
    port: TEST_PORT,
    hostname: 'localhost'
  });
  expect(testServer).toBeDefined();
});

tap.test('Extremely Long Lines - should handle lines exceeding RFC 5321 limit', 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');
    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);
    });
    
    // Send envelope
    socket.write('MAIL FROM:<sender@example.com>\r\n');
    await new Promise<string>((resolve) => {
      socket.once('data', (chunk) => resolve(chunk.toString()));
    });
    
    socket.write('RCPT TO:<recipient@example.com>\r\n');
    await new Promise<string>((resolve) => {
      socket.once('data', (chunk) => resolve(chunk.toString()));
    });
    
    socket.write('DATA\r\n');
    const dataResponse = await new Promise<string>((resolve) => {
      socket.once('data', (chunk) => resolve(chunk.toString()));
    });
    
    expect(dataResponse).toInclude('354');
    
    // Create line exceeding RFC 5321 limit (1000 chars including CRLF)
    const longLine = 'X'.repeat(2000); // 2000 character line
    
    const emailWithLongLine = 
      'From: sender@example.com\r\n' +
      'To: recipient@example.com\r\n' +
      'Subject: Long Line Test\r\n' +
      '\r\n' +
      'This email contains an extremely long line:\r\n' +
      longLine + '\r\n' +
      'End of test.\r\n' +
      '.\r\n';
    
    socket.write(emailWithLongLine);
    
    const finalResponse = await new Promise<string>((resolve) => {
      socket.once('data', (chunk) => resolve(chunk.toString()));
    });
    
    console.log(`Response to ${longLine.length} character line:`, finalResponse);
    
    // Server should handle gracefully (accept, wrap, or reject)
    const accepted = finalResponse.includes('250');
    const rejected = finalResponse.includes('552') || finalResponse.includes('500') || finalResponse.includes('554');
    
    expect(accepted || rejected).toEqual(true);
    
    if (accepted) {
      console.log('Server accepted long line (may wrap internally)');
    } else {
      console.log('Server rejected long line');
    }
    
    // Clean up
    socket.write('QUIT\r\n');
    socket.end();
    
  } finally {
    done.resolve();
  }
});

tap.test('Extremely Long Lines - should handle extremely long subject header', 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);
    });
    
    // Setup connection
    await new Promise<string>((resolve) => {
      socket.once('data', (chunk) => resolve(chunk.toString()));
    });
    
    socket.write('EHLO testhost\r\n');
    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);
    });
    
    // Send envelope
    socket.write('MAIL FROM:<sender@example.com>\r\n');
    await new Promise<string>((resolve) => {
      socket.once('data', (chunk) => resolve(chunk.toString()));
    });
    
    socket.write('RCPT TO:<recipient@example.com>\r\n');
    await new Promise<string>((resolve) => {
      socket.once('data', (chunk) => resolve(chunk.toString()));
    });
    
    socket.write('DATA\r\n');
    await new Promise<string>((resolve) => {
      socket.once('data', (chunk) => resolve(chunk.toString()));
    });
    
    // Create extremely long subject (3000 characters)
    const longSubject = 'A'.repeat(3000);
    
    const emailWithLongSubject = 
      'From: sender@example.com\r\n' +
      'To: recipient@example.com\r\n' +
      `Subject: ${longSubject}\r\n` +
      '\r\n' +
      'Body of email with extremely long subject.\r\n' +
      '.\r\n';
    
    socket.write(emailWithLongSubject);
    
    const finalResponse = await new Promise<string>((resolve) => {
      socket.once('data', (chunk) => resolve(chunk.toString()));
    });
    
    console.log(`Response to ${longSubject.length} character subject:`, finalResponse);
    
    // Server should handle this
    expect(finalResponse).toMatch(/^[2-5]\d{2}/);
    
    // Clean up
    socket.write('QUIT\r\n');
    socket.end();
    
  } finally {
    done.resolve();
  }
});

tap.test('Extremely Long Lines - should handle multiple consecutive long lines', 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);
    });
    
    // Setup connection
    await new Promise<string>((resolve) => {
      socket.once('data', (chunk) => resolve(chunk.toString()));
    });
    
    socket.write('EHLO testhost\r\n');
    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);
    });
    
    // Send envelope
    socket.write('MAIL FROM:<sender@example.com>\r\n');
    await new Promise<string>((resolve) => {
      socket.once('data', (chunk) => resolve(chunk.toString()));
    });
    
    socket.write('RCPT TO:<recipient@example.com>\r\n');
    await new Promise<string>((resolve) => {
      socket.once('data', (chunk) => resolve(chunk.toString()));
    });
    
    socket.write('DATA\r\n');
    await new Promise<string>((resolve) => {
      socket.once('data', (chunk) => resolve(chunk.toString()));
    });
    
    // Create multiple long lines
    const longLine1 = 'A'.repeat(1500);
    const longLine2 = 'B'.repeat(1800);
    const longLine3 = 'C'.repeat(2000);
    
    const emailWithMultipleLongLines = 
      'From: sender@example.com\r\n' +
      'To: recipient@example.com\r\n' +
      'Subject: Multiple Long Lines Test\r\n' +
      '\r\n' +
      'First long line:\r\n' +
      longLine1 + '\r\n' +
      'Second long line:\r\n' +
      longLine2 + '\r\n' +
      'Third long line:\r\n' +
      longLine3 + '\r\n' +
      '.\r\n';
    
    socket.write(emailWithMultipleLongLines);
    
    const finalResponse = await new Promise<string>((resolve) => {
      socket.once('data', (chunk) => resolve(chunk.toString()));
    });
    
    console.log('Response to multiple long lines:', finalResponse);
    
    // Server should handle this
    expect(finalResponse).toMatch(/^[2-5]\d{2}/);
    
    // Clean up
    socket.write('QUIT\r\n');
    socket.end();
    
  } finally {
    done.resolve();
  }
});

tap.test('Extremely Long Lines - should handle extremely long MAIL FROM parameter', 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);
    });
    
    // Setup connection
    await new Promise<string>((resolve) => {
      socket.once('data', (chunk) => resolve(chunk.toString()));
    });
    
    socket.write('EHLO testhost\r\n');
    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);
    });
    
    // Create extremely long email address (technically invalid but testing limits)
    const longLocalPart = 'a'.repeat(500);
    const longDomain = 'b'.repeat(500) + '.com';
    const longEmail = `${longLocalPart}@${longDomain}`;
    
    socket.write(`MAIL FROM:<${longEmail}>\r\n`);
    
    const response = await new Promise<string>((resolve) => {
      socket.once('data', (chunk) => resolve(chunk.toString()));
    });
    
    console.log(`Response to ${longEmail.length} character email address:`, response);
    
    // Should get error response
    expect(response).toMatch(/^5\d{2}/);
    
    // Clean up
    socket.write('QUIT\r\n');
    socket.end();
    
  } finally {
    done.resolve();
  }
});

tap.test('Extremely Long Lines - should handle line exactly at RFC limit', 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);
    });
    
    // Setup connection
    await new Promise<string>((resolve) => {
      socket.once('data', (chunk) => resolve(chunk.toString()));
    });
    
    socket.write('EHLO testhost\r\n');
    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);
    });
    
    // Send envelope
    socket.write('MAIL FROM:<sender@example.com>\r\n');
    await new Promise<string>((resolve) => {
      socket.once('data', (chunk) => resolve(chunk.toString()));
    });
    
    socket.write('RCPT TO:<recipient@example.com>\r\n');
    await new Promise<string>((resolve) => {
      socket.once('data', (chunk) => resolve(chunk.toString()));
    });
    
    socket.write('DATA\r\n');
    await new Promise<string>((resolve) => {
      socket.once('data', (chunk) => resolve(chunk.toString()));
    });
    
    // Create line exactly at RFC 5321 limit (998 chars + CRLF = 1000)
    const rfcLimitLine = 'X'.repeat(998);
    
    const emailWithRfcLimitLine = 
      'From: sender@example.com\r\n' +
      'To: recipient@example.com\r\n' +
      'Subject: RFC Limit Test\r\n' +
      '\r\n' +
      'Line at RFC 5321 limit:\r\n' +
      rfcLimitLine + '\r\n' +
      'This should be accepted.\r\n' +
      '.\r\n';
    
    socket.write(emailWithRfcLimitLine);
    
    const finalResponse = await new Promise<string>((resolve) => {
      socket.once('data', (chunk) => resolve(chunk.toString()));
    });
    
    console.log(`Response to ${rfcLimitLine.length} character line (RFC limit):`, finalResponse);
    
    // This should be accepted
    expect(finalResponse).toInclude('250');
    
    // Clean up
    socket.write('QUIT\r\n');
    socket.end();
    
  } finally {
    done.resolve();
  }
});

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

export default tap.start();