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

describe.skip('Simple Million Node Performance Benchmark', () => {
  let arbiter;
  let testArbiter;
  let validPaths = [];
  let startTime;
  let endTime;

  before(() => {
    if (process.env.TEST_DEBUG === '1') console.log('🚀 Starting simple million-node benchmark setup...');
    startTime = Date.now();
    
    // Create arbiter with caching enabled for realistic performance
    arbiter = new Arbiter({ 
      fastConstructionMode: false,
      disableCaching: false, // Keep caching enabled for realistic performance
      disableChainCaching: false,
      disableDirectCaching: false
    });
    
    // Create a simpler million-node graph directly
    if (process.env.TEST_DEBUG === '1') console.log('📊 Creating million-node graph...');
    const graphStartTime = Date.now();
    
    // Add 1 million nodes (100K users + 900K documents)
    const userCount = 100000;
    const docCount = 900000;
    
    if (process.env.TEST_DEBUG === '1') console.log(`   Adding ${userCount.toLocaleString()} users...`);
    for (let i = 0; i < userCount; i++) {
      arbiter.addNode(`user:${i}`, 'user');
    }
    
    if (process.env.TEST_DEBUG === '1') console.log(`   Adding ${docCount.toLocaleString()} documents...`);
    for (let i = 0; i < docCount; i++) {
      arbiter.addNode(`doc:${i}`, 'document');
    }
    
    const graphEndTime = Date.now();
    if (process.env.TEST_DEBUG === '1') console.log(`   Node creation took: ${(graphEndTime - graphStartTime).toFixed(0)}ms`);
    
    // Add 2 million relations (much simpler than complex chains)
    if (process.env.TEST_DEBUG === '1') console.log('📊 Adding 2 million relations...');
    const relationStartTime = Date.now();
    
    const relationCount = 2000000;
    for (let i = 0; i < relationCount; i++) {
      const userId = Math.floor(Math.random() * userCount);
      const docId = Math.floor(Math.random() * docCount);
      const relationType = ['can_read', 'can_write', 'can_delete'][Math.floor(Math.random() * 3)];
      
      arbiter.addRelation(`user:${userId}`, relationType, `doc:${docId}`, { 
        possibility: 0.8 + Math.random() * 0.2 
      });
    }
    
    const relationEndTime = Date.now();
    if (process.env.TEST_DEBUG === '1') console.log(`   Relation creation took: ${(relationEndTime - relationStartTime).toFixed(0)}ms`);
    
    // Configure simple chain rules
    arbiter.setRelationConfig('can_read_via_role', {
      type: 'chain',
      steps: [
        { relation: 'member_of', direction: 'out' },
        { relation: 'can_read', direction: 'out' }
      ]
    });
    
    // Add some role-based relations for chain testing
    if (process.env.TEST_DEBUG === '1') console.log('📊 Adding role-based relations for chain testing...');
    const roleStartTime = Date.now();
    
    // Add 10K role nodes
    for (let i = 0; i < 10000; i++) {
      arbiter.addNode(`role:${i}`, 'role');
    }
    
    // Add member_of relations (users to roles)
    for (let i = 0; i < 50000; i++) {
      const userId = Math.floor(Math.random() * userCount);
      const roleId = Math.floor(Math.random() * 10000);
      arbiter.addRelation(`user:${userId}`, 'member_of', `role:${roleId}`, { 
        possibility: 0.9 
      });
    }
    
    // Add can_read relations (roles to documents)
    for (let i = 0; i < 50000; i++) {
      const roleId = Math.floor(Math.random() * 10000);
      const docId = Math.floor(Math.random() * docCount);
      arbiter.addRelation(`role:${roleId}`, 'can_read', `doc:${docId}`, { 
        possibility: 0.8 
      });
    }
    
    const roleEndTime = Date.now();
    if (process.env.TEST_DEBUG === '1') console.log(`   Role relations took: ${(roleEndTime - roleStartTime).toFixed(0)}ms`);
    
    testArbiter = arbiter;

    // Pre-find valid paths that actually exist in the graph
    if (process.env.TEST_DEBUG === '1') console.log('🔍 Finding valid chain paths in million-node graph...');
    const pathStartTime = Date.now();
    validPaths = findValidChainPaths(testArbiter, userCount, docCount);
    const pathEndTime = Date.now();
    if (process.env.TEST_DEBUG === '1') console.log(`   Found ${validPaths.length} valid paths in ${(pathEndTime - pathStartTime).toFixed(0)}ms`);
    
    endTime = Date.now();
    if (process.env.TEST_DEBUG === '1') console.log(`✅ Setup completed in ${(endTime - startTime).toFixed(0)}ms`);
  });

  after(() => {
    arbiter = null;
    testArbiter = null;
    validPaths = null;
  });

  /**
   * Find valid chain paths that actually exist in the graph
   */
  function findValidChainPaths(arbiter, userCount, docCount) {
    const validPaths = [];
    
    // Sample a reasonable number of combinations to test
    const maxTests = Math.min(10000, userCount * docCount);
    let tested = 0;
    
    if (process.env.TEST_DEBUG === '1') console.log(`   Testing up to ${maxTests} user-document combinations...`);
    
    for (let i = 0; i < Math.min(100, userCount); i++) {
      if (tested >= maxTests) break;
      
      for (let j = 0; j < Math.min(100, docCount); j++) {
        if (tested >= maxTests) break;
        
        // Test if this path actually exists
        try {
          const result = arbiter.check(`user:${i}`, 'can_read_via_role', `doc:${j}`);
          if (result.possibility > 0) {
            validPaths.push({ user: `user:${i}`, doc: `doc:${j}`, relation: 'can_read_via_role', result });
          }
        } catch (error) {
          // Skip invalid paths
        }
        
        tested++;
        
        // Progress indicator for large graphs
        if (tested % 1000 === 0) {
          if (process.env.TEST_DEBUG === '1') console.log(`     Tested ${tested}/${maxTests} combinations, found ${validPaths.length} valid paths`);
        }
      }
    }
    
    return validPaths;
  }

  /**
   * Measure QPS for a given operation with detailed analysis
   */
  function measureQPSWithAnalysis(operation, duration = 5000, operationName = 'Operation') {
    const startTime = Date.now();
    const endTime = startTime + duration;
    let operationCount = 0;
    const latencies = [];
    let successCount = 0;
    let failureCount = 0;

    while (Date.now() < endTime) {
      const opStart = process.hrtime.bigint();
      
      try {
        const result = operation();
        operationCount++;
        
        if (result && result.possibility > 0) {
          successCount++;
        } else {
          failureCount++;
        }
        
        const opEnd = process.hrtime.bigint();
        const latency = Number(opEnd - opStart) / 1000000; // Convert to milliseconds
        latencies.push(latency);
      } catch (error) {
        operationCount++;
        failureCount++;
        const opEnd = process.hrtime.bigint();
        const latency = Number(opEnd - opStart) / 1000000;
        latencies.push(latency);
      }
    }

    const actualDuration = Date.now() - startTime;
    const qps = (operationCount / actualDuration) * 1000;
    const avgLatency = latencies.reduce((a, b) => a + b, 0) / latencies.length;
    
    // Sort once and reuse
    const sortedLatencies = latencies.sort((a, b) => a - b);
    const p95Latency = sortedLatencies[Math.floor(latencies.length * 0.95)];
    const p99Latency = sortedLatencies[Math.floor(latencies.length * 0.99)];

    return {
      qps,
      operationCount,
      duration: actualDuration,
      avgLatency,
      p95Latency,
      p99Latency,
      maxLatency: sortedLatencies[sortedLatencies.length - 1],
      minLatency: sortedLatencies[0],
      successCount,
      failureCount,
      successRate: successCount / operationCount
    };
  }

  test('Million Node Chain Query Performance', async () => {
    if (process.env.TEST_DEBUG === '1') console.log('📊 Testing Million Node Chain Query QPS...');
    
    if (validPaths.length === 0) {
      if (process.env.TEST_DEBUG === '1') console.log('   ⚠️  No valid paths found - skipping test');
      return;
    }
    
    let queryIndex = 0;
    const result = measureQPSWithAnalysis(() => {
      const path = validPaths[queryIndex % validPaths.length];
      return testArbiter.check(path.user, path.relation, path.doc);
    }, 5000, 'Million Node Chain Query');

    if (process.env.TEST_DEBUG === '1') console.log(`   QPS: ${result.qps.toFixed(2)}`);
    if (process.env.TEST_DEBUG === '1') console.log(`   Operations: ${result.operationCount}`);
    if (process.env.TEST_DEBUG === '1') console.log(`   Duration: ${result.duration}ms`);
    if (process.env.TEST_DEBUG === '1') console.log(`   Avg Latency: ${result.avgLatency.toFixed(3)}ms`);
    if (process.env.TEST_DEBUG === '1') console.log(`   P95 Latency: ${result.p95Latency.toFixed(3)}ms`);
    if (process.env.TEST_DEBUG === '1') console.log(`   P99 Latency: ${result.p99Latency.toFixed(3)}ms`);
    if (process.env.TEST_DEBUG === '1') console.log(`   Max Latency: ${result.maxLatency.toFixed(3)}ms`);
    if (process.env.TEST_DEBUG === '1') console.log(`   Min Latency: ${result.minLatency.toFixed(3)}ms`);
    if (process.env.TEST_DEBUG === '1') console.log(`   Success Rate: ${(result.successRate * 100).toFixed(1)}%`);
    if (process.env.TEST_DEBUG === '1') console.log(`   Success Count: ${result.successCount}`);
    if (process.env.TEST_DEBUG === '1') console.log(`   Failure Count: ${result.failureCount}`);

    // More lenient expectations for million-node graph
    assert.ok(result.qps > 100, `Million node chain query QPS ${result.qps.toFixed(0)} below 100 threshold`);
    assert.ok(result.avgLatency < 500, `Million node chain query latency ${result.avgLatency.toFixed(3)}ms too high`);
    assert.ok(result.successRate > 0.1, `Success rate ${(result.successRate * 100).toFixed(1)}% too low for valid paths`);
  });

  test('Million Node Direct Query Performance', async () => {
    if (process.env.TEST_DEBUG === '1') console.log('📊 Testing Million Node Direct Query QPS...');
    
    // Test direct queries (should be much faster)
    let queryIndex = 0;
    const result = measureQPSWithAnalysis(() => {
      const userId = queryIndex % 100000;
      const docId = (queryIndex + 1000) % 900000;
      const relationType = ['can_read', 'can_write', 'can_delete'][queryIndex % 3];
      return testArbiter.check(`user:${userId}`, relationType, `doc:${docId}`);
    }, 5000, 'Million Node Direct Query');

    if (process.env.TEST_DEBUG === '1') console.log(`   QPS: ${result.qps.toFixed(2)}`);
    if (process.env.TEST_DEBUG === '1') console.log(`   Operations: ${result.operationCount}`);
    if (process.env.TEST_DEBUG === '1') console.log(`   Duration: ${result.duration}ms`);
    if (process.env.TEST_DEBUG === '1') console.log(`   Avg Latency: ${result.avgLatency.toFixed(3)}ms`);
    if (process.env.TEST_DEBUG === '1') console.log(`   P95 Latency: ${result.p95Latency.toFixed(3)}ms`);
    if (process.env.TEST_DEBUG === '1') console.log(`   P99 Latency: ${result.p99Latency.toFixed(3)}ms`);
    if (process.env.TEST_DEBUG === '1') console.log(`   Max Latency: ${result.maxLatency.toFixed(3)}ms`);
    if (process.env.TEST_DEBUG === '1') console.log(`   Min Latency: ${result.minLatency.toFixed(3)}ms`);
    if (process.env.TEST_DEBUG === '1') console.log(`   Success Rate: ${(result.successRate * 100).toFixed(1)}%`);

    // Direct queries should be much faster than chain queries
    assert.ok(result.qps > 1000, `Million node direct query QPS ${result.qps.toFixed(0)} below 1000 threshold`);
    assert.ok(result.avgLatency < 50, `Million node direct query latency ${result.avgLatency.toFixed(3)}ms too high`);
  });

  test('Memory Usage Analysis', async () => {
    if (process.env.TEST_DEBUG === '1') console.log('📊 Memory Usage Analysis...');
    
    const memUsage = process.memoryUsage();
    const memUsageMB = {
      rss: (memUsage.rss / 1024 / 1024).toFixed(2),
      heapTotal: (memUsage.heapTotal / 1024 / 1024).toFixed(2),
      heapUsed: (memUsage.heapUsed / 1024 / 1024).toFixed(2),
      external: (memUsage.external / 1024 / 1024).toFixed(2)
    };
    
    if (process.env.TEST_DEBUG === '1') console.log(`   RSS Memory: ${memUsageMB.rss} MB`);
    if (process.env.TEST_DEBUG === '1') console.log(`   Heap Total: ${memUsageMB.heapTotal} MB`);
    if (process.env.TEST_DEBUG === '1') console.log(`   Heap Used: ${memUsageMB.heapUsed} MB`);
    if (process.env.TEST_DEBUG === '1') console.log(`   External: ${memUsageMB.external} MB`);
    
    // Check if we're within reasonable memory limits
    const heapUsedMB = parseFloat(memUsageMB.heapUsed);
    assert.ok(heapUsedMB < 16384, `Heap usage ${heapUsedMB}MB too high for million-node graph`);
  });

  test('Graph Statistics', async () => {
    if (process.env.TEST_DEBUG === '1') console.log('📊 Million Node Graph Statistics:');
    
    // Get stats from the arbiter
    const stats = testArbiter.getStats();
    if (process.env.TEST_DEBUG === '1') console.log(`   Total Nodes: ${stats.totalNodes.toLocaleString()}`);
    if (process.env.TEST_DEBUG === '1') console.log(`   Total Relations: ${stats.totalRelations.toLocaleString()}`);
    if (process.env.TEST_DEBUG === '1') console.log(`   Users: 100,000`);
    if (process.env.TEST_DEBUG === '1') console.log(`   Documents: 900,000`);
    if (process.env.TEST_DEBUG === '1') console.log(`   Roles: 10,000`);
    if (process.env.TEST_DEBUG === '1') console.log(`   Valid Chain Paths Found: ${validPaths.length.toLocaleString()}`);
    
    // Verify we have a million-node graph
    const totalNodes = stats.totalNodes;
    if (process.env.TEST_DEBUG === '1') console.log(`   Total Nodes: ${totalNodes.toLocaleString()}`);
    
    assert.ok(totalNodes >= 1000000, `Graph too small: ${totalNodes.toLocaleString()} nodes (expected 1M+)`);
    assert.ok(stats.totalRelations >= 2000000, `Graph too small: ${stats.totalRelations.toLocaleString()} relations (expected 2M+)`);
  });
});
