import { test, describe, before, after } from 'node:test';
import assert from 'node:assert/strict';
import { Arbiter } from '../../src/index.js';
import { BigGraphGenerator } from '../helpers/big-graph-generator.js';

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

  before(() => {
    if (process.env.TEST_DEBUG === '1') console.log('🚀 Starting 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
    });
    
    // Use million-node scale
    generator = new BigGraphGenerator({ scale: 'million', seed: 12345 });
    
    if (process.env.TEST_DEBUG === '1') console.log('📊 Generating million-node graph...');
    const graphStartTime = Date.now();
    graphData = generator.generateGraph('enterprise');
    const graphEndTime = Date.now();
    if (process.env.TEST_DEBUG === '1') console.log(`   Graph generation took: ${(graphEndTime - graphStartTime).toFixed(0)}ms`);
    
    if (process.env.TEST_DEBUG === '1') console.log('🔄 Loading graph into arbiter...');
    const loadStartTime = Date.now();
    testArbiter = generator.loadIntoArbiter(graphData);
    const loadEndTime = Date.now();
    if (process.env.TEST_DEBUG === '1') console.log(`   Graph loading took: ${(loadEndTime - loadStartTime).toFixed(0)}ms`);
    
    // Configure chain rules
    testArbiter.setRelationConfig('can_read_via_role', {
      type: 'chain',
      steps: [
        { relation: 'member_of', direction: 'out' },
        { relation: 'can_read', direction: 'out' }
      ]
    });
    
    testArbiter.setRelationConfig('can_access_multi_hop', {
      type: 'chain',
      steps: [
        { relation: 'member_of', direction: 'out' },
        { relation: 'member_of', direction: 'out' },
        { relation: 'member_of', direction: 'out' },
        { relation: 'can_read', direction: 'out' }
      ]
    });

    // 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, graphData);
    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;
    generator = null;
    testArbiter = null;
    graphData = null;
    validPaths = null;
  });

  /**
   * Find valid chain paths that actually exist in the graph
   */
  function findValidChainPaths(arbiter, graphData) {
    const validPaths = [];
    
    // Get all users and documents
    const users = graphData.users.map(u => u.id);
    const docs = graphData.documents.map(d => d.id);
    
    // Sample a reasonable number of combinations to test (don't test all combinations!)
    const maxTests = Math.min(10000, users.length * docs.length);
    let tested = 0;
    
    if (process.env.TEST_DEBUG === '1') console.log(`   Testing up to ${maxTests} user-document combinations...`);
    
    for (const user of users) {
      if (tested >= maxTests) break;
      
      for (const doc of docs) {
        if (tested >= maxTests) break;
        
        // Test if this path actually exists
        try {
          const result = arbiter.check(user, 'can_read_via_role', doc);
          if (result.possibility > 0) {
            validPaths.push({ user, doc, relation: 'can_read_via_role', result });
          }
        } catch (error) {
          // Skip invalid paths
        }
        
        tested++;
        
        // Progress indicator for large graphs
        if (tested % 10000 === 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 > 1000, `Million node chain query QPS ${result.qps.toFixed(0)} below 1000 threshold`);
    assert.ok(result.avgLatency < 100, `Million node chain query latency ${result.avgLatency.toFixed(3)}ms too high`);
    assert.ok(result.successRate > 0.5, `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)
    const directRelations = graphData.relations.filter(r => 
      r.src.startsWith('user:') && r.dst.startsWith('doc:') && 
      ['can_read', 'can_write', 'can_delete'].includes(r.relation)
    ).slice(0, 1000); // Use 1000 direct relations
    
    if (directRelations.length === 0) {
      if (process.env.TEST_DEBUG === '1') console.log('   ⚠️  No direct relations found - skipping test');
      return;
    }
    
    let queryIndex = 0;
    const result = measureQPSWithAnalysis(() => {
      const relation = directRelations[queryIndex % directRelations.length];
      return testArbiter.check(relation.src, relation.relation, relation.dst);
    }, 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 > 10000, `Million node direct query QPS ${result.qps.toFixed(0)} below 10000 threshold`);
    assert.ok(result.avgLatency < 10, `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:');
    if (process.env.TEST_DEBUG === '1') console.log(`   Total Relations: ${graphData.relations.length.toLocaleString()}`);
    if (process.env.TEST_DEBUG === '1') console.log(`   Total Users: ${graphData.users.length.toLocaleString()}`);
    if (process.env.TEST_DEBUG === '1') console.log(`   Total Documents: ${graphData.documents.length.toLocaleString()}`);
    
    const userCount = graphData.users.length;
    const docCount = graphData.documents.length;
    const groupCount = graphData.enterprises ? graphData.enterprises.length : 0;
    
    if (process.env.TEST_DEBUG === '1') console.log(`   Users: ${userCount.toLocaleString()}`);
    if (process.env.TEST_DEBUG === '1') console.log(`   Documents: ${docCount.toLocaleString()}`);
    if (process.env.TEST_DEBUG === '1') console.log(`   Groups: ${groupCount.toLocaleString()}`);
    
    const relationTypes = [...new Set(graphData.relations.map(r => r.relation))];
    if (process.env.TEST_DEBUG === '1') console.log(`   Relation Types: ${relationTypes.length} (${relationTypes.join(', ')})`);
    if (process.env.TEST_DEBUG === '1') console.log(`   Valid Chain Paths Found: ${validPaths.length.toLocaleString()}`);
    
    // Verify we have a million-node graph
    const totalNodes = userCount + docCount + groupCount;
    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(graphData.relations.length >= 1000000, `Graph too small: ${graphData.relations.length.toLocaleString()} relations (expected 1M+)`);
  });
});
