import { Arbiter } from '../src/core/Arbiter.js';
import fs from 'node:fs';
import { performance } from 'node:perf_hooks';

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;
}

const args = parseArgs(process.argv);
const edges = Number(args.get('edges') || 200000);
const users = Number(args.get('users') || 10000);
const docs = Number(args.get('docs') || 50000);
const groups = Number(args.get('groups') || 500);
const samples = Number(args.get('samples') || 5000);
const snapshotPath = args.get('snapshot-path') || null;
const debug = args.has('debug');
const fastConstruction = args.has('no-fast') ? false : true;
const ensureHits = args.has('ensure-hits');
const fixedPair = ensureHits;
const buildIndices = args.has('no-indices') ? false : true;
const hitPairs = [];

console.log('Arbiter snapshot boot bench');
console.log(`  edges: ${edges}`);
console.log(`  users: ${users}`);
console.log(`  docs: ${docs}`);
console.log(`  groups: ${groups}`);
console.log(`  samples: ${samples}`);
if (snapshotPath) {
  console.log(`  snapshot path: ${snapshotPath}`);
}
console.log(`  fast construction: ${fastConstruction}`);
console.log(`  ensure hits: ${ensureHits}`);
console.log(`  fixed pair: ${fixedPair}`);
console.log(`  build indices: ${buildIndices}`);

let snapshotBuffer = null;

if (snapshotPath && fs.existsSync(snapshotPath)) {
  snapshotBuffer = fs.readFileSync(snapshotPath).buffer;
}

let buildTime = 0;
let snapshotTime = 0;

if (!snapshotBuffer) {
  const arbiter = new Arbiter({
    fastConstructionMode: fastConstruction,
    disableCaching: fastConstruction
  });

  if (debug) console.log('  building nodes...');
  const nodeStart = performance.now();
  for (let i = 0; i < users; i++) {
    arbiter.addNode(`user:${i}`, 'user');
  }
  for (let i = 0; i < docs; i++) {
    arbiter.addNode(`doc:${i}`, 'doc');
  }
  for (let i = 0; i < groups; i++) {
    arbiter.addNode(`group:${i}`, 'group');
  }
  const nodeTime = performance.now() - nodeStart;
  if (debug) console.log(`  nodes ready (${nodeTime.toFixed(2)} ms)`);

  if (debug) console.log('  building relations...');
  const buildStart = performance.now();
  for (let i = 0; i < edges; i++) {
    const user = `user:${i % users}`;
    const doc = `doc:${i % docs}`;
    const group = `group:${i % groups}`;
    const rel = ['owner', 'editor', 'viewer'][i % 3];

    if (i % 5 === 0) {
      arbiter.addRelation(user, 'member', group);
    }
    if (i % 7 === 0) {
      arbiter.addRelation(group, 'viewer', doc);
    }
    arbiter.addRelation(user, rel, doc);
  }
  buildTime = performance.now() - buildStart;
  if (debug) console.log(`  relations ready (${buildTime.toFixed(2)} ms)`);

  if (debug) console.log('  compiling rules...');
  arbiter.setRelationConfig('member', { type: 'direct' });
  arbiter.setRelationConfig('viewer', { type: 'direct' });
  arbiter.setRelationConfig('risk', { type: 'direct' });
  arbiter.setRelationConfig('threshold', { type: 'direct' });

  arbiter.setRelationConfig('can_view', {
    union: {
      rules: [
        { type: 'direct', relation: 'owner' },
        { type: 'direct', relation: 'editor' },
        { type: 'tuple_to_userset', tuplesetRelation: 'viewer', tuplesetDirection: 'in', computedRelation: 'member' }
      ]
    }
  });

  arbiter.setRelationConfig('complex_view', {
    intersection: {
      rules: [
        { type: 'tuple_to_userset', tuplesetRelation: 'viewer', tuplesetDirection: 'in', computedRelation: 'member' },
        {
          type: 'chain',
          steps: [
            { relation: 'member', direction: 'out' },
            { relation: 'viewer', direction: 'out' }
          ]
        },
        {
          type: 'relational_comparator',
          comparator: '>=',
          left: {
            rule: { type: 'direct', relation: 'risk' },
            valueRelation: 'risk',
            aggregator: 'max',
            evaluateFrom: 'auto'
          },
          right: {
            rule: { type: 'direct', relation: 'threshold' },
            valueRelation: 'threshold',
            aggregator: 'max',
            evaluateFrom: 'auto'
          }
        }
      ]
    }
  });

  if (!ensureHits) {
    for (let i = 0; i < Math.min(20000, edges); i++) {
      arbiter.addRelation(`user:${i % users}`, 'risk', `doc:${i % docs}`, { value: (i % 100) / 100 });
      arbiter.addRelation(`user:${i % users}`, 'threshold', `doc:${i % docs}`, { value: 0.5 });
    }
  }

  if (ensureHits) {
    const hitCount = Math.min(200, users, docs, groups);
    for (let i = 0; i < hitCount; i++) {
      const user = `user:${i}`;
      const doc = `doc:${i}`;
      const group = `group:${i}`;
      arbiter.addRelation(user, 'member', group);
      arbiter.addRelation(group, 'viewer', doc);
      arbiter.addRelation(user, 'risk', doc, { value: 0.9 });
      arbiter.addRelation(user, 'threshold', doc, { value: 0.1 });
      hitPairs.push({ user, doc });
    }
  }

  if (debug) console.log('  snapshotting...');
  const snapshotStart = performance.now();
  snapshotBuffer = arbiter.toSnapshotBinary({ dropAdjacencyList: true });
  snapshotTime = performance.now() - snapshotStart;
  if (debug) console.log(`  snapshot ready (${snapshotTime.toFixed(2)} ms)`);

  console.log(`  node build time: ${nodeTime.toFixed(2)} ms`);
}

const snapshotSize = snapshotBuffer.byteLength;

if (snapshotPath && !fs.existsSync(snapshotPath)) {
  fs.writeFileSync(snapshotPath, new Uint8Array(snapshotBuffer));
}

const memBefore = process.memoryUsage();
const loadStart = performance.now();
const booted = Arbiter.fromSnapshotBinary(snapshotBuffer, { buildSnapshotIndices: buildIndices });
const loadTime = performance.now() - loadStart;
const memAfter = process.memoryUsage();

const usersArr = Array.from({ length: users }, (_, i) => `user:${i}`);
const docsArr = Array.from({ length: docs }, (_, i) => `doc:${i}`);
let effectiveHitPairs = hitPairs;
if (ensureHits && effectiveHitPairs.length === 0) {
  effectiveHitPairs = [{ user: 'user:0', doc: 'doc:0' }];
}

const hitUsers = ensureHits
  ? (fixedPair ? [effectiveHitPairs[0].user] : effectiveHitPairs.map(pair => pair.user))
  : usersArr;
const hitDocs = ensureHits
  ? (fixedPair ? [effectiveHitPairs[0].doc] : effectiveHitPairs.map(pair => pair.doc))
  : docsArr;
const hitUserIds = hitUsers.map(key => booted.resolveNodeId(key));
const hitDocIds = hitDocs.map(key => booted.resolveNodeId(key));
const complexConfig = booted.relationConfigs.get('complex_view');

const simpleStart = performance.now();
let simpleHits = 0;
for (let i = 0; i < samples; i++) {
  const user = hitUsers[i % hitUsers.length];
  const doc = hitDocs[i % hitDocs.length];
  const result = booted.check(user, 'can_view', doc, { fastPath: true });
  if (result?.possibility > 0) simpleHits++;
}
const simpleTime = performance.now() - simpleStart;

const complexStart = performance.now();
let complexHits = 0;
for (let i = 0; i < samples; i++) {
  const idx = i % hitUsers.length;
  const user = hitUsers[idx];
  const doc = hitDocs[idx];
  const userId = hitUserIds[idx];
  const docId = hitDocIds[idx];
  const result = booted.authChecker.ruleEvaluator.evaluateRule(
    userId,
    user,
    docId,
    doc,
    complexConfig,
    new Set(),
    'complex_view',
    { fastPath: false, collectValues: true, cacheRuleResult: false }
  );
  if (result?.possibility > 0) complexHits++;
}
const complexTime = performance.now() - complexStart;

console.log('\nResults');
console.log(`  build time: ${buildTime.toFixed(2)} ms`);
console.log(`  snapshot time: ${snapshotTime.toFixed(2)} ms`);
console.log(`  snapshot size: ${(snapshotSize / 1024 / 1024).toFixed(2)} MB`);
console.log(`  load time: ${loadTime.toFixed(2)} ms`);
console.log(`  memory rss before: ${(memBefore.rss / 1024 / 1024).toFixed(2)} MB`);
console.log(`  memory rss after: ${(memAfter.rss / 1024 / 1024).toFixed(2)} MB`);
console.log(`  memory heap before: ${(memBefore.heapUsed / 1024 / 1024).toFixed(2)} MB`);
console.log(`  memory heap after: ${(memAfter.heapUsed / 1024 / 1024).toFixed(2)} MB`);
console.log(`  simple avg: ${(simpleTime / samples * 1000).toFixed(3)} µs`);
console.log(`  complex avg: ${(complexTime / samples * 1000).toFixed(3)} µs`);
console.log(`  simple hits: ${simpleHits}`);
console.log(`  complex hits: ${complexHits}`);
if (ensureHits && debug) {
  const sampleUser = hitUsers[0];
  const sampleDoc = hitDocs[0];
  const sampleSimple = booted.check(sampleUser, 'can_view', sampleDoc, { fastPath: true });
  const sampleComplex = booted.check(sampleUser, 'complex_view', sampleDoc, { fastPath: false, collectValues: true, includeMeta: true });
  console.log(`  sample can_view possibility: ${sampleSimple?.possibility ?? 'null'}`);
  console.log(`  sample complex_view possibility: ${sampleComplex?.possibility ?? 'null'}`);
  if (sampleComplex?.meta) {
    console.log(`  sample complex meta: ${JSON.stringify(sampleComplex.meta)}`);
  }

  const complexConfig = booted.relationConfigs.get('complex_view');

  const userId = booted.resolveNodeId(sampleUser);
  const objectId = booted.resolveNodeId(sampleDoc);
  if (userId !== undefined && objectId !== undefined) {
    const evaluator = booted.authChecker.ruleEvaluator;
    const visited = new Set();
    const tupleRule = { type: 'tuple_to_userset', tuplesetRelation: 'viewer', tuplesetDirection: 'in', computedRelation: 'member' };
    const chainRule = {
      type: 'chain',
      steps: [
        { relation: 'member', direction: 'out' },
        { relation: 'viewer', direction: 'out' }
      ]
    };
    const compRule = {
      type: 'relational_comparator',
      comparator: '>=',
      left: {
        rule: { type: 'direct', relation: 'risk' },
        valueRelation: 'risk',
        aggregator: 'max',
        evaluateFrom: 'auto'
      },
      right: {
        rule: { type: 'direct', relation: 'threshold' },
        valueRelation: 'threshold',
        aggregator: 'max',
        evaluateFrom: 'auto'
      }
    };
    const tupleResult = evaluator.evaluateRule(userId, sampleUser, objectId, sampleDoc, tupleRule, visited, 'tuple_debug', { fastPath: false, collectValues: true });
    const chainResult = evaluator.evaluateRule(userId, sampleUser, objectId, sampleDoc, chainRule, visited, 'chain_debug', { fastPath: false, collectValues: true });
    const compResult = evaluator.evaluateRule(userId, sampleUser, objectId, sampleDoc, compRule, visited, 'comp_debug', { fastPath: false, collectValues: true, includeMeta: true });
    console.log(`  tuple_to_userset possibility: ${tupleResult?.possibility ?? 'null'}`);
    console.log(`  chain possibility: ${chainResult?.possibility ?? 'null'}`);
    console.log(`  comparator possibility: ${compResult?.possibility ?? 'null'}`);
    if (compResult?.meta) {
      console.log(`  comparator meta: ${JSON.stringify(compResult.meta)}`);
    }
    const riskRel = booted.relationManager.getDirectRelation(userId, 'risk', objectId);
    const thresholdRel = booted.relationManager.getDirectRelation(userId, 'threshold', objectId);
    console.log(`  risk relation value: ${riskRel?.value ?? 'null'}`);
    console.log(`  threshold relation value: ${thresholdRel?.value ?? 'null'}`);

  if (complexConfig) {
    const compiled = complexConfig._compiled;
      if (compiled && compiled.type === 'logical') {
        const childTypes = compiled.children.map(child => child?.type || 'null');
        console.log(`  complex compiled children: ${JSON.stringify(childTypes)}`);

        const compiledEvaluator = evaluator.compiledEvaluator;
        const childPoss = compiled.children.map(child => {
          const res = compiledEvaluator.evaluate(child, userId, sampleUser, objectId, sampleDoc, new Set(), 'complex_child', { fastPath: false, collectValues: true, includeMeta: true });
          return res?.possibility ?? null;
        });
        console.log(`  complex compiled child possibilities: ${JSON.stringify(childPoss)}`);
      }
      const directComplex = evaluator.evaluateRule(userId, sampleUser, objectId, sampleDoc, complexConfig, new Set(), 'complex_debug', { fastPath: false, collectValues: true, includeMeta: true });
      console.log(`  direct complex possibility: ${directComplex?.possibility ?? 'null'}`);
      if (directComplex?.meta) {
        console.log(`  direct complex meta: ${JSON.stringify(directComplex.meta)}`);
      }
    }
  }
}
