/**
 * E2E-6: Memory Pressure — 200+ interactions across 10 tabs (#347)
 * Validates: Heap delta < 100MB from start, no unbounded growth in any state
 * structure, response time p95 < 2x initial p95.
 *
 * Distinct from memory-stability.e2e.ts (which tests 30-min continuous operation
 * with a 50MB heap limit). This test focuses on:
 *   - High interaction count (200+ calls) across many tabs on different domains
 *   - Response time percentile tracking (p95 < 2x initial)
 *   - Larger heap budget (100MB) for the more intensive workload
 */
import * as fs from 'fs';
import * as path from 'path';
import { MCPClient } from '../harness/mcp-client';
import { HeapSampler } from '../harness/heap-sampler';
import { sleep, scaled, JEST_OVERHEAD_MS } from '../harness/time-scale';

function getFixturePort(): number {
  const stateFile = path.join(process.cwd(), '.e2e-state.json');
  const state = JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
  return state.port;
}

/**
 * Compute the p95 of a sorted array of durations in ms.
 */
function p95(durations: number[]): number {
  if (durations.length === 0) return 0;
  const sorted = [...durations].sort((a, b) => a - b);
  const idx = Math.floor(sorted.length * 0.95);
  return sorted[Math.min(idx, sorted.length - 1)];
}

describe('E2E-6: Memory Pressure (#347)', () => {
  let mcp: MCPClient;

  beforeAll(async () => {
    mcp = new MCPClient({ timeoutMs: 60_000 });
    await mcp.start();
  }, 90_000);

  afterAll(async () => {
    await mcp.stop();
  }, 30_000);

  test('200+ interactions across 10 tabs: heap delta < 100MB and p95 response time stable (#347 spec)', async () => {
    const port = getFixturePort();

    // 10 distinct URLs across different "domains" (all same fixture server,
    // different paths simulate different site contexts)
    const urls = [
      `http://localhost:${port}/`,
      `http://localhost:${port}/site-a`,
      `http://localhost:${port}/site-b`,
      `http://localhost:${port}/site-c`,
      `http://localhost:${port}/login`,
      `http://localhost:${port}/protected`,
      `http://localhost:${port}/site-a`,
      `http://localhost:${port}/site-b`,
      `http://localhost:${port}/site-c`,
      `http://localhost:${port}/`,
    ];

    // Step 1: Take baseline heap measurement before any interactions
    const sampler = new HeapSampler({ pid: mcp.pid });
    sampler.takeBaseline();

    const initialHeap = process.memoryUsage();
    console.error(
      `[memory-pressure] Baseline heap: used=${(initialHeap.heapUsed / 1024 / 1024).toFixed(1)}MB, ` +
      `rss=${(initialHeap.rss / 1024 / 1024).toFixed(1)}MB`
    );

    // Step 2: Warm-up phase — first 20 interactions to measure initial p95
    console.error('[memory-pressure] Step 2: Warm-up phase (20 interactions)');
    const warmupDurations: number[] = [];
    for (let i = 0; i < 20; i++) {
      const url = urls[i % urls.length];
      const start = Date.now();
      try {
        const warmupNav = await mcp.callTool('navigate', { url });
        let warmupTabId: string | undefined;
        try {
          const warmupNavData = JSON.parse(warmupNav.content?.find((c: { text?: string }) => c.text)?.text || warmupNav.text || '{}');
          warmupTabId = warmupNavData.tabId;
        } catch { /* fall through without tabId */ }
        await mcp.callTool('read_page', warmupTabId ? { tabId: warmupTabId } : {});
        warmupDurations.push(Date.now() - start);
      } catch (err) {
        console.error(`[memory-pressure] Warm-up interaction ${i} error: ${(err as Error).message}`);
        warmupDurations.push(Date.now() - start);
      }
    }

    const warmupP95 = p95(warmupDurations);
    console.error(`[memory-pressure] Step 2 OK: Warm-up p95=${warmupP95}ms (${warmupDurations.length} samples)`);
    sampler.takeSample();

    // Step 3: Main load — 200 interactions across 10 tabs
    console.error('[memory-pressure] Step 3: Main load phase (200 interactions across 10 tabs)');
    const mainDurations: number[] = [];
    let successCount = 0;
    let errorCount = 0;

    const TARGET_INTERACTIONS = scaled(200);
    for (let i = 0; i < TARGET_INTERACTIONS; i++) {
      const url = urls[i % urls.length];
      const start = Date.now();
      try {
        const mainNav = await mcp.callTool('navigate', { url });
        let mainTabId: string | undefined;
        try {
          const mainNavData = JSON.parse(mainNav.content?.find((c: { text?: string }) => c.text)?.text || mainNav.text || '{}');
          mainTabId = mainNavData.tabId;
        } catch { /* fall through without tabId */ }
        await mcp.callTool('read_page', mainTabId ? { tabId: mainTabId } : {});
        successCount++;
      } catch (err) {
        errorCount++;
        console.error(`[memory-pressure] Interaction ${i} error: ${(err as Error).message}`);
      }
      mainDurations.push(Date.now() - start);

      // Sample heap every 50 interactions
      if ((i + 1) % 50 === 0) {
        sampler.takeSample();
        const delta = sampler.getDelta();
        const heapDeltaMB = delta.heapUsedDelta / (1024 * 1024);
        const currentP95 = p95(mainDurations);
        console.error(
          `[memory-pressure] Interaction ${i + 1}/${TARGET_INTERACTIONS}: ` +
          `heap delta=${(heapDeltaMB / 1024).toFixed(1)}MB, ` +
          `p95=${currentP95}ms, ` +
          `successes=${successCount}, errors=${errorCount}`
        );
      }

      // Small delay between interactions to avoid overwhelming Chrome
      if (i % 10 === 9) {
        await sleep(100);
      }
    }

    console.error(
      `[memory-pressure] Step 3 OK: ${successCount} successes, ${errorCount} errors ` +
      `out of ${TARGET_INTERACTIONS} interactions`
    );

    // Step 4: Assert heap delta < 100MB per #347 spec
    console.error('[memory-pressure] Step 4: Asserting heap delta < 100MB');
    sampler.assertStable(100); // < 100MB delta from baseline
    console.error('[memory-pressure] Step 4 OK: Heap within 100MB limit');

    // Step 5: Assert response time p95 < 2x initial p95 per #347 spec
    console.error('[memory-pressure] Step 5: Asserting p95 response time stability');
    const finalP95 = p95(mainDurations);
    const p95Ratio = warmupP95 > 0 ? finalP95 / warmupP95 : 1;
    console.error(
      `[memory-pressure] Response time: warmup p95=${warmupP95}ms, ` +
      `final p95=${finalP95}ms, ratio=${p95Ratio.toFixed(2)}x`
    );
    // Allow for some variance — if warmup p95 is very small (< 100ms), use absolute check instead
    if (warmupP95 >= 100) {
      expect(finalP95).toBeLessThan(warmupP95 * 2);
    } else {
      // Warmup too fast to be a meaningful baseline; just verify p95 is under 10s
      expect(finalP95).toBeLessThan(10_000);
    }
    console.error('[memory-pressure] Step 5 OK: Response time p95 within 2x initial p95');

    console.error('[memory-pressure] All assertions passed — memory pressure spec PASS (#347)');
  }, scaled(600_000) + JEST_OVERHEAD_MS);
});
