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 createRng(seed) {
  let state = seed >>> 0;
  return () => {
    state = (1664525 * state + 1013904223) >>> 0;
    return state / 0x100000000;
  };
}

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

function formatBytes(bytes) {
  if (bytes === 0) return '0B';
  const units = ['B', 'KB', 'MB', 'GB'];
  const index = Math.min(units.length - 1, Math.floor(Math.log(bytes) / Math.log(1024)));
  const scaled = bytes / Math.pow(1024, index);
  return `${scaled.toFixed(2)}${units[index]}`;
}

function buildGraph({ nodes, edgesPerNode, rng, enableReachability, authMode, maxDepth }) {
  const arbiter = new Arbiter();
  for (let i = 0; i < nodes; i++) {
    arbiter.addNode(`node:${i}`, 'node');
  }

  const totalEdges = Math.max(1, Math.round(nodes * edgesPerNode));
  for (let i = 0; i < totalEdges; i++) {
    const src = Math.floor(rng() * nodes);
    const dst = Math.floor(rng() * nodes);
    if (src === dst) continue;
    arbiter.addRelation(`node:${src}`, 'link', `node:${dst}`, 1.0);
  }

  arbiter.setRelationConfig('link', { type: 'direct' });
  arbiter.setRelationConfig('can_reach', {
    type: 'multi_hop',
    relation: 'link',
    maxDepth,
    skipReachabilityCheck: true,
    collectValues: false,
    trackPaths: false,
    fallbackToBasicPaths: false
  });

  if (enableReachability) {
    arbiter.graphManager.initializeReachabilityChecker({ enableBackwardIndex: true });
  }

  return { arbiter, totalEdges };
}

function runAuthChecks({ arbiter, nodes, rng, samples, authMode }) {
  const start = process.hrtime.bigint();
  for (let i = 0; i < samples; i++) {
    const src = Math.floor(rng() * nodes);
    const dst = Math.floor(rng() * nodes);
    const relation = authMode === 'direct' ? 'link' : 'can_reach';
    arbiter.check(`node:${src}`, relation, `node:${dst}`, { fastPath: true });
  }
  const elapsedMs = Number(process.hrtime.bigint() - start) / 1e6;
  const qps = elapsedMs > 0 ? Math.round((samples / elapsedMs) * 1000) : 0;
  return { elapsedMs, qps };
}

function runMutationBench({ arbiter, nodes, rng, samples }) {
  const keys = [];
  const insertStart = process.hrtime.bigint();
  for (let i = 0; i < samples; i++) {
    const src = Math.floor(rng() * nodes);
    const dst = Math.floor(rng() * nodes);
    const srcKey = `node:${src}`;
    const dstKey = `node:${dst}`;
    keys.push([srcKey, dstKey]);
    arbiter.addRelation(srcKey, 'link', dstKey, 1.0);
  }
  const insertMs = Number(process.hrtime.bigint() - insertStart) / 1e6;
  const insertQps = insertMs > 0 ? Math.round((samples / insertMs) * 1000) : 0;

  const updateStart = process.hrtime.bigint();
  for (const [srcKey, dstKey] of keys) {
    arbiter.addRelation(srcKey, 'link', dstKey, { possibility: 0.7 });
  }
  const updateMs = Number(process.hrtime.bigint() - updateStart) / 1e6;
  const updateQps = updateMs > 0 ? Math.round((samples / updateMs) * 1000) : 0;

  const deleteStart = process.hrtime.bigint();
  for (const [srcKey, dstKey] of keys) {
    arbiter.removeRelation(srcKey, 'link', dstKey);
  }
  const deleteMs = Number(process.hrtime.bigint() - deleteStart) / 1e6;
  const deleteQps = deleteMs > 0 ? Math.round((samples / deleteMs) * 1000) : 0;

  return { insertMs, insertQps, updateMs, updateQps, deleteMs, deleteQps };
}

function captureMemory() {
  if (global.gc) {
    global.gc();
  }
  const memory = process.memoryUsage();
  return {
    rss: memory.rss,
    heapUsed: memory.heapUsed,
    heapTotal: memory.heapTotal
  };
}

const args = parseArgs(process.argv);
const config = {
  sizes: parseSizes(args.get('sizes')) || [5000, 10000, 25000, 50000, 100000, 200000],
  edgesPerNode: Number(args.get('edges-per-node') || 4),
  seed: Number(args.get('seed') || 42),
  maxSeconds: Number(args.get('max-seconds') || 60),
  reachability: args.get('reachability') === 'true',
  authSamples: Number(args.get('auth-samples') || 20000),
  mutationSamples: Number(args.get('mutation-samples') || 5000),
  authMode: args.get('auth-mode') || 'multi-hop',
  maxDepth: Number(args.get('max-depth') || 4)
};

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

console.log('arbiter_scaling');
console.log(`edges_per_node=${config.edgesPerNode} reachability=${config.reachability}`);
console.log(`auth_mode=${config.authMode} max_depth=${config.maxDepth}`);
console.log('nodes,edges,build_ms,reachability_ms,auth_ms,auth_qps,insert_ms,insert_qps,update_ms,update_qps,delete_ms,delete_qps,rss,heap_used,heap_total');

for (const nodes of config.sizes) {
  if ((Date.now() - start) / 1000 > config.maxSeconds) break;
  const beforeMemory = captureMemory();
  const buildStart = Date.now();
  const { arbiter, totalEdges } = buildGraph({
    nodes,
    edgesPerNode: config.edgesPerNode,
    rng,
    enableReachability: false,
    authMode: config.authMode,
    maxDepth: config.maxDepth
  });
  const buildMs = Date.now() - buildStart;

  let reachabilityMs = 0;
  if (config.reachability) {
    const reachStart = Date.now();
    arbiter.graphManager.initializeReachabilityChecker({ enableBackwardIndex: true });
    reachabilityMs = Date.now() - reachStart;
  }

  const auth = runAuthChecks({ arbiter, nodes, rng, samples: config.authSamples, authMode: config.authMode });
  const mutations = runMutationBench({ arbiter, nodes, rng, samples: config.mutationSamples });

  const afterMemory = captureMemory();
  console.log([
    nodes,
    totalEdges,
    buildMs,
    reachabilityMs,
    Math.round(auth.elapsedMs),
    auth.qps,
    Math.round(mutations.insertMs),
    mutations.insertQps,
    Math.round(mutations.updateMs),
    mutations.updateQps,
    Math.round(mutations.deleteMs),
    mutations.deleteQps,
    formatBytes(afterMemory.rss - beforeMemory.rss),
    formatBytes(afterMemory.heapUsed - beforeMemory.heapUsed),
    formatBytes(afterMemory.heapTotal - beforeMemory.heapTotal)
  ].join(','));
}
