import assert from 'node:assert/strict';
import { describe, test } from 'node:test';
import { Arbiter } from '../../src/core/Arbiter.js';
import { OWAFusion } from '../../src/utils/OWAFusion.js';

function createRng(seed) {
  let state = seed >>> 0;
  return () => {
    state = (1664525 * state + 1013904223) >>> 0;
    return state / 0x100000000;
  };
}

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

function randFloat(rng, min = 0, max = 1) {
  return min + (max - min) * rng();
}

function buildComparatorArbiter() {
  const arbiter = new Arbiter();
  arbiter.addNode('user:1', 'user');
  arbiter.addNode('resource:1', 'resource');

  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_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: { type: 'direct', relation: 'risk_limit' },
      extractValue: true,
      valueRelation: 'risk_limit',
      evaluateFrom: 'object'
    }
  });

  arbiter.registerDependencyIndex(new Map([
    ['risk_score', {
      all: new Set(['risk_ok_owa']),
      byLevel: {
        never: new Set(),
        always: new Set(),
        requires: new Set(),
        when: new Set(),
        unless: new Set(),
        ordinary: new Set(['risk_ok_owa'])
      }
    }],
    ['risk_bonus', {
      all: new Set(['risk_ok_owa']),
      byLevel: {
        never: new Set(),
        always: new Set(),
        requires: new Set(),
        when: new Set(),
        unless: new Set(),
        ordinary: new Set(['risk_ok_owa'])
      }
    }],
    ['risk_noise', {
      all: new Set(['risk_ok_owa']),
      byLevel: {
        never: new Set(),
        always: new Set(),
        requires: new Set(),
        when: new Set(),
        unless: new Set(),
        ordinary: new Set(['risk_ok_owa'])
      }
    }],
    ['risk_limit', {
      all: new Set(['risk_ok_owa']),
      byLevel: {
        never: new Set(),
        always: new Set(),
        requires: new Set(),
        when: new Set(),
        unless: new Set(),
        ordinary: new Set(['risk_ok_owa'])
      }
    }]
  ]));

  return arbiter;
}

describe('Comparator aggregation properties', () => {
  test('OWA aggregated value drives comparator decision', () => {
    const rng = createRng(24);
    const arbiter = buildComparatorArbiter();
    const metas = [{}, {}, {}];

    for (let i = 0; i < 120; i++) {
      const values = [
        randFloat(rng, 0, 100),
        randFloat(rng, 0, 100),
        randFloat(rng, 0, 100)
      ];
      const limit = randFloat(rng, 0, 100);

      arbiter.removeRelation('user:1', 'risk_score', 'resource:1');
      arbiter.removeRelation('user:1', 'risk_bonus', 'resource:1');
      arbiter.removeRelation('user:1', 'risk_noise', 'resource:1');
      arbiter.removeRelation('resource:1', 'risk_limit', 'resource:1');

      arbiter.addRelation('user:1', 'risk_score', 'resource:1', 1.0, { value: values[0] });
      arbiter.addRelation('user:1', 'risk_bonus', 'resource:1', 1.0, { value: values[1] });
      arbiter.addRelation('user:1', 'risk_noise', 'resource:1', 1.0, { value: values[2] });
      arbiter.addRelation('resource:1', 'risk_limit', 'resource:1', 1.0, { value: limit });

      const fused = OWAFusion.fuseWithMeta(values, metas, [0.5, 0.3, 0.2], 'owa', true).value;
      const expectedAllow = fused <= limit;

      const result = arbiter.check('user:1', 'risk_ok_owa', 'resource:1', { fastPath: false });
      const allow = result.possibility > 0;
      assert.strictEqual(allow, expectedAllow, 'comparator respects aggregation');
    }
  });

  test('increasing a component value does not decrease fused value', () => {
    const rng = createRng(88);
    const arbiter = buildComparatorArbiter();
    const metas = [{}, {}, {}];

    for (let i = 0; i < 120; i++) {
      const values = [
        randFloat(rng, 0, 100),
        randFloat(rng, 0, 100),
        randFloat(rng, 0, 100)
      ];
      const index = randInt(rng, values.length);
      const delta = randFloat(rng, 0, 20);
      const bumped = values.slice();
      bumped[index] += delta;

      const base = OWAFusion.fuseWithMeta(values, metas, [0.5, 0.3, 0.2], 'owa', true).value;
      const higher = OWAFusion.fuseWithMeta(bumped, metas, [0.5, 0.3, 0.2], 'owa', true).value;
      assert.ok(higher >= base - 1e-6, 'fused value is monotonic');
    }
  });
});
