import assert from 'node:assert/strict';
import { describe, test, before, after } from 'node:test';
import { CondensedGraph } from '../../src/core/CondensedGraph.js';

const runPerf = process.env.RUN_PERF_TESTS === '1';
const perfTest = runPerf ? test : test.skip;

describe('CondensedGraph - Performance & Memory Benchmarks', () => {
  const sizes = [1000, 10000, 50000];
  const results = [];

  sizes.forEach(size => {
    perfTest(`memory efficiency - ${size} edges`, () => {
      const graph = new CondensedGraph();
      
      const startMem = process.memoryUsage().heapUsed;
      
      // Add edges with realistic distribution
      for (let i = 0; i < size; i++) {
        graph.addEdge(
          `user:${i % 100}`,
          ['owner', 'member', 'viewer', 'editor'][i % 4],
          `doc:${i % 500}`
        );
      }

      graph.finalizePerfectHash();
      graph.finalizeWaveletAdjacency({ dropAdjacencyList: true });
      
      const endMem = process.memoryUsage().heapUsed;
      const memDelta = endMem - startMem;
      const stats = graph.getStats();
      
      results.push({
        size,
        memDelta,
        bytesPerEdge: memDelta / size,
        stats
      });
      
      console.log(`\nMemory (${size} edges):`);
      console.log(`  Heap delta: ${(memDelta / 1024 / 1024).toFixed(2)} MB`);
      console.log(`  Bytes per edge: ${(memDelta / size).toFixed(2)}`);
      console.log(`  Stats bytes/edge: ${stats.bytesPerEdge.toFixed(2)}`);
      console.log(`  Nodes: ${stats.numNodes}, Edges: ${stats.numEdges}`);
      console.log(`  Avg degree: ${stats.avgDegree.toFixed(2)}`);
      
      // Heap delta is noisy; assert on stats bytes per edge instead
      assert.ok(stats.bytesPerEdge < 1000, `Stats bytes/edge should be < 1000, got ${stats.bytesPerEdge.toFixed(2)}`);
    });

    perfTest(`read performance - ${size} edges - getOutEdgesByRel`, () => {
      const graph = new CondensedGraph();
      
      for (let i = 0; i < size; i++) {
        graph.addEdge(
          `user:${i % 100}`,
          ['owner', 'member', 'viewer', 'editor'][i % 4],
          `doc:${i % 500}`
        );
      }

      graph.finalizePerfectHash();
      graph.finalizeWaveletAdjacency({ dropAdjacencyList: true });
      
      // Warm-up
      for (let i = 0; i < 10; i++) {
        graph.getOutEdgesByRel(`user:${i}`, 'owner');
      }
      
      // Benchmark
      const iterations = 1000;
      const start = performance.now();
      
      for (let i = 0; i < iterations; i++) {
        graph.getOutEdgesByRel(`user:${i % 100}`, 'owner');
      }
      
      const duration = performance.now() - start;
      const avgTime = duration / iterations * 1000; // microseconds
      
      console.log(`\nRead performance - ${size} edges - getOutEdgesByRel:`);
      console.log(`  Total time: ${duration.toFixed(2)} ms`);
      console.log(`  Avg time: ${avgTime.toFixed(2)} µs`);
      console.log(`  Ops/sec: ${(1000000 / avgTime).toFixed(0)}`);
      
      // Should be reasonably fast
      assert.ok(avgTime < 1000, `getOutEdgesByRel should be < 1000µs, got ${avgTime.toFixed(2)}µs`);
    });

    perfTest(`read performance - ${size} edges - findEdge`, () => {
      const graph = new CondensedGraph();
      
      for (let i = 0; i < size; i++) {
        graph.addEdge(
          `user:${i % 100}`,
          ['owner', 'member', 'viewer', 'editor'][i % 4],
          `doc:${i % 500}`
        );
      }

      graph.finalizePerfectHash();
      graph.finalizeWaveletAdjacency({ dropAdjacencyList: true });
      
      // Warm-up
      for (let i = 0; i < 10; i++) {
        graph.findEdge(`user:${i}`, 'owner');
      }
      
      // Benchmark
      const iterations = 10000;
      const start = performance.now();
      
      for (let i = 0; i < iterations; i++) {
        graph.findEdge(`user:${i % 100}`, ['owner', 'member', 'viewer', 'editor'][i % 4]);
      }
      
      const duration = performance.now() - start;
      const avgTime = duration / iterations * 1000; // microseconds
      
      console.log(`\nRead performance - ${size} edges - findEdge:`);
      console.log(`  Total time: ${duration.toFixed(2)} ms`);
      console.log(`  Avg time: ${avgTime.toFixed(2)} µs`);
      console.log(`  Ops/sec: ${(1000000 / avgTime).toFixed(0)}`);
      
      assert.ok(avgTime < 500, `findEdge should be < 500µs, got ${avgTime.toFixed(2)}µs`);
    });

    perfTest(`read performance - ${size} edges - hasEdge`, () => {
      const graph = new CondensedGraph();
      
      for (let i = 0; i < size; i++) {
        graph.addEdge(
          `user:${i % 100}`,
          ['owner', 'member', 'viewer', 'editor'][i % 4],
          `doc:${i % 500}`
        );
      }

      graph.finalizePerfectHash();
      graph.finalizeWaveletAdjacency({ dropAdjacencyList: true });
      
      // Warm-up
      for (let i = 0; i < 10; i++) {
        graph.hasEdge(`user:${i}`, 'owner', `doc:${i}`);
      }
      
      // Benchmark
      const iterations = 10000;
      const start = performance.now();
      
      for (let i = 0; i < iterations; i++) {
        graph.hasEdge(
          `user:${i % 100}`,
          ['owner', 'member', 'viewer', 'editor'][i % 4],
          `doc:${i % 500}`
        );
      }
      
      const duration = performance.now() - start;
      const avgTime = duration / iterations * 1000; // microseconds
      
      console.log(`\nRead performance - ${size} edges - hasEdge:`);
      console.log(`  Total time: ${duration.toFixed(2)} ms`);
      console.log(`  Avg time: ${avgTime.toFixed(2)} µs`);
      console.log(`  Ops/sec: ${(1000000 / avgTime).toFixed(0)}`);
      
      assert.ok(avgTime < 1000, `hasEdge should be < 1000µs, got ${avgTime.toFixed(2)}µs`);
    });

    perfTest(`iteration performance - ${size} edges - forEachOutEdgeByRel`, () => {
      const graph = new CondensedGraph();
      
      for (let i = 0; i < size; i++) {
        graph.addEdge(
          `user:${i % 100}`,
          ['owner', 'member', 'viewer', 'editor'][i % 4],
          `doc:${i % 500}`
        );
      }

      graph.finalizePerfectHash();
      graph.finalizeWaveletAdjacency({ dropAdjacencyList: true });
      
      let edgeCount = 0;
      
      // Warm-up
      graph.forEachOutEdgeByRel('user:0', 'owner', (edge) => {
        edgeCount++;
      });
      
      edgeCount = 0;
      const start = performance.now();
      
      for (let i = 0; i < 1000; i++) {
        graph.forEachOutEdgeByRel(`user:${i % 100}`, 'owner', (edge) => {
          edgeCount++;
        });
      }
      
      const duration = performance.now() - start;
      const avgTime = duration / 1000 * 1000; // microseconds per iteration
      
      console.log(`\nIteration performance - ${size} edges - forEachOutEdgeByRel:`);
      console.log(`  Total time: ${duration.toFixed(2)} ms`);
      console.log(`  Avg time per iteration: ${avgTime.toFixed(2)} µs`);
      console.log(`  Total edges processed: ${edgeCount}`);
      
      assert.ok(avgTime < 500, `forEachOutEdgeByRel should be < 500µs, got ${avgTime.toFixed(2)}µs`);
    });

    perfTest(`write performance - ${size} edges - addEdge`, () => {
      const graph = new CondensedGraph();
      
      const start = performance.now();
      
      for (let i = 0; i < size; i++) {
        graph.addEdge(
          `user:${i % 100}`,
          ['owner', 'member', 'viewer', 'editor'][i % 4],
          `doc:${i % 500}`
        );
      }
      
      const duration = performance.now() - start;
      const avgTime = duration / size * 1000; // microseconds per edge
      
      console.log(`\nWrite performance - ${size} edges - addEdge:`);
      console.log(`  Total time: ${duration.toFixed(2)} ms`);
      console.log(`  Avg time per edge: ${avgTime.toFixed(2)} µs`);
      console.log(`  Edges/sec: ${(size / duration * 1000).toFixed(0)}`);
      
      assert.ok(avgTime < 100, `addEdge should be < 100µs, got ${avgTime.toFixed(2)}µs`);
    });
  });

  perfTest('comparison with plain object array', () => {
    const size = 50000;
    
    // Test CondensedGraph
    const cg = new CondensedGraph();
    const cgStartMem = process.memoryUsage().heapUsed;
    
    for (let i = 0; i < size; i++) {
      cg.addEdge(
        `user:${i % 100}`,
        ['owner', 'member', 'viewer', 'editor'][i % 4],
        `doc:${i % 500}`
      );
    }

    cg.finalizePerfectHash();
    cg.finalizeWaveletAdjacency({ dropAdjacencyList: true });
    const cgStats = cg.getStats();
    
    const cgEndMem = process.memoryUsage().heapUsed;
    const cgMemDelta = cgEndMem - cgStartMem;
    
    // Test plain object array
    const plainEdges = [];
    const plainStartMem = process.memoryUsage().heapUsed;
    
    for (let i = 0; i < size; i++) {
      plainEdges.push({
        src: `user:${i % 100}`,
        rel: ['owner', 'member', 'viewer', 'editor'][i % 4],
        dst: `doc:${i % 500}`,
        possibility: 1.0,
        reliability: 1.0
      });
    }
    
    const plainEndMem = process.memoryUsage().heapUsed;
    const plainMemDelta = plainEndMem - plainStartMem;
    
    // Benchmark read operations
    let cgTime = 0;
    let plainTime = 0;
    const iterations = 10000;
    
    // CondensedGraph read
    const cgReadStart = performance.now();
    for (let i = 0; i < iterations; i++) {
      cg.getOutEdgesByRel(`user:${i % 100}`, 'owner');
    }
    cgTime = performance.now() - cgReadStart;
    
    // Plain array read
    const plainReadStart = performance.now();
    for (let i = 0; i < iterations; i++) {
      const src = `user:${i % 100}`;
      plainEdges.filter(e => e.src === src);
    }
    plainTime = performance.now() - plainReadStart;
    
    console.log('\nComparison with plain object array:');
    console.log(`  CondensedGraph memory: ${(cgMemDelta / 1024 / 1024).toFixed(2)} MB`);
    console.log(`  Plain array memory: ${(plainMemDelta / 1024 / 1024).toFixed(2)} MB`);
    console.log(`  Memory savings: ${((plainMemDelta - cgMemDelta) / plainMemDelta * 100).toFixed(1)}%`);
    console.log(`  CondensedGraph bytes/edge: ${(cgMemDelta / size).toFixed(2)}`);
    console.log(`  Plain array bytes/edge: ${(plainMemDelta / size).toFixed(2)}`);
    console.log(`\n  CondensedGraph read time: ${cgTime.toFixed(2)} ms`);
    console.log(`  Plain array read time: ${plainTime.toFixed(2)} ms`);
    console.log(`  Read speedup: ${(plainTime / cgTime).toFixed(2)}x`);
    
    const plainEstimateBytes = size * 120;
    assert.ok(cgStats.bytesPerEdge < (plainEstimateBytes / size), 'CondensedGraph should use less memory');
  });

  perfTest('scalability summary', () => {
    console.log('\n\n=== Scalability Summary ===');
    console.log('Size | Memory (MB) | Bytes/Edge | Read (µs) | Write (µs)');
    console.log('-----|-------------|------------|-----------|-----------');
    
    results.forEach(r => {
      console.log(
        `${r.size.toString().padEnd(5)} | ` +
        `${(r.memDelta / 1024 / 1024).toFixed(2).padEnd(11)} | ` +
        `${r.bytesPerEdge.toFixed(2).padEnd(10)} | ` +
        `${(r.stats.avgDegree * 10).toFixed(0).padEnd(9)} | ` +
        `${(r.memDelta / r.size / 100).toFixed(2).padEnd(10)}`
      );
    });
  });
});
