import { performance } from 'perf_hooks';
import { Arbiter } from '../src/index.js';

class Benchmark {
  constructor(name) {
    this.name = name;
    this.results = [];
    this.labels = [];
  }
  async run(fn, iterations = 1000, label = '') {
    for (let i = 0; i < 10; i++) await fn();
    const times = [];
    for (let i = 0; i < iterations; i++) {
      const start = performance.now();
      await fn();
      const end = performance.now();
      times.push(end - start);
    }
    const avg = times.reduce((a, b) => a + b, 0) / times.length;
    const min = Math.min(...times);
    const max = Math.max(...times);
    const p95 = times.sort((a, b) => a - b)[Math.floor(times.length * 0.95)];
    this.results.push({
      avg: avg.toFixed(3),
      min: min.toFixed(3),
      max: max.toFixed(3),
      p95: p95.toFixed(3)
    });
    this.labels.push(label);
    return { avg, min, max, p95 };
  }
  report() {
    console.log('Configuration                    | Avg (ms) | Min (ms) | Max (ms) | P95 (ms) | Speedup');
    console.log('---------------------------------|----------|----------|----------|----------|--------');
    const baselineTime = parseFloat(this.results[0].avg);
    this.results.forEach((result, i) => {
      const label = this.labels[i] || `Test ${i + 1}`;
      const speedup = i === 0 ? '1.00x' : `${(baselineTime / parseFloat(result.avg)).toFixed(2)}x`;
      console.log(`${label.padEnd(32)} | ${result.avg.padStart(8)} | ${result.min.padStart(8)} | ${result.max.padStart(8)} | ${result.p95.padStart(8)} | ${speedup.padStart(6)}`);
    });
  }
}

function setupGraph(scale = 'medium') {
  const scales = {
    medium: { users: 2000, resources: 1000 },
    xlarge: { users: 20000, resources: 5000 }
  };
  const config = scales[scale];
  const arbiter = new Arbiter({ fastConstructionMode: true });
  const users = [], resources = [];
  for (let i = 0; i < config.users; i++) {
    const userKey = `user:user${i}`;
    arbiter.addNode(userKey, 'user');
    // Assign a random balance between 100 and 10000
    arbiter.addRelation(userKey, 'has_balance', 'balance', { value: Math.floor(Math.random() * 9900) + 100 });
    users.push(userKey);
  }
  for (let i = 0; i < config.resources; i++) {
    const resKey = `resource:res${i}`;
    arbiter.addNode(resKey, 'resource');
    // Assign a random price between 50 and 5000
    arbiter.addRelation(resKey, 'has_price', 'price', { value: Math.floor(Math.random() * 4950) + 50 });
    resources.push(resKey);
  }
  // Direct access for a subset
  for (let i = 0; i < Math.floor(config.users * 0.1); i++) {
    const user = users[i];
    const res = resources[i % resources.length];
    arbiter.addRelation(user, 'can_access', res);
  }
  // ChainRule: user->can_access->resource
  arbiter.setRelationConfig('can_access', { type: 'direct' });
  arbiter.setRelationConfig('chain_access', {
    type: 'chain',
    steps: [ { relation: 'can_access', direction: 'out' } ]
  });
  // RelationalComparatorRule: user.balance >= resource.price
  arbiter.setRelationConfig('balance_check', {
    type: 'relational_comparator',
    leftOperand: {
      rule: { type: 'direct', relation: 'has_balance' },
      extractValue: true,
      valueRelation: 'has_balance',
      aggregator: 'max'
    },
    rightOperand: {
      rule: { type: 'direct', relation: 'has_price' },
      extractValue: true,
      valueRelation: 'has_price',
      aggregator: 'max'
    },
    comparator: '>=',
    fallbackBehavior: 'deny'
  });
  return { arbiter, users, resources };
}

async function runRelationalComparatorBenchmarks() {
  for (const scale of ['medium', 'xlarge']) {
    console.log(`\n🔬 RelationalComparatorRule Benchmark (${scale.toUpperCase()})`);
    const { arbiter, users, resources } = setupGraph(scale);
    const benchmark = new Benchmark(`RelationalComparatorRule (${scale})`);
    // Direct access baseline
    await benchmark.run(() => {
      const user = users[Math.floor(Math.random() * users.length)];
      const res = resources[Math.floor(Math.random() * resources.length)];
      arbiter.check(user, 'can_access', res);
    }, scale === 'medium' ? 500 : 100, 'Direct Access (baseline)');
    // ChainRule existence
    await benchmark.run(() => {
      const user = users[Math.floor(Math.random() * users.length)];
      const res = resources[Math.floor(Math.random() * resources.length)];
      arbiter.check(user, 'chain_access', res);
    }, scale === 'medium' ? 500 : 100, 'ChainRule Existence');
    // RelationalComparatorRule (value-based)
    await benchmark.run(() => {
      const user = users[Math.floor(Math.random() * users.length)];
      const res = resources[Math.floor(Math.random() * resources.length)];
      arbiter.check(user, 'balance_check', res);
    }, scale === 'medium' ? 500 : 100, 'RelationalComparatorRule (balance >= price)');
    // --- Complex, realistic scenario: sum of all balances user can debit vs. min plan/feature price ---
    // Setup: users, accounts, features, plans, prices, debit rights
    const accounts = [], plans = [], features = [];
    for (let i = 0; i < (scale === 'medium' ? 1000 : 5000); i++) {
      const accKey = `account:acc${i}`;
      accounts.push(accKey);
      arbiter.addNode(accKey, 'account');
      // Each account has a balance (with timestamp for decay)
      arbiter.addRelation(accKey, 'has_balance', 'unit:usd', {
        value: Math.floor(Math.random() * 9900) + 100,
        updated_last_at: Date.now() - Math.floor(Math.random() * 48 * 60 * 60 * 1000) // up to 48h old
      });
    }
    for (let i = 0; i < (scale === 'medium' ? 100 : 500); i++) {
      const planKey = `plan:plan${i}`;
      plans.push(planKey);
      arbiter.addNode(planKey, 'plan');
      arbiter.addRelation(planKey, 'has_price', 'unit:usd', { value: Math.floor(Math.random() * 4950) + 50 });
    }
    for (let i = 0; i < (scale === 'medium' ? 500 : 2000); i++) {
      const featKey = `feature:feat${i}`;
      features.push(featKey);
      arbiter.addNode(featKey, 'feature');
      // Each feature has a price
      arbiter.addRelation(featKey, 'has_price', 'unit:usd', { value: Math.floor(Math.random() * 4950) + 50 });
      // Some features belong to a plan
      if (Math.random() < 0.5) {
        const plan = plans[Math.floor(Math.random() * plans.length)];
        arbiter.addRelation(featKey, 'belongs_to_plan', plan);
      }
    }
    // Each user can debit a random subset of accounts
    for (const user of users) {
      for (let j = 0; j < (scale === 'medium' ? 3 : 5); j++) {
        const acc = accounts[Math.floor(Math.random() * accounts.length)];
        arbiter.addRelation(user, 'can_debit', acc);
      }
    }
    arbiter.setRelationConfig('can_debit', { type: 'direct' });
    arbiter.setRelationConfig('belongs_to_plan', { type: 'direct' });
    arbiter.setRelationConfig('has_balance', { type: 'direct' });
    arbiter.setRelationConfig('has_price', { type: 'direct' });
    // Simplified RelationalComparatorRule: user balance vs feature price
    arbiter.setRelationConfig('can_afford_feature', {
      type: 'relational_comparator',
      leftOperand: {
        rule: { type: 'direct', relation: 'has_balance' },
        extractValue: true,
        valueRelation: 'has_balance',
        aggregator: 'max'
      },
      rightOperand: {
        rule: { type: 'direct', relation: 'has_price' },
        extractValue: true,
        valueRelation: 'has_price',
        aggregator: 'max'
      },
      comparator: '>=',
      fallbackBehavior: 'deny'
    });
    // Benchmark: can user afford feature (complex, multi-hop, aggregated, decayed)
    await benchmark.run(() => {
      const user = users[Math.floor(Math.random() * users.length)];
      const feature = features[Math.floor(Math.random() * features.length)];
      arbiter.check(user, 'can_afford_feature', feature);
    }, scale === 'medium' ? 200 : 30, 'Complex: can_debit sum >= min(plan/feature price)');
    // Optionally: Nested logical operator (e.g., require both can afford AND recent 2FA)
    // For brevity, just add a dummy 2FA relation and a logical AND
    for (const user of users) {
      arbiter.addRelation(user, 'last_2fa', '2fa:recent', { value: Date.now() - Math.floor(Math.random() * 60 * 60 * 1000) });
    }
    arbiter.addNode('2fa:recent', '2fa');
    arbiter.setRelationConfig('last_2fa', { type: 'direct' });
    arbiter.setRelationConfig('recent_2fa_check', {
      type: 'relational_comparator',
      leftOperand: {
        rule: { type: 'direct', relation: 'last_2fa' },
        extractValue: true
      },
      rightOperand: {
        rule: { type: 'computed', value: Date.now() - 10 * 60 * 1000 }, // 10 minutes ago
        extractValue: true
      },
      comparator: '>=', // last_2fa >= threshold (i.e., more recent)
      fallbackBehavior: 'deny'
    });
    arbiter.setRelationConfig('can_afford_and_recent_2fa', {
      type: 'intersection',
      rules: [
        { type: 'relational_comparator',
          leftOperand: {
            rule: { type: 'direct', relation: 'has_balance' },
            extractValue: true,
            valueRelation: 'has_balance',
            aggregator: 'max'
          },
          rightOperand: {
            rule: { type: 'direct', relation: 'has_price' },
            extractValue: true,
            valueRelation: 'has_price',
            aggregator: 'max'
          },
          comparator: '>=',
          fallbackBehavior: 'deny'
        },
        { type: 'relational_comparator',
          leftOperand: {
            rule: { type: 'direct', relation: 'last_2fa' },
            extractValue: true
          },
          rightOperand: {
            rule: { type: 'computed', value: Date.now() - 10 * 60 * 1000 },
            extractValue: true
          },
          comparator: '>=',
          fallbackBehavior: 'deny'
        }
      ]
    });
    await benchmark.run(() => {
      const user = users[Math.floor(Math.random() * users.length)];
      const feature = features[Math.floor(Math.random() * features.length)];
      arbiter.check(user, 'can_afford_and_recent_2fa', feature);
    }, scale === 'medium' ? 200 : 30, 'Complex: can_afford AND recent_2FA');
    benchmark.report();
  }
}

if (import.meta.url === `file://${process.argv[1]}`) {
  runRelationalComparatorBenchmarks();
}

export { runRelationalComparatorBenchmarks }; 