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

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

const args = parseArgs(process.argv);
const config = {
  sizes: parseSizes(args.get('sizes')) || [10000, 40000, 80000],
  samples: Number(args.get('samples') || 200000),
  seed: Number(args.get('seed') || 42),
  maxSeconds: Number(args.get('max-seconds') || 60)
};

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

console.log('edge_mutation_bench');
console.log('size,edges,metric,elapsed_ms,qps');

for (const size of config.sizes) {
  if ((Date.now() - startTime) / 1000 > config.maxSeconds) break;

  const arbiter = new Arbiter();
  buildNodes(arbiter, size, 'user');
  buildNodes(arbiter, size, 'resource');

  arbiter.setRelationConfig('link', { type: 'direct' });
  arbiter.setRelationConfig('link_add', { type: 'direct' });

  const currentDst = new Array(size);
  for (let i = 0; i < size; i++) {
    arbiter.addRelation(`user:${i}`, 'link', `resource:${i}`);
    currentDst[i] = i;
  }

  const addCount = Math.min(config.samples, size);
  const addPairs = new Array(addCount);
  for (let i = 0; i < addCount; i++) {
    addPairs[i] = {
      src: i % size,
      dst: (i * 7) % size
    };
  }

  const updateOps = new Array(config.samples);
  for (let i = 0; i < config.samples; i++) {
    const src = randInt(rng, size);
    let dst = randInt(rng, size);
    if (dst === currentDst[src]) {
      dst = (dst + 1) % size;
    }
    updateOps[i] = { src, dst };
  }

  const addMetric = measure('add_relations', addCount, index => {
    const pair = addPairs[index];
    arbiter.addRelation(`user:${pair.src}`, 'link_add', `resource:${pair.dst}`);
  });

  const removeMetric = measure('remove_relations', addCount, index => {
    const pair = addPairs[index];
    arbiter.removeRelation(`user:${pair.src}`, 'link_add', `resource:${pair.dst}`);
  });

  const updateMetric = measure('update_relations', updateOps.length, index => {
    const op = updateOps[index];
    const srcKey = `user:${op.src}`;
    const oldDst = currentDst[op.src];
    arbiter.removeRelation(srcKey, 'link', `resource:${oldDst}`);
    arbiter.addRelation(srcKey, 'link', `resource:${op.dst}`);
    currentDst[op.src] = op.dst;
  });

  const metrics = [
    { metric: addMetric, edges: addCount },
    { metric: removeMetric, edges: addCount },
    { metric: updateMetric, edges: size }
  ];

  for (const entry of metrics) {
    console.log([
      size,
      entry.edges,
      entry.metric.label,
      Math.round(entry.metric.elapsedMs),
      entry.metric.qps
    ].join(','));
  }
}
