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;

interface ResourceMetrics {
  timestamp: number;
  memoryUsage: {
    rss: number;
    heapTotal: number;
    heapUsed: number;
    external: number;
  };
  processInfo: {
    pid: number;
    uptime: number;
    cpuUsage: NodeJS.CpuUsage;
  };
}

interface LeakAnalysis {
  memoryGrowthMB: number;
  memoryTrend: number;
  stabilityScore: number;
  memoryLeakDetected: boolean;
  resourcesStable: boolean;
  samplesAnalyzed: number;
  initialMemoryMB: number;
  finalMemoryMB: number;
}

const captureResourceMetrics = async (): Promise<ResourceMetrics> => {
  // Force GC if available before measurement
  if (global.gc) {
    global.gc();
    await new Promise(resolve => setTimeout(resolve, 100));
  }

  const memUsage = process.memoryUsage();
  
  return {
    timestamp: Date.now(),
    memoryUsage: {
      rss: Math.round(memUsage.rss / 1024 / 1024 * 100) / 100,
      heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024 * 100) / 100,
      heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024 * 100) / 100,
      external: Math.round(memUsage.external / 1024 / 1024 * 100) / 100
    },
    processInfo: {
      pid: process.pid,
      uptime: process.uptime(),
      cpuUsage: process.cpuUsage()
    }
  };
};

// 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 analyzeResourceLeaks = (initial: ResourceMetrics, samples: Array<{ operation: number; metrics: ResourceMetrics }>, final: ResourceMetrics): LeakAnalysis => {
  const memoryGrowthMB = final.memoryUsage.heapUsed - initial.memoryUsage.heapUsed;
  
  // Analyze memory trend over samples
  let memoryTrend = 0;
  if (samples.length > 1) {
    const firstSample = samples[0].metrics.memoryUsage.heapUsed;
    const lastSample = samples[samples.length - 1].metrics.memoryUsage.heapUsed;
    memoryTrend = lastSample - firstSample;
  }
  
  // Calculate stability score based on memory variance
  let stabilityScore = 1.0;
  if (samples.length > 2) {
    const memoryValues = samples.map(s => s.metrics.memoryUsage.heapUsed);
    const average = memoryValues.reduce((a, b) => a + b, 0) / memoryValues.length;
    const variance = memoryValues.reduce((acc, val) => acc + Math.pow(val - average, 2), 0) / memoryValues.length;
    const stdDev = Math.sqrt(variance);
    stabilityScore = Math.max(0, 1 - (stdDev / average));
  }
  
  return {
    memoryGrowthMB: Math.round(memoryGrowthMB * 100) / 100,
    memoryTrend: Math.round(memoryTrend * 100) / 100,
    stabilityScore: Math.round(stabilityScore * 100) / 100,
    memoryLeakDetected: memoryGrowthMB > 50,
    resourcesStable: stabilityScore > 0.8 && memoryGrowthMB < 25,
    samplesAnalyzed: samples.length,
    initialMemoryMB: initial.memoryUsage.heapUsed,
    finalMemoryMB: final.memoryUsage.heapUsed
  };
};

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

tap.test('REL-03: Resource leak detection - Memory leak analysis', async (tools) => {
  const done = tools.defer();
  const operationCount = 20;
  const connections: net.Socket[] = [];
  const samples: Array<{ operation: number; metrics: ResourceMetrics }> = [];

  try {
    const initialMetrics = await captureResourceMetrics();
    console.log(`📊 Initial memory: ${initialMetrics.memoryUsage.heapUsed}MB`);

    for (let i = 0; i < operationCount; i++) {
      console.log(`🔄 Operation ${i + 1}/${operationCount}...`);

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

      connections.push(socket);

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

      // Read greeting
      await waitForResponse(socket, '220');

      // Send EHLO
      socket.write(`EHLO leaktest-${i}\r\n`);
      await waitForResponse(socket, '250');

      // Complete email transaction
      socket.write(`MAIL FROM:<sender${i}@example.com>\r\n`);
      const mailResp = await waitForResponse(socket, '250');
      expect(mailResp).toInclude('250');

      socket.write(`RCPT TO:<recipient${i}@example.com>\r\n`);
      const rcptResp = await waitForResponse(socket, '250');
      expect(rcptResp).toInclude('250');

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

      const emailContent = [
        `From: sender${i}@example.com`,
        `To: recipient${i}@example.com`,
        `Subject: Resource Leak Test ${i + 1}`,
        `Message-ID: <leak-test-${i}-${Date.now()}@example.com>`,
        '',
        `This is resource leak test iteration ${i + 1}.`,
        '.',
        ''
      ].join('\r\n');

      socket.write(emailContent);
      const sendResp = await waitForResponse(socket, '250');
      expect(sendResp).toInclude('250');

      socket.write('QUIT\r\n');
      await waitForResponse(socket, '221');
      socket.end();

      // Capture metrics every 5 operations
      if ((i + 1) % 5 === 0) {
        const metrics = await captureResourceMetrics();
        samples.push({
          operation: i + 1,
          metrics
        });
        console.log(`📈 Sample ${samples.length}: Memory ${metrics.memoryUsage.heapUsed}MB`);
      }

      // Small delay between operations
      await new Promise(resolve => setTimeout(resolve, 100));
    }

    // Clean up all connections
    connections.forEach(conn => {
      if (!conn.destroyed) {
        conn.destroy();
      }
    });

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

    const finalMetrics = await captureResourceMetrics();
    const leakAnalysis = analyzeResourceLeaks(initialMetrics, samples, finalMetrics);

    console.log('\n📊 Resource Leak Analysis:');
    console.log(`Initial memory: ${leakAnalysis.initialMemoryMB}MB`);
    console.log(`Final memory: ${leakAnalysis.finalMemoryMB}MB`);
    console.log(`Memory growth: ${leakAnalysis.memoryGrowthMB}MB`);
    console.log(`Memory trend: ${leakAnalysis.memoryTrend}MB`);
    console.log(`Stability score: ${leakAnalysis.stabilityScore}`);
    console.log(`Memory leak detected: ${leakAnalysis.memoryLeakDetected}`);
    console.log(`Resources stable: ${leakAnalysis.resourcesStable}`);

    expect(leakAnalysis.memoryLeakDetected).toEqual(false);
    expect(leakAnalysis.resourcesStable).toEqual(true);
    done.resolve();
  } catch (error) {
    connections.forEach(conn => conn.destroy());
    done.reject(error);
  }
});

tap.test('REL-03: Resource leak detection - Connection leak test', async (tools) => {
  const done = tools.defer();
  const abandonedConnections: net.Socket[] = [];

  try {
    console.log('\nTesting for connection resource leaks...');

    // Create connections that are abandoned without proper cleanup
    for (let i = 0; i < 10; i++) {
      const socket = net.createConnection({
        host: 'localhost',
        port: TEST_PORT,
        timeout: 30000
      });

      abandonedConnections.push(socket);

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

      // Read greeting but don't complete transaction
      await new Promise<void>((resolve) => {
        socket.once('data', () => resolve());
      });

      // Start but don't complete EHLO
      socket.write(`EHLO abandoned-${i}\r\n`);
      
      // Don't wait for response, just move to next
      await new Promise(resolve => setTimeout(resolve, 50));
    }

    console.log('Created 10 abandoned connections');

    // Wait a bit
    await new Promise(resolve => setTimeout(resolve, 2000));

    // Try to create new connections - should still work
    let newConnectionsSuccessful = 0;
    for (let i = 0; i < 5; i++) {
      try {
        const socket = net.createConnection({
          host: 'localhost',
          port: TEST_PORT,
          timeout: 5000
        });

        await new Promise<void>((resolve, reject) => {
          const timeout = setTimeout(() => {
            socket.destroy();
            reject(new Error('Connection timeout'));
          }, 5000);

          socket.once('connect', () => {
            clearTimeout(timeout);
            resolve();
          });
          socket.once('error', (err) => {
            clearTimeout(timeout);
            reject(err);
          });
        });

        // Verify connection works
        const greeting = await new Promise<string>((resolve) => {
          socket.once('data', (chunk) => {
            resolve(chunk.toString());
          });
        });

        if (greeting.includes('220')) {
          newConnectionsSuccessful++;
          socket.write('QUIT\r\n');
          socket.end();
        }
      } catch (error) {
        console.log(`New connection ${i + 1} failed:`, error.message);
      }
    }

    // Clean up abandoned connections
    abandonedConnections.forEach(conn => conn.destroy());

    console.log(`New connections successful: ${newConnectionsSuccessful}/5`);

    // Server should still accept new connections despite abandoned ones
    expect(newConnectionsSuccessful).toBeGreaterThanOrEqual(4);
    done.resolve();
  } catch (error) {
    abandonedConnections.forEach(conn => conn.destroy());
    done.reject(error);
  }
});

tap.test('REL-03: Resource leak detection - Rapid create/destroy cycles', async (tools) => {
  const done = tools.defer();
  const cycles = 30;
  const initialMetrics = await captureResourceMetrics();

  try {
    console.log('\nTesting rapid connection create/destroy cycles...');

    for (let i = 0; i < cycles; i++) {
      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);
      });

      // Immediately destroy after connect
      socket.destroy();

      // Very short delay
      await new Promise(resolve => setTimeout(resolve, 20));

      if ((i + 1) % 10 === 0) {
        console.log(`Completed ${i + 1} cycles`);
      }
    }

    // Wait for resources to be released
    await new Promise(resolve => setTimeout(resolve, 3000));

    const finalMetrics = await captureResourceMetrics();
    const memoryGrowth = finalMetrics.memoryUsage.heapUsed - initialMetrics.memoryUsage.heapUsed;

    console.log(`Memory growth after ${cycles} cycles: ${memoryGrowth.toFixed(2)}MB`);

    // Memory growth should be minimal
    expect(memoryGrowth).toBeLessThan(10);
    done.resolve();
  } catch (error) {
    done.reject(error);
  }
});

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

export default tap.start();