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('REL-01: Long-running operation - Continuous email sending', async (tools) => {
  const done = tools.defer();
  const testDuration = 30000; // 30 seconds
  const operationInterval = 2000; // 2 seconds between operations
  const startTime = Date.now();
  const endTime = startTime + testDuration;
  
  let operations = 0;
  let successful = 0;
  let errors = 0;
  let connectionIssues = 0;
  const operationResults: Array<{
    operation: number;
    success: boolean;
    duration: number;
    error?: string;
    timestamp: number;
  }> = [];

  console.log(`Running long-duration test for ${testDuration/1000} seconds...`);

  const performOperation = async (operationId: number): Promise<void> => {
    const operationStart = Date.now();
    operations++;

    try {
      const socket = net.createConnection({
        host: 'localhost',
        port: TEST_PORT,
        timeout: 10000
      });

      const result = await new Promise<{ success: boolean; error?: string; connectionIssue?: boolean }>((resolve) => {
        let step = 'connecting';
        let receivedData = '';

        const timeout = setTimeout(() => {
          socket.destroy();
          resolve({
            success: false,
            error: `Timeout in step ${step}`,
            connectionIssue: true
          });
        }, 10000);

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

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

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

            // Check for errors
            if (line.match(/^[45]\d\d\s/)) {
              clearTimeout(timeout);
              socket.destroy();
              resolve({
                success: false,
                error: `SMTP error in ${step}: ${line}`,
                connectionIssue: false
              });
              return;
            }

            // Process responses
            if (step === 'connected' && line.startsWith('220')) {
              step = 'ehlo';
              socket.write(`EHLO longrun-${operationId}\r\n`);
            } else if (step === 'ehlo' && line.includes('250 ') && !line.includes('250-')) {
              step = 'mail_from';
              socket.write(`MAIL FROM:<sender${operationId}@example.com>\r\n`);
            } else if (step === 'mail_from' && line.startsWith('250')) {
              step = 'rcpt_to';
              socket.write(`RCPT TO:<recipient${operationId}@example.com>\r\n`);
            } else if (step === 'rcpt_to' && line.startsWith('250')) {
              step = 'data';
              socket.write('DATA\r\n');
            } else if (step === 'data' && line.startsWith('354')) {
              step = 'email_content';
              const emailContent = [
                `From: sender${operationId}@example.com`,
                `To: recipient${operationId}@example.com`,
                `Subject: Long Running Test Operation ${operationId}`,
                `Date: ${new Date().toUTCString()}`,
                '',
                `This is test operation ${operationId} for long-running reliability testing.`,
                `Timestamp: ${Date.now()}`,
                '.',
                ''
              ].join('\r\n');
              socket.write(emailContent);
            } else if (step === 'email_content' && line.startsWith('250')) {
              step = 'quit';
              socket.write('QUIT\r\n');
            } else if (step === 'quit' && line.startsWith('221')) {
              clearTimeout(timeout);
              socket.end();
              resolve({
                success: true
              });
              return;
            }
          }
        });

        socket.on('error', (error) => {
          clearTimeout(timeout);
          resolve({
            success: false,
            error: error.message,
            connectionIssue: true
          });
        });

        socket.on('close', () => {
          if (step !== 'quit') {
            clearTimeout(timeout);
            resolve({
              success: false,
              error: 'Connection closed unexpectedly',
              connectionIssue: true
            });
          }
        });
      });

      const duration = Date.now() - operationStart;

      if (result.success) {
        successful++;
      } else {
        errors++;
        if (result.connectionIssue) {
          connectionIssues++;
        }
      }

      operationResults.push({
        operation: operationId,
        success: result.success,
        duration,
        error: result.error,
        timestamp: operationStart
      });

    } catch (error) {
      errors++;
      operationResults.push({
        operation: operationId,
        success: false,
        duration: Date.now() - operationStart,
        error: error instanceof Error ? error.message : 'Unknown error',
        timestamp: operationStart
      });
    }
  };

  try {
    // Run operations continuously until end time
    while (Date.now() < endTime) {
      const operationStart = Date.now();
      
      await performOperation(operations + 1);

      // Calculate wait time for next operation
      const nextOperation = operationStart + operationInterval;
      const waitTime = nextOperation - Date.now();

      if (waitTime > 0 && Date.now() < endTime) {
        await new Promise(resolve => setTimeout(resolve, waitTime));
      }

      // Progress update every 5 operations
      if (operations % 5 === 0) {
        console.log(`Progress: ${operations} operations, ${successful} successful, ${errors} errors`);
      }
    }

    // Calculate results
    const totalDuration = Date.now() - startTime;
    const successRate = successful / operations;
    const connectionIssueRate = connectionIssues / operations;
    const avgOperationTime = operationResults.reduce((sum, r) => sum + r.duration, 0) / operations;

    console.log(`\nLong-Running Operation Results:`);
    console.log(`Total duration: ${(totalDuration/1000).toFixed(1)}s`);
    console.log(`Total operations: ${operations}`);
    console.log(`Successful: ${successful} (${(successRate * 100).toFixed(1)}%)`);
    console.log(`Errors: ${errors}`);
    console.log(`Connection issues: ${connectionIssues} (${(connectionIssueRate * 100).toFixed(1)}%)`);
    console.log(`Average operation time: ${avgOperationTime.toFixed(0)}ms`);

    // Show last few operations for debugging
    console.log('\nLast 5 operations:');
    operationResults.slice(-5).forEach(op => {
      console.log(`  Op ${op.operation}: ${op.success ? 'success' : 'failed'} (${op.duration}ms)${op.error ? ' - ' + op.error : ''}`);
    });

    // Test passes with 85% success rate and max 10% connection issues
    expect(successRate).toBeGreaterThanOrEqual(0.85);
    expect(connectionIssueRate).toBeLessThanOrEqual(0.1);
    done.resolve();
  } catch (error) {
    done.reject(error);
  }
});

tap.test('REL-01: Long-running operation - Server stability check', async (tools) => {
  const done = tools.defer();
  const checkDuration = 15000; // 15 seconds
  const checkInterval = 3000; // 3 seconds between checks
  const startTime = Date.now();
  const endTime = startTime + checkDuration;
  
  const stabilityChecks: Array<{
    timestamp: number;
    responseTime: number;
    success: boolean;
    error?: string;
  }> = [];

  console.log(`\nRunning server stability checks for ${checkDuration/1000} seconds...`);

  try {
    while (Date.now() < endTime) {
      const checkStart = Date.now();
      
      const socket = net.createConnection({
        host: 'localhost',
        port: TEST_PORT,
        timeout: 5000
      });

      const checkResult = await new Promise<{ success: boolean; responseTime: number; error?: string }>((resolve) => {
        const connectTime = Date.now();
        let greetingReceived = false;

        const timeout = setTimeout(() => {
          socket.destroy();
          resolve({
            success: false,
            responseTime: Date.now() - connectTime,
            error: 'Timeout waiting for greeting'
          });
        }, 5000);

        socket.on('connect', () => {
          // Connected
        });

        socket.once('data', (chunk) => {
          const response = chunk.toString();
          clearTimeout(timeout);
          greetingReceived = true;
          
          if (response.startsWith('220')) {
            socket.write('QUIT\r\n');
            socket.end();
            resolve({
              success: true,
              responseTime: Date.now() - connectTime
            });
          } else {
            socket.end();
            resolve({
              success: false,
              responseTime: Date.now() - connectTime,
              error: `Unexpected greeting: ${response.substring(0, 50)}`
            });
          }
        });

        socket.on('error', (error) => {
          clearTimeout(timeout);
          resolve({
            success: false,
            responseTime: Date.now() - connectTime,
            error: error.message
          });
        });
      });

      stabilityChecks.push({
        timestamp: checkStart,
        responseTime: checkResult.responseTime,
        success: checkResult.success,
        error: checkResult.error
      });

      console.log(`Stability check ${stabilityChecks.length}: ${checkResult.success ? 'OK' : 'FAILED'} (${checkResult.responseTime}ms)`);

      // Wait for next check
      const nextCheck = checkStart + checkInterval;
      const waitTime = nextCheck - Date.now();
      if (waitTime > 0 && Date.now() < endTime) {
        await new Promise(resolve => setTimeout(resolve, waitTime));
      }
    }

    // Analyze stability
    const successfulChecks = stabilityChecks.filter(c => c.success).length;
    const avgResponseTime = stabilityChecks
      .filter(c => c.success)
      .reduce((sum, c) => sum + c.responseTime, 0) / successfulChecks || 0;
    const maxResponseTime = Math.max(...stabilityChecks.filter(c => c.success).map(c => c.responseTime));

    console.log(`\nStability Check Results:`);
    console.log(`Total checks: ${stabilityChecks.length}`);
    console.log(`Successful: ${successfulChecks} (${(successfulChecks/stabilityChecks.length * 100).toFixed(1)}%)`);
    console.log(`Average response time: ${avgResponseTime.toFixed(0)}ms`);
    console.log(`Max response time: ${maxResponseTime}ms`);

    // All checks should succeed for stable server
    expect(successfulChecks).toEqual(stabilityChecks.length);
    expect(avgResponseTime).toBeLessThan(1000);
    done.resolve();
  } catch (error) {
    done.reject(error);
  }
});

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

export default tap.start();