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

const arbiter = new Arbiter();
const SIZE = 5000;

// Build scenario
for (let i = 0; i < SIZE; i++) {
  arbiter.addNode(`user:${i}`, 'user');
  arbiter.addNode(`resource:${i}`, 'resource');
  const isSafe = i % 2 === 0;
  arbiter.addRelation(`user:${i}`, 'risk_score', `resource:${i}`, 1.0, { value: isSafe ? 20 : 80 });
  arbiter.addRelation(`user:${i}`, 'risk_bonus', `resource:${i}`, 1.0, { value: isSafe ? 5 : 15 });
  arbiter.addRelation(`user:${i}`, 'risk_noise', `resource:${i}`, 1.0, { value: isSafe ? 0 : 10 });
  arbiter.addRelation(`resource:${i}`, 'risk_limit', `resource:${i}`, 1.0, { value: 40 });
  arbiter.addRelation(`resource:${i}`, 'risk_cap', `resource:${i}`, 1.0, { value: 45 });
}

['risk_score', 'risk_bonus', 'risk_noise', 'risk_limit', 'risk_cap'].forEach(r => 
  arbiter.setRelationConfig(r, { type: 'direct' }));

arbiter.setRelationConfig('risk_ok_owa', {
  type: 'relational_comparator',
  comparator: '<=',
  fallbackBehavior: 'deny',
  left: {
    rule: {
      union: {
        rules: [
          { type: 'direct', relation: 'risk_score' },
          { type: 'direct', relation: 'risk_bonus' },
          { type: 'direct', relation: 'risk_noise' }
        ],
        aggregator: 'owa',
        owaWeights: [0.5, 0.3, 0.2]
      }
    },
    extractValue: true,
    valueRelation: 'risk_score',
    aggregator: 'owa',
    owaWeights: [0.5, 0.3, 0.2]
  },
  right: {
    rule: {
      union: {
        rules: [
          { type: 'direct', relation: 'risk_limit' },
          { type: 'direct', relation: 'risk_cap' }
        ],
        aggregator: 'owa',
        owaWeights: [0.6, 0.4]
      }
    },
    extractValue: true,
    valueRelation: 'risk_limit',
    evaluateFrom: 'object',
    aggregator: 'owa',
    owaWeights: [0.6, 0.4]
  }
});

// Warm up
for (let i = 0; i < 100; i++) {
  arbiter.check(`user:${i}`, 'risk_ok_owa', `resource:${i}`, { fastPath: true });
}

const ITERATIONS = 2000;

function measure(label, fn) {
  const start = process.hrtime.bigint();
  for (let i = 0; i < ITERATIONS; i++) fn(i % SIZE);
  const ms = Number(process.hrtime.bigint() - start) / 1e6;
  return { label, ms, qps: Math.round((ITERATIONS / ms) * 1000), perOp: ms / ITERATIONS };
}

console.log('=== AuthChecker.check() Breakdown ===\n');

const config = arbiter.relationConfigs.get('risk_ok_owa');
const ruleEval = arbiter.authChecker.ruleEvaluator;

// What AuthChecker.check does for a relational_comparator:
// 1. Parse options (backward compat check)
// 2. Get config from relationConfigs
// 3. Check if fast path (NO - it's relational_comparator, not direct)
// 4. Call _ensureIndicesBuilt
// 5. Create visitKey object
// 6. Scan visited set for cycles
// 7. Add visitKey to visited
// 8. Get userId and objectId from nodeIdByKey
// 9. Check if config exists
// 10. Build evaluationPath object
// 11. Call ruleEvaluator.evaluateRule (since no union/intersection/exclusion at top level)
// 12. Iterate through collected rules and call ruleEvaluator for each
// 13. Build result object with meta

// Let's measure each step

// Step 1-2: Options parsing + config lookup
const configGet = measure('Config lookup', () => {
  arbiter.relationConfigs.get('risk_ok_owa');
});
console.log(`Config lookup: ${(configGet.perOp * 1000).toFixed(3)}µs`);

// Step 4: _ensureIndicesBuilt
const ensureIndices = measure('_ensureIndicesBuilt', () => {
  arbiter.relationManager._ensureIndicesBuilt();
});
console.log(`_ensureIndicesBuilt: ${(ensureIndices.perOp * 1000).toFixed(3)}µs`);

// Step 5: Create visitKey object
const createVisitKey = measure('Create visitKey object', (idx) => {
  const visitKey = { userKey: `user:${idx}`, relation: 'risk_ok_owa', objectKey: `resource:${idx}` };
});
console.log(`Create visitKey: ${(createVisitKey.perOp * 1000).toFixed(3)}µs`);

// Step 6: Scan visited set (empty)
const visited = new Set();
const scanEmpty = measure('Scan empty visited', () => {
  for (const v of visited) {
    if (v.userKey === 'user:0') break;
  }
});
console.log(`Scan empty visited: ${(scanEmpty.perOp * 1000).toFixed(3)}µs`);

// Step 7: Add to visited
const addVisited = measure('Add to visited', (idx) => {
  const s = new Set();
  s.add({ userKey: `user:${idx}`, relation: 'risk_ok_owa', objectKey: `resource:${idx}` });
});
console.log(`Add to visited (new Set + add): ${(addVisited.perOp * 1000).toFixed(3)}µs`);

// Step 8: Get IDs
const getIds = measure('Get user/object IDs', (idx) => {
  arbiter.nodeIdByKey.get(`user:${idx}`);
  arbiter.nodeIdByKey.get(`resource:${idx}`);
});
console.log(`Get IDs: ${(getIds.perOp * 1000).toFixed(3)}µs`);

// Step 10: Build evaluationPath object
const buildEvalPath = measure('Build evaluationPath object', (idx) => {
  const evaluationPath = {
    userKey: `user:${idx}`,
    relation: 'risk_ok_owa',
    objectKey: `resource:${idx}`,
    config: config,
    rules: [],
    visitedPath: []  // Would be Array.from(_visited) but that's expensive
  };
});
console.log(`Build evaluationPath: ${(buildEvalPath.perOp * 1000).toFixed(3)}µs`);

// Step 10b: Array.from(_visited) - THIS IS LIKELY EXPENSIVE
const visitedWith5 = new Set();
for (let i = 0; i < 5; i++) {
  visitedWith5.add({ userKey: `user:${i}`, relation: 'test', objectKey: `resource:${i}` });
}
const arrayFrom = measure('Array.from(visited) with 5 entries', () => {
  Array.from(visitedWith5);
});
console.log(`Array.from(visited) x5: ${(arrayFrom.perOp * 1000).toFixed(3)}µs`);

// Step 11: ruleEvaluator.evaluateRule for comparator
const evalRule = measure('ruleEvaluator.evaluateRule', (idx) => {
  const userId = arbiter.nodeIdByKey.get(`user:${idx}`);
  const objectId = arbiter.nodeIdByKey.get(`resource:${idx}`);
  ruleEval.evaluateRule(userId, `user:${idx}`, objectId, `resource:${idx}`,
    config, new Set(), 'risk_ok_owa', { fastPath: true });
});
console.log(`ruleEvaluator.evaluateRule: ${(evalRule.perOp * 1000).toFixed(3)}µs`);

// Full check for comparison
const fullCheck = measure('Full arbiter.check', (idx) => {
  arbiter.check(`user:${idx}`, 'risk_ok_owa', `resource:${idx}`, { fastPath: true });
});
console.log(`Full arbiter.check: ${(fullCheck.perOp * 1000).toFixed(3)}µs`);

// Difference analysis
const overhead = fullCheck.perOp - evalRule.perOp;
console.log(`\n=== AuthChecker overhead: ${(overhead * 1000).toFixed(2)}µs ===`);

// What's causing it?
const sumOfParts = configGet.perOp + ensureIndices.perOp + createVisitKey.perOp + 
                   addVisited.perOp + getIds.perOp + buildEvalPath.perOp + arrayFrom.perOp;
console.log(`Sum of measured parts: ${(sumOfParts * 1000).toFixed(2)}µs`);
console.log(`Unaccounted overhead: ${((overhead - sumOfParts) * 1000).toFixed(2)}µs`);

// Check what happens inside RuleEvaluator.evaluateRule
console.log('\n=== RuleEvaluator.evaluateRule Breakdown ===\n');

// The evaluateRule does:
// 1. Extract binary, valueContext from options
// 2. Convert string IDs to numeric (already numeric here)
// 3. Check if rule needs values (_ruleRequiresValues) - THIS COULD BE EXPENSIVE
// 4. Create enhanced options
// 5. Route to handler

const needsValues = measure('_ruleRequiresValues', () => {
  ruleEval._ruleRequiresValues(config, new Set());
});
console.log(`_ruleRequiresValues: ${(needsValues.perOp * 1000).toFixed(3)}µs`);

// Handler.evaluate directly
const handler = ruleEval.ruleHandlers.relational_comparator;
const handlerEval = measure('handler.evaluate (direct)', (idx) => {
  const userId = arbiter.nodeIdByKey.get(`user:${idx}`);
  const objectId = arbiter.nodeIdByKey.get(`resource:${idx}`);
  handler.evaluate(userId, `user:${idx}`, objectId, `resource:${idx}`,
    config, new Set(), 'risk_ok_owa', { fastPath: true });
});
console.log(`handler.evaluate: ${(handlerEval.perOp * 1000).toFixed(3)}µs`);

// Check _ruleRequiresValues deeply
console.log('\n=== _ruleRequiresValues Analysis ===\n');
// This recursively checks the rule tree
// For relational_comparator, it returns true immediately
// But does it actually traverse?
const visited2 = new Set();
const start = process.hrtime.bigint();
for (let i = 0; i < 10000; i++) {
  visited2.clear();
  ruleEval._ruleRequiresValues(config, visited2);
}
const requiresMs = Number(process.hrtime.bigint() - start) / 1e6;
console.log(`_ruleRequiresValues (10k iterations): ${requiresMs.toFixed(2)}ms`);
console.log(`Per call: ${(requiresMs / 10).toFixed(3)}µs`);

// Actually look at what the method does for relational_comparator
console.log(`\nrule.type = '${config.type}'`);
console.log(`_ruleRequiresValues returns true immediately for relational_comparator`);
