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 formatBytes(bytes) {
  const units = ['B', 'KB', 'MB', 'GB'];
  let idx = 0;
  let val = bytes;
  while (val >= 1024 && idx < units.length - 1) {
    val /= 1024;
    idx++;
  }
  return `${val.toFixed(2)}${units[idx]}`;
}

function gc() {
  if (global.gc) {
    global.gc();
    global.gc();
  }
}

function measureMemory(label) {
  const mem = process.memoryUsage();
  return {
    label,
    rss: mem.rss,
    heapUsed: mem.heapUsed,
    heapTotal: mem.heapTotal,
    external: mem.external
  };
}

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

function addEdges(arbiter, size, relation, perNode, srcPrefix = 'user', dstPrefix = 'resource') {
  for (let i = 0; i < size; i++) {
    for (let j = 0; j < perNode; j++) {
      const dstId = (i + j) % size;
      arbiter.addRelation(`${srcPrefix}:${i}`, relation, `${dstPrefix}:${dstId}`);
    }
  }
}

function buildGraph(arbiter, size) {
  buildNodes(arbiter, size);

  addEdges(arbiter, size, 'owner', 1);
  addEdges(arbiter, size, 'viewer', 1);
  addEdges(arbiter, size, 'group_tuple_to_userset', 1);
  addEdges(arbiter, size, 'plan_chain', 1);
  addEdges(arbiter, size, 'account_admin_chain', 1);
  addEdges(arbiter, size, 'blocked_unless', 1);
  addEdges(arbiter, size, 'age_abac', 2);
  addEdges(arbiter, size, 'risk_limit', 2);
  addEdges(arbiter, size, 'feature_flag', 1);
  addEdges(arbiter, size, 'owa_union', 1);
  addEdges(arbiter, size, 'owa_comparator_nested', 5);

  arbiter.setRelationConfig('owner', { type: 'direct' });
  arbiter.setRelationConfig('viewer', { type: 'direct' });
  arbiter.setRelationConfig('group_tuple_to_userset', { type: 'direct' });
  arbiter.setRelationConfig('plan_chain', { type: 'direct' });
  arbiter.setRelationConfig('account_admin_chain', { type: 'direct' });
  arbiter.setRelationConfig('blocked_unless', { type: 'direct' });
  arbiter.setRelationConfig('age_abac', { type: 'direct' });
  arbiter.setRelationConfig('risk_limit', { type: 'direct' });
  arbiter.setRelationConfig('feature_flag', { type: 'direct' });
  arbiter.setRelationConfig('owa_union', { type: 'direct' });
  arbiter.setRelationConfig('owa_comparator_nested', { type: 'direct' });
}

const args = parseArgs(process.argv);
const sizes = parseSizes(args.get('sizes')) || [10000, 50000, 100000];

console.log('memory_profile');
console.log('size,nodes,edges,rss,heap_used,heap_total,external');

for (const size of sizes) {
  const arbiter = new Arbiter();
  gc();
  const before = measureMemory('before');
  buildGraph(arbiter, size);
  gc();
  const after = measureMemory('after');

  const nodes = size * 2;
  const edges = size * (1 + 1 + 1 + 1 + 1 + 1 + 2 + 2 + 1 + 1 + 5);
  const delta = {
    rss: after.rss - before.rss,
    heapUsed: after.heapUsed - before.heapUsed,
    heapTotal: after.heapTotal - before.heapTotal,
    external: after.external - before.external
  };

  console.log([
    size,
    nodes,
    edges,
    formatBytes(delta.rss),
    formatBytes(delta.heapUsed),
    formatBytes(delta.heapTotal),
    formatBytes(delta.external)
  ].join(','));
}
