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

function parseArgNumber(args, name, fallback) {
  const idx = args.indexOf(name);
  if (idx === -1 || idx + 1 >= args.length) return fallback;
  const value = Number(args[idx + 1]);
  return Number.isFinite(value) ? value : fallback;
}

function hasArg(args, name) {
  return args.includes(name);
}

class Profiler {
  constructor() {
    this.stats = new Map();
  }

  _record(label, deltaMs) {
    let entry = this.stats.get(label);
    if (!entry) {
      entry = { calls: 0, totalMs: 0 };
      this.stats.set(label, entry);
    }
    entry.calls += 1;
    entry.totalMs += deltaMs;
  }

  wrap(obj, methodName, label) {
    if (!obj || typeof obj[methodName] !== 'function') return;
    const original = obj[methodName];
    if (original.__profiled) return;
    const profiler = this;
    const wrapped = function(...args) {
      const start = process.hrtime.bigint();
      try {
        return original.apply(this, args);
      } finally {
        const deltaMs = Number(process.hrtime.bigint() - start) / 1e6;
        profiler._record(label, deltaMs);
      }
    };
    wrapped.__profiled = true;
    obj[methodName] = wrapped;
  }

  report() {
    const entries = Array.from(this.stats.entries()).map(([label, data]) => ({
      label,
      calls: data.calls,
      totalMs: data.totalMs,
      avgMs: data.calls ? data.totalMs / data.calls : 0
    }));
    entries.sort((a, b) => b.totalMs - a.totalMs);
    console.log('profile:function_ms');
    for (const entry of entries) {
      console.log(`  ${entry.label} calls=${entry.calls} total_ms=${entry.totalMs.toFixed(3)} avg_ms=${entry.avgMs.toFixed(6)}`);
    }
  }
}

function buildOwaComparatorScenario(arbiter, size) {
  for (let i = 0; i < size; i++) {
    arbiter.addNode(`user:${i}`, 'user');
    arbiter.addNode(`resource:${i}`, 'resource');
    arbiter.addRelation(`user:${i}`, 'risk_score', `resource:${i}`, 1.0, { value: i % 2 === 0 ? 20 : 80 });
    arbiter.addRelation(`user:${i}`, 'risk_bonus', `resource:${i}`, 1.0, { value: i % 2 === 0 ? 5 : 15 });
    arbiter.addRelation(`user:${i}`, 'risk_noise', `resource:${i}`, 1.0, { value: i % 2 === 0 ? 0 : 10 });
    arbiter.addRelation(`resource:${i}`, 'risk_limit', `resource:${i}`, 1.0, { value: 40 });
    arbiter.addRelation(`resource:${i}`, 'risk_cap', `resource:${i}`, 1.0, { value: 45 });
  }

  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_union_left', {
    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]
    }
  });

  arbiter.setRelationConfig('risk_union_right', {
    union: {
      rules: [
        { type: 'direct', relation: 'risk_limit' },
        { type: 'direct', relation: 'risk_cap' }
      ],
      aggregator: 'owa',
      owaWeights: [0.6, 0.4]
    }
  });

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

function resetSortCounter() {
  OWAFusion.sortCounter = { total: 0, byMode: {}, byMethod: {} };
  return OWAFusion.sortCounter;
}

function snapshotSortCounter() {
  const counter = OWAFusion.sortCounter || { total: 0, byMode: {}, byMethod: {} };
  return {
    total: counter.total || 0,
    byMode: { ...counter.byMode },
    byMethod: { ...counter.byMethod }
  };
}

function runTrace(arbiter, relation, userKey, objectKey, label) {
  resetSortCounter();
  const start = process.hrtime.bigint();
  const result = arbiter.check(userKey, relation, objectKey, {
    fastPath: true,
    includeMeta: true,
    cacheRuleResult: false
  });
  const durationMs = Number(process.hrtime.bigint() - start) / 1e6;
  const counts = snapshotSortCounter();

  console.log(`trace:${label}`);
  console.log(`  result=${result.possibility.toFixed(3)} duration_ms=${durationMs.toFixed(3)}`);
  console.log(`  sorts_total=${counts.total}`);
  console.log(`  sorts_by_method=${JSON.stringify(counts.byMethod)}`);
  console.log(`  sorts_by_mode=${JSON.stringify(counts.byMode)}`);
}

function runBench(arbiter, relation, size, runs, label, useResourceKey = false) {
  const userKey = `user:${Math.floor(size / 2)}`;
  const objectKey = `resource:${Math.floor(size / 2)}`;
  const subjectKey = useResourceKey ? objectKey : userKey;
  const targetKey = useResourceKey ? objectKey : objectKey;
  resetSortCounter();

  for (let i = 0; i < 200; i++) {
    arbiter.check(subjectKey, relation, targetKey, { fastPath: true, cacheRuleResult: false });
  }

  const start = process.hrtime.bigint();
  for (let i = 0; i < runs; i++) {
    arbiter.check(subjectKey, relation, targetKey, { fastPath: true, cacheRuleResult: false });
  }
  const durationMs = Number(process.hrtime.bigint() - start) / 1e6;
  const counts = snapshotSortCounter();
  const perQuery = durationMs / runs;
  const sortsPerQuery = runs > 0 ? counts.total / runs : 0;

  console.log(`bench:${label}`);
  console.log(`  runs=${runs} total_ms=${durationMs.toFixed(2)} per_query_ms=${perQuery.toFixed(4)}`);
  console.log(`  sorts_total=${counts.total} sorts_per_query=${sortsPerQuery.toFixed(3)}`);
  console.log(`  sorts_by_method=${JSON.stringify(counts.byMethod)}`);
  console.log(`  sorts_by_mode=${JSON.stringify(counts.byMode)}`);
}

const args = process.argv.slice(2);
const size = parseArgNumber(args, '--size', 10000);
const runs = parseArgNumber(args, '--runs', 20000);
const traceOne = hasArg(args, '--trace-one');
const profile = hasArg(args, '--profile');

const arbiter = new Arbiter({
  enableRuleResultCache: false,
  disableCaching: true
});
buildOwaComparatorScenario(arbiter, size);

let profiler = null;
if (profile) {
  profiler = new Profiler();
  profiler.wrap(arbiter, 'check', 'Arbiter.check');
  profiler.wrap(arbiter.authChecker, 'check', 'AuthorizationChecker.check');
  profiler.wrap(arbiter.authChecker.ruleEvaluator, 'evaluateRule', 'RuleEvaluator.evaluateRule');
  profiler.wrap(arbiter.authChecker.ruleEvaluator.logicalOperators, 'evaluateUnion', 'LogicalOperators.evaluateUnion');
  profiler.wrap(arbiter.authChecker.ruleEvaluator.ruleHandlers.direct, 'evaluate', 'DirectRule.evaluate');
  const comparator = arbiter.authChecker.ruleEvaluator.ruleHandlers.relational_comparator?.rule;
  if (comparator) {
    profiler.wrap(comparator, '_evaluateOperand', 'RelationalComparatorRule._evaluateOperand');
    profiler.wrap(comparator, '_extractValues', 'RelationalComparatorRule._extractValues');
    profiler.wrap(comparator, '_aggregateCrispValues', 'RelationalComparatorRule._aggregateCrispValues');
    profiler.wrap(comparator, '_compareBlurredValues', 'RelationalComparatorRule._compareBlurredValues');
  }
  profiler.wrap(OWAFusion, 'fuseWithMeta', 'OWAFusion.fuseWithMeta');
  profiler.wrap(OWAFusion, 'fuseTriplesWithMeta', 'OWAFusion.fuseTriplesWithMeta');
}

if (traceOne) {
  const userKey = `user:${Math.floor(size / 2)}`;
  const objectKey = `resource:${Math.floor(size / 2)}`;
  const resourceKey = objectKey;
  runTrace(arbiter, 'risk_union_left', userKey, objectKey, 'union_left');
  runTrace(arbiter, 'risk_union_right', resourceKey, resourceKey, 'union_right');
  runTrace(arbiter, 'risk_ok_owa', userKey, objectKey, 'comparator_nested');
} else {
  runBench(arbiter, 'risk_union_left', size, Math.max(1000, Math.floor(runs / 2)), 'union_left');
  runBench(arbiter, 'risk_union_right', size, Math.max(1000, Math.floor(runs / 2)), 'union_right', true);
  runBench(arbiter, 'risk_ok_owa', size, runs, 'comparator_nested');
}

if (profiler) {
  profiler.report();
}
