/**
 * Test chain caching performance improvements
 */

import { test as _test } from 'node:test';
const test = process.env.RUN_PERF_TESTS === '1' ? _test : _test.skip;
import assert from 'node:assert/strict';
import { BigGraphGenerator } from '../helpers/big-graph-generator.js';
import { ChainRule } from '../../src/authorization/rules/ChainRule.js';

test('measures QPS improvement with chain caching', async () => {
  if (process.env.TEST_DEBUG === '1') console.log('🚀 Testing chain caching QPS improvements...');
  
  const generator = new BigGraphGenerator();
  const graphData = generator.generateGraph('enterprise');
  const arbiter = generator.loadIntoArbiter(graphData);
  
  const rule = new ChainRule(arbiter);
  
  // Create a test chain rule
  const chainRule = {
    type: 'chain',
    steps: [
      { relation: 'role_membership', direction: 'out' },
      { relation: 'role_permission', direction: 'out' }
    ],
    collectValues: false,
    valueAggregation: 'sum'
  };
  
  // Test with multiple user-object pairs to create cache hits
  const testPairs = [
    { user: 'user:Alice Smith-0', object: 'doc:secret-999' },
    { user: 'user:Bob Johnson-1', object: 'doc:report-54' },
    { user: 'user:Charlie Brown-2', object: 'doc:contract-480' },
    { user: 'user:Diana Martinez-3', object: 'doc:policy-115' },
    { user: 'user:Eve Wilson-4', object: 'doc:analysis-200' }
  ];
  
  if (process.env.TEST_DEBUG === '1') console.log(`   Testing with ${testPairs.length} user-object pairs...`);
  
  // Test 1: Without caching (clear cache between runs)
  if (process.env.TEST_DEBUG === '1') console.log('   Test 1: Without caching...');
  const start1 = Date.now();
  let queryCount1 = 0;
  const end1 = start1 + 2000; // 2 seconds
  
  while (Date.now() < end1) {
    for (const pair of testPairs) {
      // Clear cache before each query to simulate no caching
      rule.chainResultCache.clear();
      rule.chainPathCache.clear();
      
      rule.evaluate(
        arbiter.nodeIdByKey.get(pair.user),
        pair.user,
        arbiter.nodeIdByKey.get(pair.object),
        pair.object,
        chainRule,
        new Set(),
        'can_read_via_role',
        {}
      );
      queryCount1++;
    }
  }
  
  const duration1 = Date.now() - start1;
  const qps1 = (queryCount1 / duration1) * 1000;
  
  if (process.env.TEST_DEBUG === '1') console.log(`   Without caching: ${qps1.toFixed(2)} QPS (${queryCount1} queries in ${duration1}ms)`);
  
  // Test 2: With caching (let cache accumulate)
  if (process.env.TEST_DEBUG === '1') console.log('   Test 2: With caching...');
  rule.chainResultCache.clear();
  rule.chainPathCache.clear();
  
  const start2 = Date.now();
  let queryCount2 = 0;
  const end2 = start2 + 2000; // 2 seconds
  
  while (Date.now() < end2) {
    for (const pair of testPairs) {
      rule.evaluate(
        arbiter.nodeIdByKey.get(pair.user),
        pair.user,
        arbiter.nodeIdByKey.get(pair.object),
        pair.object,
        chainRule,
        new Set(),
        'can_read_via_role',
        {}
      );
      queryCount2++;
    }
  }
  
  const duration2 = Date.now() - start2;
  const qps2 = (queryCount2 / duration2) * 1000;
  
  if (process.env.TEST_DEBUG === '1') console.log(`   With caching: ${qps2.toFixed(2)} QPS (${queryCount2} queries in ${duration2}ms)`);
  if (process.env.TEST_DEBUG === '1') console.log(`   Cache size: ${rule.chainResultCache.size} results, ${rule.chainPathCache.size} paths`);
  
  // Calculate improvement
  const improvement = qps2 / qps1;
  if (process.env.TEST_DEBUG === '1') console.log(`   QPS improvement: ${improvement.toFixed(2)}x faster`);
  
  // Verify improvement
  assert.ok(improvement > 1, `Caching should improve QPS (${improvement.toFixed(2)}x)`);
  assert.ok(rule.chainResultCache.size > 0, 'Cache should be populated');
  
  if (process.env.TEST_DEBUG === '1') console.log('   ✅ Chain caching improves QPS');
});

test('measures QPS improvement with repeated queries', async () => {
  if (process.env.TEST_DEBUG === '1') console.log('🔄 Testing QPS improvement with repeated queries...');
  
  const generator = new BigGraphGenerator();
  const graphData = generator.generateGraph('enterprise');
  const arbiter = generator.loadIntoArbiter(graphData);
  
  const rule = new ChainRule(arbiter);
  
  // Create a test chain rule
  const chainRule = {
    type: 'chain',
    steps: [
      { relation: 'role_membership', direction: 'out' },
      { relation: 'role_permission', direction: 'out' }
    ],
    collectValues: false,
    valueAggregation: 'sum'
  };
  
  // Test with same user-object pair repeated many times
  const testUser = 'user:Alice Smith-0';
  const testObject = 'doc:secret-999';
  
  if (process.env.TEST_DEBUG === '1') console.log(`   Testing repeated queries: ${testUser} -> ${testObject}`);
  
  // Test 1: First run (no cache)
  if (process.env.TEST_DEBUG === '1') console.log('   First run (no cache)...');
  const start1 = Date.now();
  let queryCount1 = 0;
  const end1 = start1 + 1000; // 1 second
  
  while (Date.now() < end1) {
    rule.evaluate(
      arbiter.nodeIdByKey.get(testUser),
      testUser,
      arbiter.nodeIdByKey.get(testObject),
      testObject,
      chainRule,
      new Set(),
      'can_read_via_role',
      {}
    );
    queryCount1++;
  }
  
  const duration1 = Date.now() - start1;
  const qps1 = (queryCount1 / duration1) * 1000;
  
  if (process.env.TEST_DEBUG === '1') console.log(`   First run: ${qps1.toFixed(2)} QPS (${queryCount1} queries)`);
  if (process.env.TEST_DEBUG === '1') console.log(`   Cache size after first run: ${rule.chainResultCache.size}`);
  
  // Test 2: Second run (with cache)
  if (process.env.TEST_DEBUG === '1') console.log('   Second run (with cache)...');
  const start2 = Date.now();
  let queryCount2 = 0;
  const end2 = start2 + 1000; // 1 second
  
  while (Date.now() < end2) {
    rule.evaluate(
      arbiter.nodeIdByKey.get(testUser),
      testUser,
      arbiter.nodeIdByKey.get(testObject),
      testObject,
      chainRule,
      new Set(),
      'can_read_via_role',
      {}
    );
    queryCount2++;
  }
  
  const duration2 = Date.now() - start2;
  const qps2 = (queryCount2 / duration2) * 1000;
  
  if (process.env.TEST_DEBUG === '1') console.log(`   Second run: ${qps2.toFixed(2)} QPS (${queryCount2} queries)`);
  if (process.env.TEST_DEBUG === '1') console.log(`   Cache size after second run: ${rule.chainResultCache.size}`);
  
  // Calculate improvement
  const improvement = qps2 / qps1;
  if (process.env.TEST_DEBUG === '1') console.log(`   QPS improvement: ${improvement.toFixed(2)}x faster`);
  
  // Verify improvement
  assert.ok(improvement > 1, `Caching should improve QPS (${improvement.toFixed(2)}x)`);
  assert.ok(rule.chainResultCache.size > 0, 'Cache should be populated');
  
  if (process.env.TEST_DEBUG === '1') console.log('   ✅ Repeated query caching improves QPS');
});

test('measures QPS improvement with mixed query patterns', async () => {
  if (process.env.TEST_DEBUG === '1') console.log('🎯 Testing QPS improvement with mixed query patterns...');
  
  const generator = new BigGraphGenerator();
  const graphData = generator.generateGraph('enterprise');
  const arbiter = generator.loadIntoArbiter(graphData);
  
  const rule = new ChainRule(arbiter);
  
  // Create a test chain rule
  const chainRule = {
    type: 'chain',
    steps: [
      { relation: 'role_membership', direction: 'out' },
      { relation: 'role_permission', direction: 'out' }
    ],
    collectValues: false,
    valueAggregation: 'sum'
  };
  
  // Create a mix of repeated and unique queries
  const repeatedPairs = [
    { user: 'user:Alice Smith-0', object: 'doc:secret-999' },
    { user: 'user:Bob Johnson-1', object: 'doc:report-54' }
  ];
  
  const uniquePairs = [
    { user: 'user:Charlie Brown-2', object: 'doc:contract-480' },
    { user: 'user:Diana Martinez-3', object: 'doc:policy-115' },
    { user: 'user:Eve Wilson-4', object: 'doc:analysis-200' },
    { user: 'user:Frank Davis-5', object: 'doc:spec-300' },
    { user: 'user:Grace Miller-6', object: 'doc:proposal-400' }
  ];
  
  if (process.env.TEST_DEBUG === '1') console.log(`   Testing with ${repeatedPairs.length} repeated pairs and ${uniquePairs.length} unique pairs...`);
  
  // Test 1: Without caching
  if (process.env.TEST_DEBUG === '1') console.log('   Test 1: Without caching...');
  const start1 = Date.now();
  let queryCount1 = 0;
  const end1 = start1 + 3000; // 3 seconds
  
  while (Date.now() < end1) {
    // Mix of repeated and unique queries
    for (const pair of repeatedPairs) {
      rule.chainResultCache.clear(); // Clear cache to simulate no caching
      rule.evaluate(
        arbiter.nodeIdByKey.get(pair.user),
        pair.user,
        arbiter.nodeIdByKey.get(pair.object),
        pair.object,
        chainRule,
        new Set(),
        'can_read_via_role',
        {}
      );
      queryCount1++;
    }
    
    for (const pair of uniquePairs) {
      rule.chainResultCache.clear(); // Clear cache to simulate no caching
      rule.evaluate(
        arbiter.nodeIdByKey.get(pair.user),
        pair.user,
        arbiter.nodeIdByKey.get(pair.object),
        pair.object,
        chainRule,
        new Set(),
        'can_read_via_role',
        {}
      );
      queryCount1++;
    }
  }
  
  const duration1 = Date.now() - start1;
  const qps1 = (queryCount1 / duration1) * 1000;
  
  if (process.env.TEST_DEBUG === '1') console.log(`   Without caching: ${qps1.toFixed(2)} QPS (${queryCount1} queries)`);
  
  // Test 2: With caching
  if (process.env.TEST_DEBUG === '1') console.log('   Test 2: With caching...');
  rule.chainResultCache.clear();
  rule.chainPathCache.clear();
  
  const start2 = Date.now();
  let queryCount2 = 0;
  const end2 = start2 + 3000; // 3 seconds
  
  while (Date.now() < end2) {
    // Mix of repeated and unique queries
    for (const pair of repeatedPairs) {
      rule.evaluate(
        arbiter.nodeIdByKey.get(pair.user),
        pair.user,
        arbiter.nodeIdByKey.get(pair.object),
        pair.object,
        chainRule,
        new Set(),
        'can_read_via_role',
        {}
      );
      queryCount2++;
    }
    
    for (const pair of uniquePairs) {
      rule.evaluate(
        arbiter.nodeIdByKey.get(pair.user),
        pair.user,
        arbiter.nodeIdByKey.get(pair.object),
        pair.object,
        chainRule,
        new Set(),
        'can_read_via_role',
        {}
      );
      queryCount2++;
    }
  }
  
  const duration2 = Date.now() - start2;
  const qps2 = (queryCount2 / duration2) * 1000;
  
  if (process.env.TEST_DEBUG === '1') console.log(`   With caching: ${qps2.toFixed(2)} QPS (${queryCount2} queries)`);
  if (process.env.TEST_DEBUG === '1') console.log(`   Cache size: ${rule.chainResultCache.size} results, ${rule.chainPathCache.size} paths`);
  
  // Calculate improvement
  const improvement = qps2 / qps1;
  if (process.env.TEST_DEBUG === '1') console.log(`   QPS improvement: ${improvement.toFixed(2)}x faster`);
  
  // Verify improvement
  assert.ok(improvement > 1, `Caching should improve QPS (${improvement.toFixed(2)}x)`);
  assert.ok(rule.chainResultCache.size > 0, 'Cache should be populated');
  
  if (process.env.TEST_DEBUG === '1') console.log('   ✅ Mixed query pattern caching improves QPS');
});
