/**
 * Targeted B2C Diagnostic Test
 * 
 * Purpose: Diagnose why B2C scenario has:
 * 1. False negatives (over-refusing)
 * 2. Low QPS (1002 vs 17241 in enterprise)
 * 
 * Hypothesis:
 * - SCC bug causing incorrect PLTC queries (fixed, but need to verify)
 * - Chain evaluation not finding paths that exist
 * - Execution path not using PLTC efficiently
 * 
 * Approach:
 * 1. Small controlled graph (50 users, 200 files) for easy inspection
 * 2. Trace execution path for each query
 * 3. Measure time in each phase
 * 4. Verify SCC correctness
 * 5. Verify PLTC queries
 * 6. Verify chain evaluation
 */

import { Arbiter } from '../src/index.js';

// Helper function to generate preferential attachment edges (from original test)
function generatePreferentialAttachmentEdges(numNodes, avgDegree) {
  const edges = [];
  const degrees = new Array(numNodes).fill(0);
  
  // Start with a small clique
  for (let i = 0; i < Math.min(5, numNodes); i++) {
    for (let j = i + 1; j < Math.min(5, numNodes); j++) {
      edges.push([i, j]);
      degrees[i]++;
      degrees[j]++;
    }
  }
  
  // Preferential attachment
  const totalEdges = Math.floor((numNodes * avgDegree) / 2);
  for (let i = edges.length; i < totalEdges; i++) {
    // Select source node with probability proportional to degree
    let src = Math.floor(Math.random() * numNodes);
    const srcProb = degrees[src] / (edges.length * 2 + 1);
    if (Math.random() > srcProb) {
      src = Math.floor(Math.random() * numNodes);
    }
    
    // Select target node with probability proportional to degree
    let dst = Math.floor(Math.random() * numNodes);
    while (dst === src) {
      dst = Math.floor(Math.random() * numNodes);
    }
    const dstProb = degrees[dst] / (edges.length * 2 + 1);
    if (Math.random() > dstProb) {
      dst = Math.floor(Math.random() * numNodes);
      while (dst === src) {
        dst = Math.floor(Math.random() * numNodes);
      }
    }
    
    edges.push([src, dst]);
    degrees[src]++;
    degrees[dst]++;
  }
  
  return edges;
}

// Small controlled B2C scenario
function setupSmallB2C() {
  const arbiter = new Arbiter({
    fastConstructionMode: true,
    enableInference: false
  });
  
  const numUsers = 50;
  const numFiles = 200;
  
  // Create users
  const users = [];
  for (let i = 0; i < numUsers; i++) {
    const userKey = `user:user${i}`;
    users.push(userKey);
    arbiter.addNode(userKey, 'user');
  }
  
  // Create a controlled friend graph (not random)
  // Create 3 friend clusters with some cross-connections
  console.log('   Building controlled friend graph...');
  const clusters = [
    users.slice(0, 15),   // Cluster 1: users 0-14
    users.slice(15, 30),   // Cluster 2: users 15-29
    users.slice(30, 50)    // Cluster 3: users 30-49
  ];
  
  // Within-cluster friendships (bidirectional)
  for (const cluster of clusters) {
    for (let i = 0; i < cluster.length; i++) {
      for (let j = i + 1; j < Math.min(i + 5, cluster.length); j++) {
        arbiter.addRelation(cluster[i], 'friend', cluster[j]);
        arbiter.addRelation(cluster[j], 'friend', cluster[i]);
      }
    }
  }
  
  // Cross-cluster connections (fewer, for friend-of-friend testing)
  for (let i = 0; i < 5; i++) {
    const u1 = clusters[0][Math.floor(Math.random() * clusters[0].length)];
    const u2 = clusters[1][Math.floor(Math.random() * clusters[1].length)];
    arbiter.addRelation(u1, 'friend', u2);
    arbiter.addRelation(u2, 'friend', u1);
  }
  
  // Create files with known owners
  console.log('   Creating files with known owners...');
  const files = [];
  for (let i = 0; i < numFiles; i++) {
    const fileKey = `file:file${i}`;
    files.push(fileKey);
    arbiter.addNode(fileKey, 'file');
    
    // Assign owner: first 100 files to cluster 1, next 50 to cluster 2, rest to cluster 3
    let owner;
    if (i < 100) {
      owner = clusters[0][i % clusters[0].length];
    } else if (i < 150) {
      owner = clusters[1][(i - 100) % clusters[1].length];
    } else {
      owner = clusters[2][(i - 150) % clusters[2].length];
    }
    arbiter.addRelation(owner, 'owns', fileKey);
  }
  
  // Configure relations
  arbiter.setRelationConfig('friend', { type: 'direct' });
  arbiter.setRelationConfig('owns', { type: 'direct' });
  
  // Chain rule: friend_file = user -> friend -> owns -> file
  arbiter.setRelationConfig('friend_file', {
    type: 'chain',
    steps: [
      { relation: 'friend', direction: 'out' },
      { relation: 'owns', direction: 'out' }
    ]
  });
  
  // Friend-of-friend rule: user -> friend -> friend -> owns -> file
  arbiter.setRelationConfig('friend_of_friend_file', {
    type: 'chain',
    steps: [
      { relation: 'friend', direction: 'out' },
      { relation: 'friend', direction: 'out' },
      { relation: 'owns', direction: 'out' }
    ]
  });
  
  console.log(`   Created ${arbiter.relations.length} relations`);
  console.log(`   ${numUsers} users, ${numFiles} files`);
  
  return { arbiter, users, files, clusters };
}

// Generate test queries with known expected results
function generateTestQueries(setup) {
  const { arbiter, users, files, clusters } = setup;
  const queries = [];
  
  // Build helper maps to find actual valid paths
  const userToFriends = new Map();
  const friendToFiles = new Map();
  
  for (const rel of arbiter.relations) {
    const srcKey = arbiter.keyByNodeId.get(rel.src);
    const dstKey = arbiter.keyByNodeId.get(rel.dst);
    if (!srcKey || !dstKey) continue;
    
    const relName = rel.rel || rel.relation;
    
    if (relName === 'friend') {
      if (!userToFriends.has(srcKey)) userToFriends.set(srcKey, []);
      userToFriends.get(srcKey).push(dstKey);
    } else if (relName === 'owns' && users.includes(srcKey) && files.includes(dstKey)) {
      if (!friendToFiles.has(srcKey)) friendToFiles.set(srcKey, []);
      friendToFiles.get(srcKey).push(dstKey);
    }
  }
  
  // Test Case 1: Direct friend file access (should PASS)
  // Find a user with a friend who owns a file
  let foundFriendFile = false;
  for (const user of users) {
    const friends = userToFriends.get(user) || [];
    for (const friend of friends) {
      const friendFiles = friendToFiles.get(friend) || [];
      if (friendFiles.length > 0) {
  queries.push({
          sourceKey: user,
          targetKey: friendFiles[0],
    relation: 'friend_file',
    expected: true,
          description: `Direct friend file access: ${user} -> friend -> ${friend} -> owns -> ${friendFiles[0]}`
        });
        foundFriendFile = true;
        break;
      }
    }
    if (foundFriendFile) break;
  }
  
  // Test Case 2: Friend-of-friend file access (should PASS)
  // Find a user -> friend -> friend -> owns -> file path
  let foundFoFFile = false;
  for (const user of users) {
    const friends = userToFriends.get(user) || [];
    for (const friend of friends) {
      const friendFriends = userToFriends.get(friend) || [];
      for (const fof of friendFriends) {
        if (fof === user) continue; // Skip self
        const fofFiles = friendToFiles.get(fof) || [];
        if (fofFiles.length > 0) {
  queries.push({
            sourceKey: user,
            targetKey: fofFiles[0],
    relation: 'friend_of_friend_file',
    expected: true,
            description: `Friend-of-friend file access: ${user} -> ${friend} -> ${fof} -> owns -> ${fofFiles[0]}`
          });
          foundFoFFile = true;
          break;
        }
      }
      if (foundFoFFile) break;
    }
    if (foundFoFFile) break;
  }
  
  // Test Case 3: No path (should FAIL)
  // Find a file owned by a user with no connection to source
  queries.push({
    sourceKey: clusters[0][0],
    targetKey: 'file:file150', // Owned by cluster 3, no connection
    relation: 'friend_file',
    expected: false,
    description: 'No path (different cluster, no connection)'
  });
  
  // Test Case 4: Direct ownership (should PASS via 'owns', not chain)
  queries.push({
    sourceKey: clusters[0][0],
    targetKey: 'file:file0',
    relation: 'owns',
    expected: true,
    description: 'Direct ownership'
  });
  
  // Test Case 5: Simple friend_file within cluster (should PASS)
  // Find a user -> friend -> owns -> file path within same cluster
  for (const user of clusters[0]) {
    const friends = userToFriends.get(user) || [];
    for (const friend of friends) {
      if (!clusters[0].includes(friend)) continue; // Must be in same cluster
      const friendFiles = friendToFiles.get(friend) || [];
      if (friendFiles.length > 0) {
        queries.push({
          sourceKey: user,
          targetKey: friendFiles[0],
          relation: 'friend_file',
          expected: true,
          description: `Friend file access within cluster: ${user} -> ${friend} -> owns -> ${friendFiles[0]}`
        });
        return queries; // Found all test cases
      }
    }
  }
  
  return queries;
}

// Trace execution path for a single query
function traceQuery(arbiter, query) {
  const { sourceKey, targetKey, relation } = query;
  const trace = {
    query,
    timings: {},
    results: {},
    path: []
  };
  
  const sourceId = arbiter.nodeIdByKey.get(sourceKey);
  const targetId = arbiter.nodeIdByKey.get(targetKey);
  
  trace.path.push(`Query: ${sourceKey} -> ${targetKey} (${relation})`);
  trace.path.push(`Node IDs: ${sourceId} -> ${targetId}`);
  
  // Phase 1: PLTC reachability check
  const rc = arbiter.graphManager.operations.reachabilityChecker;
  const pltcStart = Date.now();
  
  if (rc && rc.pltcIndex && rc.pltcIndex.initialized) {
    // PLTC handles SCC internally - query directly with original node IDs
    const pltcQuery = rc.pltcIndex.query(sourceId, targetId);
    const isReachable = arbiter.isReachable(sourceKey, targetKey);
    
    trace.timings.pltc = Date.now() - pltcStart;
    trace.results.pltc = {
      pltcQuery,
      isReachable
    };
    
    trace.path.push(`PLTC: query=${pltcQuery}, isReachable=${isReachable}`);
  } else {
    trace.results.pltc = { error: 'PLTC not initialized' };
  }
  
  // Phase 2: Authorization check (full chain evaluation)
  const authStart = Date.now();
  const authResult = arbiter.check(sourceKey, relation, targetKey, { noInfer: true });
  trace.timings.auth = Date.now() - authStart;
  
  const authPass = authResult && authResult.possibility > 0;
  trace.results.auth = {
    result: authResult,
    pass: authPass,
    method: authResult?.meta?.method || 'unknown'
  };
  
  trace.path.push(`Auth: ${authPass ? 'ALLOW' : 'DENY'} (method: ${trace.results.auth.method})`);
  
  // Phase 3: Manual chain traversal (if chain rule)
  if (relation === 'friend_file' || relation === 'friend_of_friend_file') {
    trace.path.push(`Manual chain traversal:`);
    const relationConfig = arbiter.relationConfigs.get(relation);
    if (relationConfig && relationConfig.type === 'chain') {
      const steps = relationConfig.steps;
      trace.path.push(`  Steps: ${steps.map(s => `${s.relation}:${s.direction}`).join(' -> ')}`);
      
      // Manually trace the chain
      let currentNodes = [sourceId];
      for (let stepIdx = 0; stepIdx < steps.length; stepIdx++) {
        const step = steps[stepIdx];
        const nextNodes = [];
        
        for (const nodeId of currentNodes) {
          const nodeKey = arbiter.keyByNodeId.get(nodeId);
          const outgoing = arbiter.relations.filter(r => 
            r.src === nodeId && (r.rel || r.relation) === step.relation
          );
          
          trace.path.push(`  Step ${stepIdx + 1} (${step.relation}): ${nodeKey} has ${outgoing.length} outgoing edges`);
          
          for (const rel of outgoing) {
            nextNodes.push(rel.dst);
            const dstKey = arbiter.keyByNodeId.get(rel.dst);
            trace.path.push(`    → ${dstKey}`);
          }
        }
        
        currentNodes = nextNodes;
        
        // Check if target is reachable at this step
        if (currentNodes.includes(targetId)) {
          trace.path.push(`  ✓ Target reached at step ${stepIdx + 1}!`);
          break;
        }
      }
      
      if (!currentNodes.includes(targetId)) {
        trace.path.push(`  ✗ Target not reached through chain`);
      }
    }
  }
  
  return trace;
}

// Main diagnostic function
function runDiagnostics() {
  console.log('🔬 B2C Diagnostic Test\n');
  console.log('='.repeat(60));
  
  // Setup
  const setup = setupSmallB2C();
  const { arbiter } = setup;
  
  // Initialize PLTC
  console.log('\n⚡ Initializing PLTC...');
  const initStart = Date.now();
  arbiter.graphManager.initializeReachabilityChecker();
  const initTime = Date.now() - initStart;
  const rc = arbiter.graphManager.operations.reachabilityChecker;
  
  console.log(`   Initialization time: ${initTime}ms`);
  console.log(`   PLTC initialized: ${rc.pltcIndex.initialized}`);
  console.log(`   Graph edges: ${rc.pltcIndex.graph.edges.size}`);
  console.log(`   SCC count: ${rc.pltcIndex.sccUnionFind?.getComponentCount() || 'N/A'}`);
  
  // Generate test queries
  const queries = generateTestQueries(setup);
  
  // Trace each query
  console.log('\n📊 Tracing queries...');
  const allTraces = [];
  let totalPLTCTime = 0;
  let totalAuthTime = 0;
  
  for (const query of queries) {
    console.log(`\n${'-'.repeat(60)}`);
    console.log(`Query: ${query.description}`);
    console.log(`  ${query.sourceKey} -> ${query.targetKey} (${query.relation})`);
    console.log(`  Expected: ${query.expected ? 'PASS' : 'FAIL'}`);
    
    const trace = traceQuery(arbiter, query);
    allTraces.push(trace);
    
    totalPLTCTime += trace.timings.pltc || 0;
    totalAuthTime += trace.timings.auth || 0;
    
    // Print trace
    for (const line of trace.path) {
      console.log(`  ${line}`);
    }
    
    // Check result
    const actual = trace.results.auth.pass;
    const match = actual === query.expected;
    console.log(`  Result: ${actual ? 'PASS' : 'FAIL'} ${match ? '✓' : '❌'} (Expected: ${query.expected ? 'PASS' : 'FAIL'})`);
    
    if (!match) {
      console.log(`  ⚠️  MISMATCH!`);
      if (query.expected && !actual) {
        console.log(`     False negative: Should pass but failed`);
        console.log(`     PLTC query: ${trace.results.pltc?.pltcQuery}`);
        console.log(`     isReachable: ${trace.results.pltc?.isReachable}`);
      } else {
        console.log(`     False positive: Should fail but passed`);
      }
    }
  }
  
  // Performance summary
  console.log(`\n${'='.repeat(60)}`);
  console.log('Performance Summary:');
  console.log('='.repeat(60));
  console.log(`Total queries: ${queries.length}`);
  console.log(`Total PLTC time: ${totalPLTCTime}ms (${(totalPLTCTime / queries.length).toFixed(2)}ms/query)`);
  console.log(`Total Auth time: ${totalAuthTime}ms (${(totalAuthTime / queries.length).toFixed(2)}ms/query)`);
  const pltcQPS = totalPLTCTime > 0 ? Math.round(queries.length / (totalPLTCTime / 1000)) : 'N/A';
  const authQPS = totalAuthTime > 0 ? Math.round(queries.length / (totalAuthTime / 1000)) : 'N/A';
  console.log(`PLTC QPS: ${pltcQPS}`);
  console.log(`Auth QPS: ${authQPS}`);
  
  // Accuracy summary
  const correct = allTraces.filter((t, i) => 
    t.results.auth.pass === queries[i].expected
  ).length;
  console.log(`\nAccuracy: ${correct}/${queries.length} (${(correct / queries.length * 100).toFixed(1)}%)`);
  
  // Diagnose issues
  console.log(`\n${'='.repeat(60)}`);
  console.log('Diagnosis:');
  console.log('='.repeat(60));
  
  const falseNegatives = allTraces.filter((t, i) => 
    queries[i].expected && !t.results.auth.pass
  );
  
  if (falseNegatives.length > 0) {
    console.log(`\n⚠️  False Negatives (${falseNegatives.length}):`);
    for (const trace of falseNegatives) {
      console.log(`  ${trace.query.description}`);
      console.log(`    PLTC query: ${trace.results.pltc?.pltcQuery}`);
      console.log(`    isReachable: ${trace.results.pltc?.isReachable}`);
      console.log(`    Auth method: ${trace.results.auth.method}`);
    }
  }
  
  const falsePositives = allTraces.filter((t, i) => 
    !queries[i].expected && t.results.auth.pass
  );
  
  if (falsePositives.length > 0) {
    console.log(`\n⚠️  False Positives (${falsePositives.length}):`);
    for (const trace of falsePositives) {
      console.log(`  ${trace.query.description}`);
    }
  }
  
  if (falseNegatives.length === 0 && falsePositives.length === 0) {
    console.log(`\n✓ All queries correct!`);
  }
  
  // Performance analysis: Why is QPS low in full scenario?
  console.log(`\n${'='.repeat(60)}`);
  console.log('Performance Analysis:');
  console.log('='.repeat(60));
  
  const avgPLTCTime = totalPLTCTime / queries.length;
  const avgAuthTime = totalAuthTime / queries.length;
  const pltcFastFail = allTraces.filter(t => 
    t.results.auth.method === 'pltc_fast_fail'
  ).length;
  const pltcPass = allTraces.filter(t => 
    t.results.pltc?.pltcQuery === true && t.results.auth.pass
  ).length;
  
  console.log(`Average PLTC time: ${avgPLTCTime.toFixed(2)}ms`);
  console.log(`Average Auth time: ${avgAuthTime.toFixed(2)}ms`);
  console.log(`PLTC fast-fail count: ${pltcFastFail}`);
  console.log(`PLTC pass (true positive): ${pltcPass}`);
  console.log(`\nObservations:`);
  console.log(`  - PLTC is ${(avgPLTCTime / avgAuthTime).toFixed(1)}x faster than full auth check`);
  console.log(`  - ${((pltcFastFail / queries.length) * 100).toFixed(1)}% queries fast-failed by PLTC`);
  console.log(`  - ${((pltcPass / queries.length) * 100).toFixed(1)}% queries passed PLTC and auth`);
  
  // Check if chain evaluation is the bottleneck
  const chainQueries = queries.filter(q => q.relation === 'friend_file' || q.relation === 'friend_of_friend_file');
  if (chainQueries.length > 0) {
    const chainTraces = allTraces.filter((t, i) => 
      chainQueries.some(cq => cq === queries[i])
    );
    const avgChainTime = chainTraces.reduce((sum, t) => sum + (t.timings.auth || 0), 0) / chainTraces.length;
    console.log(`  - Average chain evaluation time: ${avgChainTime.toFixed(2)}ms`);
    console.log(`  - Chain queries: ${chainQueries.length}/${queries.length}`);
  }
  
  // Debug: Check if PLTC is missing paths due to condensation
  console.log(`\n${'='.repeat(60)}`);
  console.log('PLTC Path Verification:');
  console.log('='.repeat(60));
  
  // For each chain query, verify if PLTC should see the path
  for (let i = 0; i < queries.length; i++) {
    const query = queries[i];
    const trace = allTraces[i];
    
    if (query.relation === 'friend_file' || query.relation === 'friend_of_friend_file') {
      const sourceId = arbiter.nodeIdByKey.get(query.sourceKey);
      const targetId = arbiter.nodeIdByKey.get(query.targetKey);
      
      // PLTC handles SCC internally - query directly
      const pltcQuery = rc.pltcIndex.query(sourceId, targetId);
      
      console.log(`\nQuery: ${query.sourceKey} -> ${query.targetKey} (${query.relation})`);
      console.log(`  PLTC query result: ${pltcQuery}`);
      console.log(`  Expected PLTC result: ${query.expected ? 'true' : 'false'}`);
      
      if (pltcQuery !== query.expected && query.expected) {
        console.log(`  ⚠️  PLTC MISMATCH: Expected true but got ${pltcQuery}`);
        console.log(`     This suggests PLTC's transitive closure is missing the path`);
      }
    }
  }
}

runDiagnostics();

