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

const TEST_PORT = 2525;

let testServer;

const createConnection = async (): Promise<net.Socket> => {
  const socket = net.createConnection({
    host: 'localhost',
    port: TEST_PORT,
    timeout: 5000
  });

  await new Promise<void>((resolve, reject) => {
    socket.once('connect', resolve);
    socket.once('error', reject);
  });

  return socket;
};

// Helper function to wait for SMTP response
const waitForResponse = (socket: net.Socket, expectedCode?: string, timeout = 5000): Promise<string> => {
  return new Promise((resolve, reject) => {
    let buffer = '';
    const timer = setTimeout(() => {
      socket.removeListener('data', handler);
      reject(new Error(`Timeout waiting for ${expectedCode || 'any'} response`));
    }, timeout);
    
    const handler = (data: Buffer) => {
      buffer += data.toString();
      const lines = buffer.split('\r\n');
      
      // Check if we have a complete response
      for (const line of lines) {
        if (expectedCode) {
          if (line.startsWith(expectedCode + ' ')) {
            clearTimeout(timer);
            socket.removeListener('data', handler);
            resolve(buffer);
            return;
          }
        } else {
          // Any complete response line
          if (line.match(/^\d{3} /)) {
            clearTimeout(timer);
            socket.removeListener('data', handler);
            resolve(buffer);
            return;
          }
        }
      }
    };
    
    socket.on('data', handler);
  });
};

const getResponse = waitForResponse;

const testBasicSmtpFlow = async (socket: net.Socket): Promise<boolean> => {
  try {
    await waitForResponse(socket, '220');

    socket.write('EHLO test.example.com\r\n');
    const ehloResp = await waitForResponse(socket, '250');
    if (!ehloResp.includes('250')) return false;

    socket.write('MAIL FROM:<test@example.com>\r\n');
    const mailResp = await waitForResponse(socket, '250');
    if (!mailResp.includes('250')) return false;

    socket.write('RCPT TO:<recipient@example.com>\r\n');
    const rcptResp = await waitForResponse(socket, '250');
    if (!rcptResp.includes('250')) return false;

    socket.write('DATA\r\n');
    const dataResp = await waitForResponse(socket, '354');
    if (!dataResp.includes('354')) return false;

    const testEmail = [
      'From: test@example.com',
      'To: recipient@example.com',
      'Subject: Interruption Recovery Test',
      '',
      'This email tests server recovery after network interruption.',
      '.',
      ''
    ].join('\r\n');

    socket.write(testEmail);
    const finalResp = await waitForResponse(socket, '250');

    socket.write('QUIT\r\n');
    socket.end();

    return finalResp.includes('250');
  } catch (error) {
    return false;
  }
};

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

tap.test('REL-06: Network interruption - Sudden connection drop', async (tools) => {
  const done = tools.defer();

  try {
    console.log('Testing sudden connection drop during session...');

    // Phase 1: Create connection and drop it mid-session
    const socket1 = await createConnection();
    await waitForResponse(socket1, '220');

    socket1.write('EHLO testhost\r\n');
    await waitForResponse(socket1, '250');

    socket1.write('MAIL FROM:<sender@example.com>\r\n');
    await waitForResponse(socket1, '250');

    // Abruptly close connection during active session
    socket1.destroy();
    console.log('  Connection abruptly closed');

    // Phase 2: Test recovery
    await new Promise(resolve => setTimeout(resolve, 1000));

    const socket2 = await createConnection();
    const recoverySuccess = await testBasicSmtpFlow(socket2);

    expect(recoverySuccess).toEqual(true);
    console.log('✓ Server recovered from sudden connection drop');
    done.resolve();
  } catch (error) {
    done.reject(error);
  }
});

tap.test('REL-06: Network interruption - Data transfer interruption', async (tools) => {
  const done = tools.defer();

  try {
    console.log('\nTesting connection interruption during data transfer...');

    const socket = await createConnection();
    await waitForResponse(socket, '220');

    socket.write('EHLO datatest\r\n');
    await waitForResponse(socket, '250');

    socket.write('MAIL FROM:<sender@example.com>\r\n');
    await waitForResponse(socket, '250');

    socket.write('RCPT TO:<recipient@example.com>\r\n');
    await waitForResponse(socket, '250');

    socket.write('DATA\r\n');
    const dataResp = await waitForResponse(socket, '354');
    expect(dataResp).toInclude('354');

    // Start sending data but interrupt midway
    socket.write('From: sender@example.com\r\n');
    socket.write('To: recipient@example.com\r\n');
    socket.write('Subject: Interruption Test\r\n\r\n');
    socket.write('This email will be interrupted...\r\n');

    // Wait briefly then destroy connection (simulating network loss)
    await new Promise(resolve => setTimeout(resolve, 500));
    socket.destroy();
    console.log('  Connection interrupted during data transfer');

    // Test recovery
    await new Promise(resolve => setTimeout(resolve, 1500));

    const newSocket = await createConnection();
    const recoverySuccess = await testBasicSmtpFlow(newSocket);

    expect(recoverySuccess).toEqual(true);
    console.log('✓ Server recovered from data transfer interruption');
    done.resolve();
  } catch (error) {
    done.reject(error);
  }
});

tap.test('REL-06: Network interruption - Rapid reconnection attempts', async (tools) => {
  const done = tools.defer();
  const connections: net.Socket[] = [];

  try {
    console.log('\nTesting rapid reconnection after interruptions...');

    // Create and immediately destroy multiple connections
    console.log('  Creating 5 unstable connections...');
    for (let i = 0; i < 5; i++) {
      try {
        const socket = net.createConnection({
          host: 'localhost',
          port: TEST_PORT,
          timeout: 2000
        });
        
        connections.push(socket);
        
        // Destroy after short random delay to simulate instability
        setTimeout(() => socket.destroy(), 50 + Math.random() * 150);
        
        await new Promise(resolve => setTimeout(resolve, 50));
      } catch (error) {
        // Expected - some connections might fail
      }
    }

    // Wait for cleanup
    await new Promise(resolve => setTimeout(resolve, 2000));

    // Now test if server can handle normal connections
    let successfulConnections = 0;
    console.log('  Testing recovery with stable connections...');
    
    for (let i = 0; i < 3; i++) {
      try {
        const socket = await createConnection();
        const success = await testBasicSmtpFlow(socket);
        
        if (success) {
          successfulConnections++;
        }
      } catch (error) {
        console.log(`  Connection ${i + 1} failed:`, error.message);
      }
      
      await new Promise(resolve => setTimeout(resolve, 500));
    }

    const recoveryRate = successfulConnections / 3;
    console.log(`  Recovery rate: ${successfulConnections}/3 (${(recoveryRate * 100).toFixed(0)}%)`);

    expect(recoveryRate).toBeGreaterThanOrEqual(0.66); // At least 2/3 should succeed
    console.log('✓ Server recovered from rapid reconnection attempts');
    done.resolve();
  } catch (error) {
    connections.forEach(conn => conn.destroy());
    done.reject(error);
  }
});

tap.test('REL-06: Network interruption - Partial command interruption', async (tools) => {
  const done = tools.defer();

  try {
    console.log('\nTesting partial command transmission interruption...');

    const socket = await createConnection();
    await waitForResponse(socket, '220');

    // Send partial EHLO command and interrupt
    socket.write('EH');
    console.log('  Sent partial command "EH"');
    
    await new Promise(resolve => setTimeout(resolve, 100));
    socket.destroy();
    console.log('  Connection destroyed with incomplete command');

    // Test recovery
    await new Promise(resolve => setTimeout(resolve, 1000));

    const newSocket = await createConnection();
    const recoverySuccess = await testBasicSmtpFlow(newSocket);

    expect(recoverySuccess).toEqual(true);
    console.log('✓ Server recovered from partial command interruption');
    done.resolve();
  } catch (error) {
    done.reject(error);
  }
});

tap.test('REL-06: Network interruption - Multiple interruption types', async (tools) => {
  const done = tools.defer();
  const results: Array<{ type: string; recovered: boolean }> = [];

  try {
    console.log('\nTesting recovery from multiple interruption types...');

    // Test 1: Interrupt after greeting
    try {
      const socket = await createConnection();
      await waitForResponse(socket, '220');
      socket.destroy();
      results.push({ type: 'after-greeting', recovered: false });
    } catch (e) {
      results.push({ type: 'after-greeting', recovered: false });
    }

    await new Promise(resolve => setTimeout(resolve, 500));

    // Test 2: Interrupt during EHLO
    try {
      const socket = await createConnection();
      await waitForResponse(socket, '220');
      socket.write('EHLO te');
      socket.destroy();
      results.push({ type: 'during-ehlo', recovered: false });
    } catch (e) {
      results.push({ type: 'during-ehlo', recovered: false });
    }

    await new Promise(resolve => setTimeout(resolve, 500));

    // Test 3: Interrupt with invalid data
    try {
      const socket = await createConnection();
      await waitForResponse(socket, '220');
      socket.write('\x00\x01\x02\x03');
      socket.destroy();
      results.push({ type: 'invalid-data', recovered: false });
    } catch (e) {
      results.push({ type: 'invalid-data', recovered: false });
    }

    await new Promise(resolve => setTimeout(resolve, 1000));

    // Test final recovery
    try {
      const socket = await createConnection();
      const success = await testBasicSmtpFlow(socket);
      
      if (success) {
        // Mark all previous tests as recovered
        results.forEach(r => r.recovered = true);
      }
    } catch (error) {
      console.log('Final recovery failed:', error.message);
    }

    const recoveredCount = results.filter(r => r.recovered).length;
    console.log(`\nInterruption recovery summary:`);
    results.forEach(r => {
      console.log(`  ${r.type}: ${r.recovered ? 'recovered' : 'failed'}`);
    });

    expect(recoveredCount).toBeGreaterThan(0);
    console.log(`✓ Server recovered from ${recoveredCount}/${results.length} interruption scenarios`);
    done.resolve();
  } catch (error) {
    done.reject(error);
  }
});

tap.test('REL-06: Network interruption - Long delay recovery', async (tools) => {
  const done = tools.defer();

  try {
    console.log('\nTesting recovery after long network interruption...');

    // Create connection and start transaction
    const socket = await createConnection();
    await waitForResponse(socket, '220');

    socket.write('EHLO longdelay\r\n');
    await waitForResponse(socket, '250');

    socket.write('MAIL FROM:<sender@example.com>\r\n');
    await waitForResponse(socket, '250');

    // Simulate long network interruption
    socket.pause();
    console.log('  Connection paused (simulating network freeze)');
    
    await new Promise(resolve => setTimeout(resolve, 5000)); // 5 second "freeze"
    
    // Try to continue - should fail
    socket.resume();
    socket.write('RCPT TO:<recipient@example.com>\r\n');
    
    let continuationFailed = false;
    try {
      await waitForResponse(socket, '250', 3000);
    } catch (error) {
      continuationFailed = true;
      console.log('  Continuation failed as expected');
    }

    socket.destroy();

    // Test recovery with new connection
    const newSocket = await createConnection();
    const recoverySuccess = await testBasicSmtpFlow(newSocket);

    expect(recoverySuccess).toEqual(true);
    console.log('✓ Server recovered after long network interruption');
    done.resolve();
  } catch (error) {
    done.reject(error);
  }
});

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

export default tap.start();