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('=== BASELINE MEASUREMENTS ===\n');

// Baseline: single direct check
const directCheck = measure('Direct check (single)', (idx) => {
  arbiter.check(`user:${idx}`, 'risk_score', `resource:${idx}`, { fastPath: true });
});
console.log(`${directCheck.label}: ${directCheck.qps} QPS (${(directCheck.perOp * 1000).toFixed(2)}µs/op)`);

// Full comparator check
const fullCheck = measure('Full comparator check', (idx) => {
  arbiter.check(`user:${idx}`, 'risk_ok_owa', `resource:${idx}`, { fastPath: true });
});
console.log(`${fullCheck.label}: ${fullCheck.qps} QPS (${(fullCheck.perOp * 1000).toFixed(2)}µs/op)`);

console.log(`\nSlowdown: ${(fullCheck.perOp / directCheck.perOp).toFixed(1)}x`);
console.log(`Extra time per check: ${((fullCheck.perOp - directCheck.perOp) * 1000).toFixed(2)}µs`);

console.log('\n=== COMPONENT BREAKDOWN ===\n');

// What does a comparator check actually do?
// 1. Resolve node IDs (2x)
// 2. Get relation config
// 3. Cycle detection (visited set scan)  
// 4. Left operand: evaluate union (3 direct rules) + extract values + aggregate
// 5. Right operand: evaluate union (2 direct rules) + extract values + aggregate
// 6. Compare intervals
// 7. Build result meta

// Measure individual components
const ruleEval = arbiter.authChecker.ruleEvaluator;
const comparatorHandler = ruleEval.ruleHandlers.relational_comparator.numericRule;
const config = arbiter.relationConfigs.get('risk_ok_owa');

// Component 1: Node ID resolution (2 lookups)
const nodeIdLookup = measure('Node ID lookup (2x)', (idx) => {
  arbiter.nodeIdByKey.get(`user:${idx}`);
  arbiter.nodeIdByKey.get(`resource:${idx}`);
});
console.log(`${nodeIdLookup.label}: ${(nodeIdLookup.perOp * 1000).toFixed(3)}µs`);

// Component 2: Config lookup
const configLookup = measure('Config lookup', () => {
  arbiter.relationConfigs.get('risk_ok_owa');
});
console.log(`${configLookup.label}: ${(configLookup.perOp * 1000).toFixed(3)}µs`);

// Component 3: Direct check via indices (what union does internally per rule)
const directIndex = measure('Direct index lookup', (idx) => {
  const userId = arbiter.nodeIdByKey.get(`user:${idx}`);
  const objectId = arbiter.nodeIdByKey.get(`resource:${idx}`);
  arbiter.indices.getDirectRelation(userId, 'risk_score', objectId);
});
console.log(`${directIndex.label}: ${(directIndex.perOp * 1000).toFixed(3)}µs`);

// Component 4: Evaluate a single direct rule via ruleEvaluator
const singleDirect = measure('Single direct rule eval', (idx) => {
  const userId = arbiter.nodeIdByKey.get(`user:${idx}`);
  const objectId = arbiter.nodeIdByKey.get(`resource:${idx}`);
  ruleEval.evaluateRule(userId, `user:${idx}`, objectId, `resource:${idx}`,
    { type: 'direct', relation: 'risk_score' }, new Set(), 'test', { fastPath: true });
});
console.log(`${singleDirect.label}: ${(singleDirect.perOp * 1000).toFixed(3)}µs`);

// Component 5: Union of 3 direct rules  
const union3 = measure('Union (3 direct rules)', (idx) => {
  const userId = arbiter.nodeIdByKey.get(`user:${idx}`);
  const objectId = arbiter.nodeIdByKey.get(`resource:${idx}`);
  ruleEval.evaluateRule(userId, `user:${idx}`, objectId, `resource:${idx}`,
    config.left.rule, new Set(), 'test', { fastPath: true });
});
console.log(`${union3.label}: ${(union3.perOp * 1000).toFixed(3)}µs`);

// Component 6: Union of 2 direct rules
const union2 = measure('Union (2 direct rules)', (idx) => {
  const objectId = arbiter.nodeIdByKey.get(`resource:${idx}`);
  ruleEval.evaluateRule(objectId, `resource:${idx}`, objectId, `resource:${idx}`,
    config.right.rule, new Set(), 'test', { fastPath: true });
});
console.log(`${union2.label}: ${(union2.perOp * 1000).toFixed(3)}µs`);

// Component 7: _evaluateOperand (left) - includes union + extract + aggregate
const leftOp = measure('Left _evaluateOperand', (idx) => {
  const userId = arbiter.nodeIdByKey.get(`user:${idx}`);
  const objectId = arbiter.nodeIdByKey.get(`resource:${idx}`);
  comparatorHandler._evaluateOperand(
    userId, `user:${idx}`, objectId, `resource:${idx}`,
    config.left, new Set(), 'test', { fastPath: true }, 'left', 1.0, null
  );
});
console.log(`${leftOp.label}: ${(leftOp.perOp * 1000).toFixed(3)}µs`);

// Component 8: _evaluateOperand (right)
const rightOp = measure('Right _evaluateOperand', (idx) => {
  const userId = arbiter.nodeIdByKey.get(`user:${idx}`);
  const objectId = arbiter.nodeIdByKey.get(`resource:${idx}`);
  comparatorHandler._evaluateOperand(
    userId, `user:${idx}`, objectId, `resource:${idx}`,
    config.right, new Set(), 'test', { fastPath: true }, 'right', 1.0, null
  );
});
console.log(`${rightOp.label}: ${(rightOp.perOp * 1000).toFixed(3)}µs`);

// Component 9: _evaluateRule (both operands + compare)
const evalRule = measure('comparator._evaluateRule', (idx) => {
  const userId = arbiter.nodeIdByKey.get(`user:${idx}`);
  const objectId = arbiter.nodeIdByKey.get(`resource:${idx}`);
  comparatorHandler._evaluateRule(
    userId, `user:${idx}`, objectId, `resource:${idx}`,
    config, new Set(), 'test', { fastPath: true }
  );
});
console.log(`${evalRule.label}: ${(evalRule.perOp * 1000).toFixed(3)}µs`);

console.log('\n=== OVERHEAD ANALYSIS ===\n');

const directIdxTime = directIndex.perOp * 1000;
const singleDirectTime = singleDirect.perOp * 1000;
const union3Time = union3.perOp * 1000;
const leftOpTime = leftOp.perOp * 1000;
const rightOpTime = rightOp.perOp * 1000;
const evalRuleTime = evalRule.perOp * 1000;
const fullCheckTime = fullCheck.perOp * 1000;

console.log(`Index lookup:           ${directIdxTime.toFixed(2)}µs`);
console.log(`Single direct rule:     ${singleDirectTime.toFixed(2)}µs (+${(singleDirectTime - directIdxTime).toFixed(2)}µs overhead)`);
console.log(`Union (3 rules):        ${union3Time.toFixed(2)}µs (expected ~${(singleDirectTime * 3).toFixed(2)}µs, actual overhead: ${(union3Time - singleDirectTime * 3).toFixed(2)}µs)`);
console.log(`Left operand:           ${leftOpTime.toFixed(2)}µs (+${(leftOpTime - union3Time).toFixed(2)}µs for extract+aggregate)`);
console.log(`Right operand:          ${rightOpTime.toFixed(2)}µs`);
console.log(`_evaluateRule:          ${evalRuleTime.toFixed(2)}µs (expected ~${(leftOpTime + rightOpTime).toFixed(2)}µs, actual: ${evalRuleTime.toFixed(2)}µs)`);
console.log(`Full check:             ${fullCheckTime.toFixed(2)}µs (+${(fullCheckTime - evalRuleTime).toFixed(2)}µs AuthChecker overhead)`);

console.log('\n=== THEORETICAL VS ACTUAL ===\n');
const theoreticalMin = (singleDirectTime * 5) + 2; // 5 direct lookups + some overhead
console.log(`Theoretical minimum (5 direct lookups): ~${theoreticalMin.toFixed(2)}µs`);
console.log(`Actual:                                  ${fullCheckTime.toFixed(2)}µs`);
console.log(`Overhead factor:                         ${(fullCheckTime / theoreticalMin).toFixed(1)}x`);

// Check Set creation overhead
console.log('\n=== SET CREATION OVERHEAD ===\n');
const setCreate = measure('new Set()', () => new Set());
const setWithAdd = measure('new Set() + 1 add', () => {
  const s = new Set();
  s.add({ userKey: 'user:0', relation: 'test', objectKey: 'resource:0' });
});
console.log(`new Set():                ${(setCreate.perOp * 1000).toFixed(3)}µs`);
console.log(`new Set() + 1 object add: ${(setWithAdd.perOp * 1000).toFixed(3)}µs`);
console.log(`Sets created per check:   ~6-8`);
console.log(`Total Set overhead:       ~${((setCreate.perOp * 7) * 1000).toFixed(2)}µs`);

// Check object allocation in visited scan
console.log('\n=== VISITED SCAN OVERHEAD ===\n');
const visited = new Set();
for (let i = 0; i < 10; i++) {
  visited.add({ userKey: `user:${i}`, relation: 'test', objectKey: `resource:${i}` });
}
const visitedScan = measure('Scan visited (10 entries)', () => {
  const userKey = 'user:5';
  const relation = 'test';
  const objectKey = 'resource:5';
  for (const v of visited) {
    if (v.userKey === userKey && v.relation === relation && v.objectKey === objectKey) break;
  }
});
console.log(`Scan 10-entry visited set: ${(visitedScan.perOp * 1000).toFixed(3)}µs`);
console.log(`(This happens at each level of rule evaluation)`);
