import assert from 'node:assert/strict';
import { describe, test } 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 - Reliable Memory & Performance', () => {
  perfTest('memory efficiency - multiple sizes', () => {
    const sizes = [1000, 10000, 50000];
    
    sizes.forEach(size => {
      if (global.gc) global.gc();
      
      const baseline = process.memoryUsage();
      const graph = new CondensedGraph();
      const afterCreate = process.memoryUsage();
      
      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 afterAdd = process.memoryUsage();
      const stats = graph.getStats();
      
      const typedArrayMem = stats.memoryUsage.typedArrays;
      const nodeMapMem = stats.memoryUsage.nodeMap;
      const totalMem = stats.memoryUsage.total;
      
      console.log(`\nMemory (${size} edges):`);
      console.log(`  Nodes: ${stats.numNodes}`);
      console.log(`  Edges: ${stats.numEdges}`);
      console.log(`  Avg degree: ${stats.avgDegree.toFixed(2)}`);
      console.log(`  Typed arrays: ${(typedArrayMem / 1024 / 1024).toFixed(2)} MB`);
      console.log(`  Node map: ${(nodeMapMem / 1024 / 1024).toFixed(2)} MB`);
      console.log(`  Total: ${(totalMem / 1024 / 1024).toFixed(2)} MB`);
      console.log(`  Bytes/edge: ${stats.bytesPerEdge.toFixed(2)}`);
      console.log(`  Capacity: ${stats.capacity}, Utilization: ${(stats.utilization * 100).toFixed(1)}%`);
      
      assert.ok(stats.numEdges === size, `Should have ${size} edges`);
      assert.ok(totalMem < size * 1000, `Memory should be reasonable`);
    });
  });

  perfTest('read performance - warm cache', () => {
    const sizes = [1000, 10000, 50000];
    
    sizes.forEach(size => {
      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 });
      
      const operations = [
        { name: 'getOutEdgesByRel', fn: () => graph.getOutEdgesByRel('user:0', 'owner') },
        { name: 'findEdge', fn: () => graph.findEdge('user:0', 'owner') },
        { name: 'hasEdge', fn: () => graph.hasEdge('user:0', 'owner', 'doc:0') },
        { name: 'getDegree', fn: () => graph.getDegree('user:0') }
      ];
      
      console.log(`\nRead performance (${size} edges):`);
      
      operations.forEach(op => {
        const iterations = 100000;
        
        let count = 0;
        const start = performance.now();
        for (let i = 0; i < iterations; i++) {
          const result = op.fn();
          if (result) count++;
        }
        const duration = performance.now() - start;
        const avgMicros = (duration / iterations) * 1000;
        const opsPerSec = iterations / (duration / 1000);
        
        console.log(`  ${op.name}: ${avgMicros.toFixed(3)} µs (${opsPerSec.toFixed(0)} ops/sec)`);
        
        assert.ok(duration < 5000, `${op.name} should be fast`);
      });
    });
  });

  perfTest('iteration performance', () => {
    const sizes = [1000, 10000, 50000];
    
    sizes.forEach(size => {
      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 });
      
      console.log(`\nIteration performance (${size} edges):`);
      
      let totalEdges = 0;
      const iterations = 10000;
      const start = performance.now();
      
      for (let i = 0; i < iterations; i++) {
        graph.forEachOutEdgeByRel(`user:${i % 100}`, 'owner', (edge) => {
          totalEdges++;
        });
      }
      
      const duration = performance.now() - start;
      const avgMicros = (duration / iterations) * 1000;
      
      console.log(`  forEachOutEdgeByRel: ${avgMicros.toFixed(3)} µs/iteration`);
      console.log(`  Total edges processed: ${totalEdges}`);
      console.log(`  Avg edges/node: ${(totalEdges / iterations).toFixed(2)}`);
      
      assert.ok(duration < 5000, 'Iteration should be fast');
    });
  });

  perfTest('write performance', () => {
    const sizes = [1000, 10000, 50000];
    
    sizes.forEach(size => {
      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 avgMicros = (duration / size) * 1000;
      const edgesPerSec = size / (duration / 1000);
      
      console.log(`\nWrite performance (${size} edges):`);
      console.log(`  Total time: ${duration.toFixed(2)} ms`);
      console.log(`  Avg time/edge: ${avgMicros.toFixed(3)} µs`);
      console.log(`  Edges/sec: ${edgesPerSec.toFixed(0)}`);
      
      assert.ok(avgMicros < 100, `addEdge should be fast`);
    });
  });

  perfTest('comparison with plain array - memory', () => {
    const size = 50000;
    
    // CondensedGraph
    const cg = new CondensedGraph();
    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 cgMem = cgStats.memoryUsage.total;
    
    // Plain array
    const plainEdges = [];
    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
      });
    }
    
    // Estimate plain array memory (rough estimate)
    const plainMem = size * 120; // ~120 bytes per object + overhead
    
    console.log('\nMemory comparison (50K edges):');
    console.log(`  CondensedGraph: ${(cgMem / 1024 / 1024).toFixed(2)} MB`);
    console.log(`  Plain array (est): ${(plainMem / 1024 / 1024).toFixed(2)} MB`);
    console.log(`  Savings: ${((plainMem - cgMem) / plainMem * 100).toFixed(1)}%`);
    console.log(`  CondensedGraph bytes/edge: ${cgStats.bytesPerEdge.toFixed(2)}`);
    console.log(`  Plain array bytes/edge (est): ${(plainMem / size).toFixed(2)}`);
    
    assert.ok(cgMem < plainMem, 'CondensedGraph should use less memory');
  });

  perfTest('comparison with plain array - performance', () => {
    const size = 50000;
    
    // CondensedGraph
    const cg = new CondensedGraph();
    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 });
    
    // Plain array
    const plainEdges = [];
    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 iterations = 1000;
    
    // CondensedGraph: getOutEdges
    const cgStart = performance.now();
    for (let i = 0; i < iterations; i++) {
      cg.getOutEdgesByRel(`user:${i % 100}`, 'owner');
    }
    const cgTime = performance.now() - cgStart;
    
    // Plain array: filter
    const plainStart = performance.now();
    for (let i = 0; i < iterations; i++) {
      plainEdges.filter(e => e.src === `user:${i % 100}`);
    }
    const plainTime = performance.now() - plainStart;
    
    console.log('\nPerformance comparison (50K edges, 1K lookups):');
    console.log(`  CondensedGraph: ${cgTime.toFixed(2)} ms (${(cgTime / iterations).toFixed(3)} µs/lookup)`);
    console.log(`  Plain array filter: ${plainTime.toFixed(2)} ms (${(plainTime / iterations).toFixed(3)} µs/lookup)`);
    console.log(`  Speedup: ${(plainTime / cgTime).toFixed(2)}x`);
    
    assert.ok(cgTime < plainTime, 'CondensedGraph should be faster');
  });

  perfTest('scalability trends', () => {
    console.log('\n\n=== Scalability Analysis ===\n');
    
    const sizes = [1000, 10000, 50000];
    
    console.log('Size  | Nodes | Edges | Avg Deg | Memory (MB) | Bytes/Edge');
    console.log('------|-------|-------|---------|-------------|-------------');
    
    sizes.forEach(size => {
      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 });
      
      const stats = graph.getStats();
      
      console.log(
        `${size.toString().padStart(5)} | ` +
        `${stats.numNodes.toString().padStart(5)} | ` +
        `${stats.numEdges.toString().padStart(5)} | ` +
        `${stats.avgDegree.toFixed(2).padStart(7)} | ` +
        `${(stats.memoryUsage.total / 1024 / 1024).toFixed(2).padStart(11)} | ` +
        `${stats.bytesPerEdge.toFixed(2).padStart(11)}`
      );
    });
  });
});
