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

function parseArgs(argv) {
  const args = new Map();
  for (let i = 2; i < argv.length; i++) {
    const value = argv[i];
    if (!value.startsWith('--')) continue;
    if (value.startsWith('--no-')) {
      const key = value.slice(5);
      args.set(key, false);
      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 percentile(sorted, p) {
  if (sorted.length === 0) return 0;
  const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil((p / 100) * sorted.length) - 1));
  return sorted[index];
}

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

function randRange(rng, min, max) {
  return min + Math.floor(rng() * (max - min));
}

function buildUsers(arbiter, count, prefix) {
  const ids = [];
  for (let i = 0; i < count; i++) {
    ids.push(arbiter.addNode(`${prefix}:${i}`, prefix));
  }
  return ids;
}

function setupOwaUnionScenario(arbiter, size, rng, relationName, unionConfig) {
  buildUsers(arbiter, size, 'user');
  buildUsers(arbiter, size, 'resource');
  const trueIds = [];
  const falseIds = [];
  for (let i = 0; i < size; i++) {
    if (i % 3 === 0) {
      arbiter.addRelation(`user:${i}`, 'viewer', `resource:${i}`, 0.6);
    }
    if (i % 5 === 0) {
      arbiter.addRelation(`user:${i}`, 'owner', `resource:${i}`, 0.9);
    }
    if (i % 3 === 0 || i % 5 === 0) {
      trueIds.push(i);
    } else {
      falseIds.push(i);
    }
  }
  arbiter.setRelationConfig('viewer', { type: 'direct' });
  arbiter.setRelationConfig('owner', { type: 'direct' });
  arbiter.setRelationConfig(relationName, {
    union: {
      rules: [
        { type: 'direct', relation: 'viewer' },
        { type: 'direct', relation: 'owner' }
      ],
      ...unionConfig
    }
  });
  const queries = createQueries(
    Math.min(2000, size),
    () => {
      const userId = trueIds.length > 0 ? trueIds[randInt(rng, trueIds.length)] : 0;
      return { user: `user:${userId}`, object: `resource:${userId}` };
    },
    () => {
      const userId = falseIds.length > 0 ? falseIds[randInt(rng, falseIds.length)] : 0;
      return { user: `user:${userId}`, object: `resource:${userId}` };
    }
  );
  return { queries: shuffleWithRng(queries, rng) };
}

function createQueries(count, pickTrue, pickFalse) {
  const queries = [];
  const half = Math.floor(count / 2);
  for (let i = 0; i < half; i++) {
    queries.push({ ...pickTrue(), expected: true });
  }
  for (let i = 0; i < count - half; i++) {
    queries.push({ ...pickFalse(), expected: false });
  }
  return queries;
}

function createChallengeQueries(count, pickTrue, pickFalse) {
  const queries = [];
  const half = Math.floor(count / 2);
  for (let i = 0; i < half; i++) {
    const entry = pickTrue();
    queries.push({ ...entry, expected: true });
  }
  for (let i = 0; i < count - half; i++) {
    const entry = pickFalse();
    queries.push({ ...entry, expected: false });
  }
  return queries;
}

function shuffle(items) {
  for (let i = items.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [items[i], items[j]] = [items[j], items[i]];
  }
  return items;
}

function shuffleWithRng(items, rng) {
  for (let i = items.length - 1; i > 0; i--) {
    const j = Math.floor(rng() * (i + 1));
    [items[i], items[j]] = [items[j], items[i]];
  }
  return items;
}

const scenarios = [
  {
    name: 'owner_direct',
    relation: 'owner',
    setup: (arbiter, size, rng) => {
      buildUsers(arbiter, size, 'user');
      buildUsers(arbiter, size, 'resource');
      for (let i = 0; i < size; i++) {
        arbiter.addRelation(`user:${i}`, 'owner', `resource:${i}`);
      }
      arbiter.setRelationConfig('owner', { type: 'direct' });
      const queries = createQueries(
        Math.min(2000, size * 2),
        () => {
          const id = randInt(rng, size);
          return { user: `user:${id}`, object: `resource:${id}` };
        },
        () => {
          const id = randInt(rng, size);
          return { user: `user:${id}`, object: `resource:${(id + 1) % size}` };
        }
      );
      return { queries: shuffleWithRng(queries, rng) };
    }
  },
  {
    name: 'viewer_direct',
    relation: 'viewer',
    setup: (arbiter, size, rng) => {
      buildUsers(arbiter, size, 'user');
      buildUsers(arbiter, size, 'resource');
      for (let i = 0; i < size; i++) {
        arbiter.addRelation(`user:${i}`, 'viewer', `resource:${i}`);
      }
      arbiter.setRelationConfig('viewer', { type: 'direct' });
      const queries = createQueries(
        Math.min(2000, size * 2),
        () => {
          const id = randInt(rng, size);
          return { user: `user:${id}`, object: `resource:${id}` };
        },
        () => {
          const id = randInt(rng, size);
          return { user: `user:${id}`, object: `resource:${(id + 1) % size}` };
        }
      );
      return { queries: shuffleWithRng(queries, rng) };
    }
  },
  {
    name: 'group_tuple_to_userset',
    relation: 'can_view',
    setup: (arbiter, size, rng) => {
      const groupCount = Math.max(1, Math.floor(size / 10));
      buildUsers(arbiter, size, 'user');
      buildUsers(arbiter, groupCount, 'group');
      buildUsers(arbiter, groupCount, 'resource');
      for (let i = 0; i < size; i++) {
        const groupId = i % groupCount;
        arbiter.addRelation(`user:${i}`, 'member', `group:${groupId}`);
      }
      for (let i = 0; i < groupCount; i++) {
        arbiter.addRelation(`group:${i}`, 'group_access', `resource:${i}`);
      }
      arbiter.setRelationConfig('member', { type: 'direct' });
      arbiter.setRelationConfig('group_access', { type: 'direct' });
      arbiter.setRelationConfig('can_view', {
        type: 'tuple_to_userset',
        tuplesetRelation: 'group_access',
        tuplesetDirection: 'in',
        computedRelation: 'member'
      });
      const queries = createQueries(
        Math.min(2000, size),
        () => {
          const userId = randInt(rng, size);
          const groupId = userId % groupCount;
          return { user: `user:${userId}`, object: `resource:${groupId}` };
        },
        () => {
          const userId = randInt(rng, size);
          const groupId = (userId + 1) % groupCount;
          return { user: `user:${userId}`, object: `resource:${groupId}` };
        }
      );
      return { queries: shuffleWithRng(queries, rng) };
    }
  },
  {
    name: 'plan_chain',
    relation: 'can_use',
    setup: (arbiter, size, rng) => {
      const planCount = Math.max(1, Math.floor(size / 10));
      buildUsers(arbiter, size, 'user');
      buildUsers(arbiter, planCount, 'plan');
      buildUsers(arbiter, planCount, 'resource');
      for (let i = 0; i < size; i++) {
        const planId = i % planCount;
        arbiter.addRelation(`user:${i}`, 'has_plan', `plan:${planId}`);
      }
      for (let i = 0; i < planCount; i++) {
        arbiter.addRelation(`plan:${i}`, 'plan_grants', `resource:${i}`);
      }
      arbiter.setRelationConfig('has_plan', { type: 'direct' });
      arbiter.setRelationConfig('plan_grants', { type: 'direct' });
      arbiter.setRelationConfig('can_use', {
        type: 'chain',
        steps: [
          { relation: 'has_plan', direction: 'out' },
          { relation: 'plan_grants', direction: 'out' }
        ],
        collectValues: false
      });
      const queries = createQueries(
        Math.min(2000, size),
        () => {
          const userId = randInt(rng, size);
          const planId = userId % planCount;
          return { user: `user:${userId}`, object: `resource:${planId}` };
        },
        () => {
          const userId = randInt(rng, size);
          const planId = (userId + 1) % planCount;
          return { user: `user:${userId}`, object: `resource:${planId}` };
        }
      );
      return { queries: shuffleWithRng(queries, rng) };
    }
  },
  {
    name: 'account_admin_chain',
    relation: 'can_admin',
    setup: (arbiter, size, rng) => {
      const accountCount = Math.max(1, Math.floor(size / 10));
      buildUsers(arbiter, size, 'user');
      buildUsers(arbiter, accountCount, 'account');
      buildUsers(arbiter, accountCount, 'resource');
      for (let i = 0; i < size; i++) {
        const accountId = i % accountCount;
        arbiter.addRelation(`user:${i}`, 'admin_of', `account:${accountId}`);
      }
      for (let i = 0; i < accountCount; i++) {
        arbiter.addRelation(`account:${i}`, 'account_owns', `resource:${i}`);
      }
      arbiter.setRelationConfig('admin_of', { type: 'direct' });
      arbiter.setRelationConfig('account_owns', { type: 'direct' });
      arbiter.setRelationConfig('can_admin', {
        type: 'chain',
        steps: [
          { relation: 'admin_of', direction: 'out' },
          { relation: 'account_owns', direction: 'out' }
        ],
        collectValues: false
      });
      const queries = createQueries(
        Math.min(2000, size),
        () => {
          const userId = randInt(rng, size);
          const accountId = userId % accountCount;
          return { user: `user:${userId}`, object: `resource:${accountId}` };
        },
        () => {
          const userId = randInt(rng, size);
          const accountId = (userId + 1) % accountCount;
          return { user: `user:${userId}`, object: `resource:${accountId}` };
        }
      );
      return { queries: shuffleWithRng(queries, rng) };
    }
  },
  {
    name: 'blocked_unless',
    relation: 'can_view_blocked',
    setup: (arbiter, size, rng) => {
      buildUsers(arbiter, size, 'user');
      buildUsers(arbiter, size, 'resource');
      for (let i = 0; i < size; i++) {
        arbiter.addRelation(`user:${i}`, 'viewer', `resource:${i}`);
      }
      for (let i = 0; i < Math.floor(size / 4); i++) {
        arbiter.addRelation(`user:${i}`, 'blocked', `resource:${i}`);
      }
      arbiter.setRelationConfig('viewer', { type: 'direct' });
      arbiter.setRelationConfig('blocked', { type: 'direct' });
      arbiter.setRelationConfig('can_view_blocked', {
        exclusion: [
          { type: 'direct', relation: 'viewer' },
          { type: 'direct', relation: 'blocked' }
        ]
      });
      const queries = createQueries(
        Math.min(2000, size),
        () => {
          const blockedRange = Math.floor(size / 4);
          const userId = randRange(rng, blockedRange, size);
          return { user: `user:${userId}`, object: `resource:${userId}` };
        },
        () => {
          const userId = randInt(rng, Math.floor(size / 4));
          return { user: `user:${userId}`, object: `resource:${userId}` };
        }
      );
      return { queries: shuffleWithRng(queries, rng) };
    }
  },
  {
    name: 'age_abac',
    relation: 'age_ok',
    setup: (arbiter, size, rng) => {
      buildUsers(arbiter, size, 'user');
      buildUsers(arbiter, size, 'resource');
      for (let i = 0; i < size; i++) {
        arbiter.addRelation(`user:${i}`, 'age', `user:${i}`, 1.0, { value: 18 + (i % 30) });
        arbiter.addRelation(`resource:${i}`, 'min_age', `resource:${i}`, 1.0, { value: 18 + (i % 15) });
      }
      arbiter.setRelationConfig('age', { type: 'direct' });
      arbiter.setRelationConfig('min_age', { type: 'direct' });
      arbiter.setRelationConfig('age_ok', {
        type: 'relational_comparator',
        comparator: '>=',
        fallbackBehavior: 'deny',
        left: {
          rule: { type: 'direct', relation: 'age' },
          extractValue: true,
          valueRelation: 'age'
        },
        right: {
          rule: { type: 'direct', relation: 'min_age' },
          extractValue: true,
          valueRelation: 'min_age',
          evaluateFrom: 'object'
        }
      });
      const queries = createQueries(
        Math.min(2000, size),
        () => {
          const userId = randInt(rng, size);
          const resourceId = userId % size;
          return { user: `user:${userId}`, object: `resource:${resourceId}` };
        },
        () => {
          const userId = randInt(rng, size);
          const resourceId = (userId + Math.floor(size / 2)) % size;
          return { user: `user:${userId}`, object: `resource:${resourceId}` };
        }
      );
      const withExpected = queries.map(q => {
        const userId = Number(q.user.split(':')[1]);
        const resourceId = Number(q.object.split(':')[1]);
        const age = 18 + (userId % 30);
        const minAge = 18 + (resourceId % 15);
        return { ...q, expected: age >= minAge };
      });
      return { queries: shuffleWithRng(withExpected, rng) };
    }
  },
  {
    name: 'risk_limit',
    relation: 'risk_ok',
    setup: (arbiter, size, rng) => {
      buildUsers(arbiter, size, 'user');
      buildUsers(arbiter, size, 'resource');
      for (let i = 0; i < size; i++) {
        const score = i % 100;
        arbiter.addRelation(`user:${i}`, 'risk_score', `resource:${i}`, 1.0, { value: score });
        arbiter.addRelation(`resource:${i}`, 'risk_limit', `resource:${i}`, 1.0, { value: 50 });
      }
      arbiter.setRelationConfig('risk_score', { type: 'direct' });
      arbiter.setRelationConfig('risk_limit', { type: 'direct' });
      arbiter.setRelationConfig('risk_ok', {
        type: 'relational_comparator',
        comparator: '<=',
        fallbackBehavior: 'deny',
        left: {
          rule: { type: 'direct', relation: 'risk_score' },
          extractValue: true,
          valueRelation: 'risk_score'
        },
        right: {
          rule: { type: 'direct', relation: 'risk_limit' },
          extractValue: true,
          valueRelation: 'risk_limit',
          evaluateFrom: 'object'
        }
      });
      const queries = createQueries(
        Math.min(2000, size),
        () => {
          const userId = randInt(rng, Math.min(size, 51));
          return { user: `user:${userId}`, object: `resource:${userId}` };
        },
        () => {
          const base = Math.min(size - 51, 49);
          const userId = base + randInt(rng, Math.max(1, size - base));
          return { user: `user:${userId}`, object: `resource:${userId}` };
        }
      );
      const withExpected = queries.map(q => {
        const userId = Number(q.user.split(':')[1]);
        const score = userId % 100;
        return { ...q, expected: score <= 50 };
      });
      return { queries: shuffleWithRng(withExpected, rng) };
    }
  },
  {
    name: 'feature_flag',
    relation: 'can_feature',
    setup: (arbiter, size, rng) => {
      const flagCount = Math.max(1, Math.floor(size / 10));
      buildUsers(arbiter, size, 'user');
      buildUsers(arbiter, size, 'resource');
      buildUsers(arbiter, flagCount, 'flag');
      for (let i = 0; i < size; i++) {
        const flagId = i % flagCount;
        arbiter.addRelation(`user:${i}`, 'has_flag', `flag:${flagId}`);
        if (i % 2 === 0) {
          arbiter.addRelation(`resource:${i}`, 'feature_flag', `flag:${flagId}`);
        }
      }
      arbiter.setRelationConfig('has_flag', { type: 'direct' });
      arbiter.setRelationConfig('feature_flag', { type: 'direct' });
      arbiter.setRelationConfig('can_feature', {
        type: 'tuple_to_userset',
        tuplesetRelation: 'feature_flag',
        tuplesetDirection: 'out',
        computedRelation: 'has_flag'
      });
      const queries = createQueries(
        Math.min(2000, size),
        () => {
          const userId = randInt(rng, Math.floor(size / 2)) * 2;
          return { user: `user:${userId}`, object: `resource:${userId}` };
        },
        () => {
          const userId = randInt(rng, Math.floor(size / 2)) * 2 + 1;
          return { user: `user:${userId}`, object: `resource:${userId}` };
        }
      );
      return { queries: shuffleWithRng(queries, rng) };
    }
  }
  ,{
    name: 'owa_union',
    relation: 'can_view_owa',
    setup: (arbiter, size, rng) => {
      return setupOwaUnionScenario(arbiter, size, rng, 'can_view_owa', {
        aggregator: 'owa',
        owaWeights: [0.7, 0.3]
      });
    }
  }
  ,{
    name: 'owa_union_max',
    relation: 'can_view_owa_max',
    setup: (arbiter, size, rng) => {
      return setupOwaUnionScenario(arbiter, size, rng, 'can_view_owa_max', {
        aggregator: 'max'
      });
    }
  }
  ,{
    name: 'owa_union_min',
    relation: 'can_view_owa_min',
    setup: (arbiter, size, rng) => {
      return setupOwaUnionScenario(arbiter, size, rng, 'can_view_owa_min', {
        aggregator: 'min'
      });
    }
  }
  ,{
    name: 'owa_union_top2',
    relation: 'can_view_owa_top2',
    setup: (arbiter, size, rng) => {
      return setupOwaUnionScenario(arbiter, size, rng, 'can_view_owa_top2', {
        aggregator: 'top2'
      });
    }
  }
  ,{
    name: 'owa_union_median',
    relation: 'can_view_owa_median',
    setup: (arbiter, size, rng) => {
      return setupOwaUnionScenario(arbiter, size, rng, 'can_view_owa_median', {
        aggregator: 'median'
      });
    }
  }
  ,{
    name: 'owa_comparator_nested',
    relation: 'risk_ok_owa',
    setup: (arbiter, size, rng) => {
      buildUsers(arbiter, size, 'user');
      buildUsers(arbiter, size, 'resource');
      const trueIds = [];
      const falseIds = [];
      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]
        }
      });
      const computeExpected = (userId, resourceId) => {
        const valuesLeft = [
          userId % 2 === 0 ? 20 : 80,
          userId % 2 === 0 ? 5 : 15,
          userId % 2 === 0 ? 0 : 10
        ];
        const leftIntervals = valuesLeft.map(value => ({ min: value, max: value }));
        const leftMeta = valuesLeft.map(() => ({ ruleType: 'defeasible', rule: {} }));
        const leftFused = OWAFusion.fuseIntervalsWithMeta(leftIntervals, leftMeta, [0.5, 0.3, 0.2], 'owa');
        const rightValues = [40, 45];
        const rightIntervals = rightValues.map(value => ({ min: value, max: value }));
        const rightMeta = rightValues.map(() => ({ ruleType: 'defeasible', rule: {} }));
        const rightFused = OWAFusion.fuseIntervalsWithMeta(rightIntervals, rightMeta, [0.6, 0.4], 'owa');
        return leftFused.interval.min <= rightFused.interval.min;
      };
      const pickWithExpectation = (expected) => {
        const pool = expected ? trueIds : falseIds;
        if (pool.length === 0) {
          const userId = randInt(rng, size);
          return { user: `user:${userId}`, object: `resource:${userId}` };
        }
        const userId = pool[randInt(rng, pool.length)];
        return { user: `user:${userId}`, object: `resource:${userId}` };
      };
      const queries = createQueries(
        Math.min(2000, size),
        () => pickWithExpectation(true),
        () => pickWithExpectation(false)
      );
      return { queries: shuffleWithRng(queries, rng) };
    }
  },
  {
    name: 'challenge_mfa_within',
    relation: 'secure_action',
    setup: (arbiter, size, rng) => {
      buildUsers(arbiter, size, 'user');
      buildUsers(arbiter, size, 'resource');
      arbiter.setRelationConfig('secure_action', {
        type: 'challenge',
        challenge: 'mfa',
        subject: 'user',
        withinMs: 5 * 60 * 1000
      });

      const now = Date.now();
      const queries = createChallengeQueries(
        Math.min(2000, size * 2),
        () => {
          const id = randInt(rng, size);
          return {
            user: `user:${id}`,
            object: `resource:${id}`,
            options: {
              partialGraph: {
                challenges: [
                  { name: 'mfa', subject: `user:${id}`, issuedAt: now }
                ]
              }
            }
          };
        },
        () => {
          const id = randInt(rng, size);
          return {
            user: `user:${id}`,
            object: `resource:${id}`,
            options: {
              partialGraph: {
                challenges: [
                  { name: 'mfa', subject: `user:${id}`, issuedAt: now - 10 * 60 * 1000 }
                ]
              }
            }
          };
        }
      );

      return { queries: shuffleWithRng(queries, rng) };
    }
  },
  {
    name: 'challenge_composed_union',
    relation: 'can_delete',
    setup: (arbiter, size, rng) => {
      buildUsers(arbiter, size, 'user');
      buildUsers(arbiter, size, 'resource');
      arbiter.setRelationConfig('secure_connection', {
        union: {
          rules: [
            { type: 'challenge', challenge: 'mfa', subject: 'user', withinMs: 5 * 60 * 1000 },
            { type: 'challenge', challenge: 'webauthn', subject: 'user', withinMs: 5 * 60 * 1000 }
          ]
        }
      });
      arbiter.setRelationConfig('can_delete', { type: 'computed', relation: 'secure_connection' });

      const now = Date.now();
      const queries = createChallengeQueries(
        Math.min(2000, size * 2),
        () => {
          const id = randInt(rng, size);
          return {
            user: `user:${id}`,
            object: `resource:${id}`,
            options: {
              partialGraph: {
                challenges: [
                  { name: 'mfa', subject: `user:${id}`, issuedAt: now }
                ]
              }
            }
          };
        },
        () => {
          const id = randInt(rng, size);
          return {
            user: `user:${id}`,
            object: `resource:${id}`,
            options: {
              partialGraph: {
                challenges: []
              }
            }
          };
        }
      );

      return { queries: shuffleWithRng(queries, rng) };
    }
  }
];

const args = parseArgs(process.argv);
const config = {
  sizes: parseSizes(args.get('sizes')) || [1000, 5000, 10000],
  samples: Number(args.get('samples') || 2000),
  seed: Number(args.get('seed') || 42),
  maxSeconds: Number(args.get('max-seconds') || 60),
  verify: args.has('verify'),
  snapshot: args.has('snapshot')
};

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

console.log('b2c_auth_bench');
console.log('scenario,nodes,edges,build_ms,auth_ms,auth_qps,precision,recall,f1,p95_ms,p99_ms');

for (const scenario of scenarios) {
  for (const size of config.sizes) {
    if ((Date.now() - startTime) / 1000 > config.maxSeconds) break;
    const arbiter = new Arbiter();
    const buildStart = Date.now();
    const { queries } = scenario.setup(arbiter, size, rng);
    let buildMs = Date.now() - buildStart;

    if (config.snapshot) {
      const snapshotStart = Date.now();
      arbiter.enableCondensedSnapshot();
      buildMs += Date.now() - snapshotStart;
    }

    const actualQueries = queries.slice(0, config.samples);
    let tp = 0;
    let tn = 0;
    let fp = 0;
    let fn = 0;
    const latencies = [];
    const authStart = process.hrtime.bigint();
    for (const query of actualQueries) {
      const start = process.hrtime.bigint();
      const options = query.options ? { fastPath: true, ...query.options } : { fastPath: true };
      const result = arbiter.check(query.user, scenario.relation, query.object, options);
      const predicted = result && result.possibility > 0;
      if (predicted && query.expected) tp++;
      else if (!predicted && !query.expected) tn++;
      else if (predicted && !query.expected) fp++;
      else if (!predicted && query.expected) fn++;
      latencies.push(Number(process.hrtime.bigint() - start) / 1e6);
    }
    const authMs = Number(process.hrtime.bigint() - authStart) / 1e6;
    const authQps = authMs > 0 ? Math.round((actualQueries.length / authMs) * 1000) : 0;
    const precision = tp + fp > 0 ? tp / (tp + fp) : 0;
    const recall = tp + fn > 0 ? tp / (tp + fn) : 0;
    const f1 = precision + recall > 0 ? (2 * precision * recall) / (precision + recall) : 0;
    const sorted = latencies.sort((a, b) => a - b);
    const p95 = percentile(sorted, 95).toFixed(3);
    const p99 = percentile(sorted, 99).toFixed(3);

    console.log([
      scenario.name,
      size,
      arbiter.relations.length,
      buildMs,
      Math.round(authMs),
      authQps,
      precision.toFixed(3),
      recall.toFixed(3),
      f1.toFixed(3),
      p95,
      p99
    ].join(','));

    if (config.verify && (fp + fn) > 0) {
      throw new Error(`Verification failed for ${scenario.name} size ${size}: fp=${fp} fn=${fn}`);
    }
  }
}
