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

function parseArgs(argv) {
  const args = new Map();
  for (let i = 2; i < argv.length; i++) {
    const value = argv[i];
    if (!value.startsWith('--')) continue;
    const [key, inline] = value.slice(2).split('=');
    if (inline !== undefined) {
      args.set(key, inline);
      continue;
    }
    const next = argv[i + 1];
    if (next && !next.startsWith('--')) {
      args.set(key, next);
      i++;
    } else {
      args.set(key, true);
    }
  }
  return args;
}

function parseSizes(value) {
  if (!value) return null;
  return value.split(',').map(item => Number(item.trim())).filter(Number.isFinite);
}

function createRng(seed) {
  let state = seed >>> 0;
  return () => {
    state = (1664525 * state + 1013904223) >>> 0;
    return state / 0x100000000;
  };
}

function randInt(rng, max) {
  return Math.floor(rng() * max);
}

function buildUsers(arbiter, count, prefix) {
  for (let i = 0; i < count; i++) {
    arbiter.addNode(`${prefix}:${i}`, prefix);
  }
}

function buildScenario(arbiter, size) {
  const trueIds = [];
  const falseIds = [];
  buildUsers(arbiter, size, 'user');
  buildUsers(arbiter, size, 'resource');
  for (let i = 0; i < size; i++) {
    const isSafe = i % 2 === 0;
    const score = isSafe ? 20 : 80;
    const bonus = isSafe ? 5 : 15;
    const noise = isSafe ? 0 : 10;
    arbiter.addRelation(`user:${i}`, 'risk_score', `resource:${i}`, 1.0, { value: score });
    arbiter.addRelation(`user:${i}`, 'risk_bonus', `resource:${i}`, 1.0, { value: bonus });
    arbiter.addRelation(`user:${i}`, 'risk_noise', `resource:${i}`, 1.0, { value: noise });
    arbiter.addRelation(`resource:${i}`, 'risk_limit', `resource:${i}`, 1.0, { value: 40 });
    arbiter.addRelation(`resource:${i}`, 'risk_cap', `resource:${i}`, 1.0, { value: 45 });
    if (isSafe) trueIds.push(i);
    else falseIds.push(i);
  }
  arbiter.setRelationConfig('risk_score', { type: 'direct' });
  arbiter.setRelationConfig('risk_bonus', { type: 'direct' });
  arbiter.setRelationConfig('risk_noise', { type: 'direct' });
  arbiter.setRelationConfig('risk_limit', { type: 'direct' });
  arbiter.setRelationConfig('risk_cap', { 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]
    }
  });
  return { trueIds, falseIds };
}

function buildQueries(size, rng, samples, trueIds, falseIds) {
  const queries = [];
  const half = Math.floor(samples / 2);
  for (let i = 0; i < half; i++) {
    const userId = trueIds.length ? trueIds[randInt(rng, trueIds.length)] : randInt(rng, size);
    queries.push({ userKey: `user:${userId}`, objectKey: `resource:${userId}` });
  }
  for (let i = 0; i < samples - half; i++) {
    const userId = falseIds.length ? falseIds[randInt(rng, falseIds.length)] : randInt(rng, size);
    queries.push({ userKey: `user:${userId}`, objectKey: `resource:${userId}` });
  }
  return queries;
}

function measure(label, iterations, fn) {
  const start = process.hrtime.bigint();
  for (let i = 0; i < iterations; i++) {
    fn();
  }
  const elapsedMs = Number(process.hrtime.bigint() - start) / 1e6;
  return { label, elapsedMs, qps: elapsedMs > 0 ? Math.round((iterations / elapsedMs) * 1000) : 0 };
}

function measureMedian(label, iterations, fn, warmups, runs) {
  for (let w = 0; w < warmups; w++) {
    for (let i = 0; i < iterations; i++) fn();
  }

  const samples = [];
  for (let r = 0; r < runs; r++) {
    const result = measure(label, iterations, fn);
    samples.push(result.qps);
  }

  samples.sort((a, b) => a - b);
  const mid = Math.floor(samples.length / 2);
  const median = samples.length % 2 === 0
    ? Math.round(((samples[mid - 1] + samples[mid]) / 2) * 100) / 100
    : samples[mid];
  return { label, elapsedMs: null, qps: median };
}

const args = parseArgs(process.argv);
const config = {
  sizes: parseSizes(args.get('sizes')) || [5000, 10000, 20000],
  samples: Number(args.get('samples') || 2000),
  seed: Number(args.get('seed') || 42),
  maxSeconds: Number(args.get('max-seconds') || 60),
  includeMeta: !args.has('no-meta'),
  collectValues: !args.has('no-values'),
  warmupRuns: Number(args.get('warmup-runs') || 2),
  medianRuns: Number(args.get('median-runs') || 5)
};

const rng = createRng(config.seed);
const startTime = Date.now();

console.log('owa_comparator_micro_bench');
console.log('size,samples,meta,values,metric,elapsed_ms,qps');

for (const size of config.sizes) {
  if ((Date.now() - startTime) / 1000 > config.maxSeconds) break;
  const arbiter = new Arbiter();
  const { trueIds, falseIds } = buildScenario(arbiter, size);
  const queries = buildQueries(size, rng, config.samples, trueIds, falseIds);
  const comparatorRule = arbiter.authChecker.ruleEvaluator.ruleHandlers.relational_comparator.numericRule;
  const visited = new Set();
  const baseOptions = { fastPath: true, includeMeta: config.includeMeta, collectValues: config.collectValues };
  const valuesOptions = { fastPath: true, includeMeta: config.includeMeta, collectValues: true };

  const leftUnionRule = {
    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]
    }
  };
  const rightUnionRule = {
    union: {
      rules: [
        { type: 'direct', relation: 'risk_limit' },
        { type: 'direct', relation: 'risk_cap' }
      ],
      aggregator: 'owa',
      owaWeights: [0.6, 0.4]
    }
  };

  const fullComparator = measureMedian('comparator_full', queries.length, () => {
    const q = queries[randInt(rng, queries.length)];
    arbiter.check(q.userKey, 'risk_ok_owa', q.objectKey, baseOptions);
  }, config.warmupRuns, config.medianRuns);

  const fullComparatorValues = measureMedian('comparator_values_agg', queries.length, () => {
    const q = queries[randInt(rng, queries.length)];
    arbiter.check(q.userKey, 'risk_ok_owa', q.objectKey, valuesOptions);
  }, config.warmupRuns, config.medianRuns);

  const leftUnionEval = measureMedian('left_union_eval', queries.length, () => {
    const q = queries[randInt(rng, queries.length)];
    const userId = arbiter.nodeIdByKey.get(q.userKey);
    const objectId = arbiter.nodeIdByKey.get(q.objectKey);
    arbiter.authChecker.ruleEvaluator.evaluateRule(
      userId,
      q.userKey,
      objectId,
      q.objectKey,
      leftUnionRule,
      visited,
      'risk_ok_owa',
      baseOptions
    );
  }, config.warmupRuns, config.medianRuns);

  const rightUnionEval = measureMedian('right_union_eval', queries.length, () => {
    const q = queries[randInt(rng, queries.length)];
    const userId = arbiter.nodeIdByKey.get(q.userKey);
    const objectId = arbiter.nodeIdByKey.get(q.objectKey);
    arbiter.authChecker.ruleEvaluator.evaluateRule(
      objectId,
      q.objectKey,
      objectId,
      q.objectKey,
      rightUnionRule,
      visited,
      'risk_ok_owa',
      baseOptions
    );
  }, config.warmupRuns, config.medianRuns);

  const leftExtract = measureMedian('left_extract', queries.length, () => {
    const q = queries[randInt(rng, queries.length)];
    const userId = arbiter.nodeIdByKey.get(q.userKey);
    const objectId = arbiter.nodeIdByKey.get(q.objectKey);
    const ruleResult = arbiter.authChecker.ruleEvaluator.evaluateRule(
      userId,
      q.userKey,
      objectId,
      q.objectKey,
      leftUnionRule,
      visited,
      'risk_ok_owa',
      baseOptions
    );
    comparatorRule._extractValues(
      userId,
      q.userKey,
      objectId,
      q.objectKey,
      leftUnionRule,
      'risk_score',
      ruleResult,
      'auto',
      null,
      24 * 60 * 60 * 1000,
      null,
      baseOptions
    );
  }, config.warmupRuns, config.medianRuns);

  const rightExtract = measureMedian('right_extract', queries.length, () => {
    const q = queries[randInt(rng, queries.length)];
    const userId = arbiter.nodeIdByKey.get(q.userKey);
    const objectId = arbiter.nodeIdByKey.get(q.objectKey);
    const ruleResult = arbiter.authChecker.ruleEvaluator.evaluateRule(
      objectId,
      q.objectKey,
      objectId,
      q.objectKey,
      rightUnionRule,
      visited,
      'risk_ok_owa',
      baseOptions
    );
    comparatorRule._extractValues(
      objectId,
      q.objectKey,
      objectId,
      q.objectKey,
      rightUnionRule,
      'risk_limit',
      ruleResult,
      'object',
      null,
      24 * 60 * 60 * 1000,
      null,
      baseOptions
    );
  }, config.warmupRuns, config.medianRuns);

  const leftAggregate = measureMedian('left_aggregate', queries.length, () => {
    const q = queries[randInt(rng, queries.length)];
    const userId = arbiter.nodeIdByKey.get(q.userKey);
    const objectId = arbiter.nodeIdByKey.get(q.objectKey);
    const ruleResult = arbiter.authChecker.ruleEvaluator.evaluateRule(
      userId,
      q.userKey,
      objectId,
      q.objectKey,
      leftUnionRule,
      visited,
      'risk_ok_owa',
      baseOptions
    );
    const values = comparatorRule._extractValues(
      userId,
      q.userKey,
      objectId,
      q.objectKey,
      leftUnionRule,
      'risk_score',
      ruleResult,
      'auto',
      null,
      24 * 60 * 60 * 1000,
      null,
      baseOptions
    );
    comparatorRule._aggregateCrispValues(values, 'owa', [0.5, 0.3, 0.2], null);
  }, config.warmupRuns, config.medianRuns);

  const rightAggregate = measureMedian('right_aggregate', queries.length, () => {
    const q = queries[randInt(rng, queries.length)];
    const objectId = arbiter.nodeIdByKey.get(q.objectKey);
    const ruleResult = arbiter.authChecker.ruleEvaluator.evaluateRule(
      objectId,
      q.objectKey,
      objectId,
      q.objectKey,
      rightUnionRule,
      visited,
      'risk_ok_owa',
      baseOptions
    );
    const values = comparatorRule._extractValues(
      objectId,
      q.objectKey,
      objectId,
      q.objectKey,
      rightUnionRule,
      'risk_limit',
      ruleResult,
      'object',
      null,
      24 * 60 * 60 * 1000,
      null,
      baseOptions
    );
    comparatorRule._aggregateCrispValues(values, 'owa', [0.6, 0.4], null);
  }, config.warmupRuns, config.medianRuns);

  const compareOnly = measureMedian('compare_only', queries.length, () => {
    const q = queries[randInt(rng, queries.length)];
    const userId = arbiter.nodeIdByKey.get(q.userKey);
    const objectId = arbiter.nodeIdByKey.get(q.objectKey);
    const leftValues = comparatorRule._extractValues(
      userId,
      q.userKey,
      objectId,
      q.objectKey,
      leftUnionRule,
      'risk_score',
      { collectedValues: [] },
      'auto',
      null,
      24 * 60 * 60 * 1000,
      null,
      baseOptions
    );
    const rightValues = comparatorRule._extractValues(
      objectId,
      q.objectKey,
      objectId,
      q.objectKey,
      rightUnionRule,
      'risk_limit',
      { collectedValues: [] },
      'object',
      null,
      24 * 60 * 60 * 1000,
      null,
      baseOptions
    );
    const leftAgg = comparatorRule._aggregateCrispValues(leftValues, 'owa', [0.5, 0.3, 0.2], null);
    const rightAgg = comparatorRule._aggregateCrispValues(rightValues, 'owa', [0.6, 0.4], null);
    comparatorRule._compareBlurredValues(
      { hasValue: !!leftAgg.interval, valueInterval: leftAgg.interval, operandPossibility: leftAgg.possibility, reliability: leftAgg.reliability },
      { hasValue: !!rightAgg.interval, valueInterval: rightAgg.interval, operandPossibility: rightAgg.possibility, reliability: rightAgg.reliability },
      '<=',
      'deny',
      {},
      {},
      {}
    );
  }, config.warmupRuns, config.medianRuns);

  for (const metric of [
    fullComparator,
    fullComparatorValues,
    leftUnionEval,
    rightUnionEval,
    leftExtract,
    rightExtract,
    leftAggregate,
    rightAggregate,
    compareOnly
  ]) {
    console.log([
      size,
      queries.length,
      config.includeMeta ? 'on' : 'off',
      config.collectValues ? 'on' : 'off',
      metric.label,
      metric.elapsedMs === null ? 'median' : Math.round(metric.elapsedMs),
      metric.qps
    ].join(','));
  }
}
