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;

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

tap.test('PERF-02: Concurrency testing - Multiple simultaneous connections', async (tools) => {
  const done = tools.defer();
  const concurrentCount = 20;
  const connectionResults: Array<{
    connectionId: number;
    success: boolean;
    duration: number;
    error?: string;
  }> = [];

  const createConcurrentConnection = (connectionId: number): Promise<void> => {
    return new Promise((resolve) => {
      const startTime = Date.now();
      const socket = net.createConnection({
        host: 'localhost',
        port: TEST_PORT,
        timeout: 10000
      });

      let state = 'connecting';
      let receivedData = '';

      const timeoutHandle = setTimeout(() => {
        socket.destroy();
        connectionResults.push({
          connectionId,
          success: false,
          duration: Date.now() - startTime,
          error: 'Connection timeout'
        });
        resolve();
      }, 10000);

      socket.on('connect', () => {
        state = 'connected';
      });

      socket.on('data', (chunk) => {
        receivedData += chunk.toString();
        const lines = receivedData.split('\r\n');

        for (const line of lines) {
          if (!line.trim()) continue;

          if (state === 'connected' && line.startsWith('220')) {
            state = 'ehlo';
            socket.write(`EHLO testhost-${connectionId}\r\n`);
          } else if (state === 'ehlo' && line.includes('250 ') && !line.includes('250-')) {
            // Final 250 response received
            state = 'quit';
            socket.write('QUIT\r\n');
          } else if (state === 'quit' && line.startsWith('221')) {
            clearTimeout(timeoutHandle);
            socket.end();
            connectionResults.push({
              connectionId,
              success: true,
              duration: Date.now() - startTime
            });
            resolve();
          }
        }
      });

      socket.on('error', (error) => {
        clearTimeout(timeoutHandle);
        connectionResults.push({
          connectionId,
          success: false,
          duration: Date.now() - startTime,
          error: error.message
        });
        resolve();
      });

      socket.on('close', () => {
        clearTimeout(timeoutHandle);
        if (!connectionResults.find(r => r.connectionId === connectionId)) {
          connectionResults.push({
            connectionId,
            success: false,
            duration: Date.now() - startTime,
            error: 'Connection closed unexpectedly'
          });
        }
        resolve();
      });
    });
  };

  try {
    // Create all concurrent connections
    const promises: Promise<void>[] = [];
    console.log(`Creating ${concurrentCount} concurrent connections...`);

    for (let i = 0; i < concurrentCount; i++) {
      promises.push(createConcurrentConnection(i));
      // Small stagger to avoid overwhelming the system
      if (i % 5 === 0) {
        await new Promise(resolve => setTimeout(resolve, 10));
      }
    }

    // Wait for all connections to complete
    await Promise.all(promises);

    // Analyze results
    const successful = connectionResults.filter(r => r.success).length;
    const failed = connectionResults.filter(r => !r.success).length;
    const successRate = successful / concurrentCount;
    const avgDuration = connectionResults
      .filter(r => r.success)
      .reduce((sum, r) => sum + r.duration, 0) / successful || 0;

    console.log(`\nConcurrency Test Results:`);
    console.log(`Total connections: ${concurrentCount}`);
    console.log(`Successful: ${successful} (${(successRate * 100).toFixed(1)}%)`);
    console.log(`Failed: ${failed}`);
    console.log(`Average duration: ${avgDuration.toFixed(0)}ms`);

    if (failed > 0) {
      const errors = connectionResults
        .filter(r => !r.success)
        .map(r => r.error)
        .filter((v, i, a) => a.indexOf(v) === i); // unique errors
      console.log(`Unique errors: ${errors.join(', ')}`);
    }

    // Success if at least 80% of connections succeed
    expect(successRate).toBeGreaterThanOrEqual(0.8);
    done.resolve();
  } catch (error) {
    done.reject(error);
  }
});

tap.test('PERF-02: Concurrency testing - Concurrent transactions', async (tools) => {
  const done = tools.defer();
  const transactionCount = 10;
  const transactionResults: Array<{
    transactionId: number;
    success: boolean;
    duration: number;
    error?: string;
  }> = [];

  const performConcurrentTransaction = (transactionId: number): Promise<void> => {
    return new Promise((resolve) => {
      const startTime = Date.now();
      const socket = net.createConnection({
        host: 'localhost',
        port: TEST_PORT,
        timeout: 15000
      });

      let state = 'connecting';

      const timeoutHandle = setTimeout(() => {
        socket.destroy();
        transactionResults.push({
          transactionId,
          success: false,
          duration: Date.now() - startTime,
          error: 'Transaction timeout'
        });
        resolve();
      }, 15000);

      const processResponse = async () => {
        try {
          // Read greeting
          await new Promise<void>((res) => {
            let greeting = '';
            const handleGreeting = (chunk: Buffer) => {
              greeting += chunk.toString();
              if (greeting.includes('220') && greeting.includes('\r\n')) {
                socket.removeListener('data', handleGreeting);
                res();
              }
            };
            socket.on('data', handleGreeting);
          });

          // Send EHLO
          socket.write(`EHLO testhost-tx-${transactionId}\r\n`);
          
          await new Promise<void>((res) => {
            let data = '';
            const handleData = (chunk: Buffer) => {
              data += chunk.toString();
              // Look for the end of EHLO response (250 without dash)
              if (data.includes('250 ')) {
                socket.removeListener('data', handleData);
                res();
              }
            };
            socket.on('data', handleData);
          });

          // Complete email transaction
          socket.write(`MAIL FROM:<sender${transactionId}@example.com>\r\n`);
          
          await new Promise<void>((res, rej) => {
            let mailResponse = '';
            const handleMailResponse = (chunk: Buffer) => {
              mailResponse += chunk.toString();
              if (mailResponse.includes('\r\n')) {
                socket.removeListener('data', handleMailResponse);
                if (!mailResponse.includes('250')) {
                  rej(new Error('MAIL FROM failed'));
                } else {
                  res();
                }
              }
            };
            socket.on('data', handleMailResponse);
          });

          socket.write(`RCPT TO:<recipient${transactionId}@example.com>\r\n`);
          
          await new Promise<void>((res, rej) => {
            let rcptResponse = '';
            const handleRcptResponse = (chunk: Buffer) => {
              rcptResponse += chunk.toString();
              if (rcptResponse.includes('\r\n')) {
                socket.removeListener('data', handleRcptResponse);
                if (!rcptResponse.includes('250')) {
                  rej(new Error('RCPT TO failed'));
                } else {
                  res();
                }
              }
            };
            socket.on('data', handleRcptResponse);
          });

          socket.write('DATA\r\n');
          
          await new Promise<void>((res, rej) => {
            let dataResponse = '';
            const handleDataResponse = (chunk: Buffer) => {
              dataResponse += chunk.toString();
              if (dataResponse.includes('\r\n')) {
                socket.removeListener('data', handleDataResponse);
                if (!dataResponse.includes('354')) {
                  rej(new Error('DATA command failed'));
                } else {
                  res();
                }
              }
            };
            socket.on('data', handleDataResponse);
          });

          // Send email content
          const emailContent = [
            `From: sender${transactionId}@example.com`,
            `To: recipient${transactionId}@example.com`,
            `Subject: Concurrent test ${transactionId}`,
            '',
            `This is concurrent test message ${transactionId}`,
            '.',
            ''
          ].join('\r\n');

          socket.write(emailContent);

          await new Promise<void>((res, rej) => {
            let submitResponse = '';
            const handleSubmitResponse = (chunk: Buffer) => {
              submitResponse += chunk.toString();
              if (submitResponse.includes('\r\n') && submitResponse.includes('250')) {
                socket.removeListener('data', handleSubmitResponse);
                res();
              } else if (submitResponse.includes('\r\n') && (submitResponse.includes('4') || submitResponse.includes('5'))) {
                socket.removeListener('data', handleSubmitResponse);
                rej(new Error('Message submission failed'));
              }
            };
            socket.on('data', handleSubmitResponse);
          });

          socket.write('QUIT\r\n');
          
          await new Promise<void>((res) => {
            socket.once('data', () => res());
          });

          clearTimeout(timeoutHandle);
          socket.end();

          transactionResults.push({
            transactionId,
            success: true,
            duration: Date.now() - startTime
          });
          resolve();

        } catch (error) {
          clearTimeout(timeoutHandle);
          socket.end();
          const errorMsg = error instanceof Error ? error.message : 'Unknown error';
          console.log(`Transaction ${transactionId} failed: ${errorMsg}`);
          transactionResults.push({
            transactionId,
            success: false,
            duration: Date.now() - startTime,
            error: errorMsg
          });
          resolve();
        }
      };

      socket.on('connect', () => {
        state = 'connected';
        processResponse();
      });

      socket.on('error', (error) => {
        clearTimeout(timeoutHandle);
        if (!transactionResults.find(r => r.transactionId === transactionId)) {
          transactionResults.push({
            transactionId,
            success: false,
            duration: Date.now() - startTime,
            error: error.message
          });
        }
        resolve();
      });
    });
  };

  try {
    // Create concurrent transactions
    const promises: Promise<void>[] = [];
    console.log(`\nStarting ${transactionCount} concurrent email transactions...`);

    for (let i = 0; i < transactionCount; i++) {
      promises.push(performConcurrentTransaction(i));
      // Small stagger
      await new Promise(resolve => setTimeout(resolve, 50));
    }

    // Wait for all transactions
    await Promise.all(promises);

    // Analyze results
    const successful = transactionResults.filter(r => r.success).length;
    const failed = transactionResults.filter(r => !r.success).length;
    const successRate = successful / transactionCount;
    const avgDuration = transactionResults
      .filter(r => r.success)
      .reduce((sum, r) => sum + r.duration, 0) / successful || 0;

    console.log(`\nConcurrent Transaction Results:`);
    console.log(`Total transactions: ${transactionCount}`);
    console.log(`Successful: ${successful} (${(successRate * 100).toFixed(1)}%)`);
    console.log(`Failed: ${failed}`);
    console.log(`Average duration: ${avgDuration.toFixed(0)}ms`);

    // Success if at least 80% of transactions complete
    expect(successRate).toBeGreaterThanOrEqual(0.8);
    done.resolve();
  } catch (error) {
    done.reject(error);
  }
});

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

export default tap.start();