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

const EPS = 1e-6;

function approxEqual(actual, expected, epsilon = EPS) {
  assert.ok(Math.abs(actual - expected) <= epsilon, `expected ${expected} but got ${actual}`);
}

function addValueRelation(arbiter, src, relation, dst, value, possibility, reliability) {
  arbiter.addRelation(src, relation, dst, { value, possibility, reliability });
  arbiter.setRelationConfig(relation, { type: 'direct' });
}

function buildComparatorRule(leftRelations, rightRelations, weights) {
  return {
    type: 'relational_comparator',
    comparator: '>=',
    fallbackBehavior: 'deny',
    left: {
      rule: {
        union: {
          rules: leftRelations.map((relation) => ({ type: 'direct', relation })),
          aggregator: 'owa',
          owaWeights: weights
        }
      },
      extractValue: true,
      aggregator: 'owa',
      owaWeights: weights
    },
    right: {
      rule: {
        union: {
          rules: rightRelations.map((relation) => ({ type: 'direct', relation })),
          aggregator: 'owa',
          owaWeights: weights
        }
      },
      extractValue: true,
      aggregator: 'owa',
      owaWeights: weights,
      evaluateFrom: 'object'
    }
  };
}

function fuseTriples(values, possibilities, reliabilities, weights) {
  const metas = values.map(() => ({}));
  return OWAFusion.fuseTriplesWithMeta(values, possibilities, reliabilities, metas, weights, 'owa');
}

function computeComparatorOutcome(left, right, weights) {
  const fusedLeft = fuseTriples(left.values, left.possibilities, left.reliabilities, weights);
  const fusedRight = fuseTriples(right.values, right.possibilities, right.reliabilities, weights);
  const comparison = fusedLeft.value >= fusedRight.value ? 1 : 0;
  const averageOperandPossibility = (fusedLeft.possibility + fusedRight.possibility) / 2;
  const confidenceWeight = Math.min(averageOperandPossibility * 2, 1.0);
  return {
    possibility: comparison * confidenceWeight,
    reliability: Math.min(fusedLeft.reliability, fusedRight.reliability)
  };
}

describe('Relational comparator uncertainty', () => {
  test('OWA value aggregation drives comparator possibility', () => {
    const arbiter = new Arbiter();
    arbiter.addNode('user:alice', 'user');
    arbiter.addNode('doc:1', 'doc');

    const weights = [0.6, 0.3, 0.1];
    const left = {
      relations: ['risk_low', 'risk_mid', 'risk_high'],
      values: [10, 20, 40],
      possibilities: [0.9, 0.6, 0.4],
      reliabilities: [0.8, 0.9, 0.7]
    };
    const right = {
      relations: ['limit_low', 'limit_mid', 'limit_high'],
      values: [15, 25, 30],
      possibilities: [0.8, 0.5, 0.9],
      reliabilities: [0.9, 0.8, 0.95]
    };

    left.relations.forEach((relation, idx) => {
      addValueRelation(arbiter, 'user:alice', relation, 'doc:1', left.values[idx], left.possibilities[idx], left.reliabilities[idx]);
    });
    right.relations.forEach((relation, idx) => {
      addValueRelation(arbiter, 'doc:1', relation, 'doc:1', right.values[idx], right.possibilities[idx], right.reliabilities[idx]);
    });

    arbiter.setRelationConfig('risk_ok', buildComparatorRule(left.relations, right.relations, weights));

    const expected = computeComparatorOutcome(left, right, weights);
    const result = arbiter.check('user:alice', 'risk_ok', 'doc:1', { fastPath: false, explain: true });

    approxEqual(result.possibility, expected.possibility);
    approxEqual(result.reliability, expected.reliability);
  });

  test('low operand confidence scales comparator output', () => {
    const arbiter = new Arbiter();
    arbiter.addNode('user:alice', 'user');
    arbiter.addNode('doc:1', 'doc');

    const weights = [0.5, 0.3, 0.2];
    const left = {
      relations: ['risk_a', 'risk_b', 'risk_c'],
      values: [50, 30, 10],
      possibilities: [0.1, 0.1, 0.1],
      reliabilities: [0.9, 0.9, 0.9]
    };
    const right = {
      relations: ['limit_a', 'limit_b', 'limit_c'],
      values: [20, 15, 5],
      possibilities: [0.1, 0.1, 0.1],
      reliabilities: [0.9, 0.9, 0.9]
    };

    left.relations.forEach((relation, idx) => {
      addValueRelation(arbiter, 'user:alice', relation, 'doc:1', left.values[idx], left.possibilities[idx], left.reliabilities[idx]);
    });
    right.relations.forEach((relation, idx) => {
      addValueRelation(arbiter, 'doc:1', relation, 'doc:1', right.values[idx], right.possibilities[idx], right.reliabilities[idx]);
    });

    arbiter.setRelationConfig('risk_ok', buildComparatorRule(left.relations, right.relations, weights));

    const expected = computeComparatorOutcome(left, right, weights);
    const result = arbiter.check('user:alice', 'risk_ok', 'doc:1', { fastPath: false, explain: true });

    approxEqual(result.possibility, expected.possibility);
    approxEqual(result.reliability, expected.reliability);
    assert.ok(result.possibility < 1, 'expected confidence scaling to reduce possibility');
  });

  test('OWA union aggregates comparator results with uncertainty', () => {
    const arbiter = new Arbiter();
    arbiter.addNode('user:alice', 'user');
    arbiter.addNode('doc:1', 'doc');

    const weights = [0.6, 0.3, 0.1];
    const unionWeights = [0.7, 0.3];

    const leftA = {
      relations: ['risk_a1', 'risk_a2', 'risk_a3'],
      values: [12, 18, 25],
      possibilities: [0.9, 0.7, 0.5],
      reliabilities: [0.9, 0.8, 0.7]
    };
    const rightA = {
      relations: ['limit_a1', 'limit_a2', 'limit_a3'],
      values: [10, 15, 20],
      possibilities: [0.8, 0.6, 0.7],
      reliabilities: [0.9, 0.9, 0.8]
    };
    const leftB = {
      relations: ['risk_b1', 'risk_b2', 'risk_b3'],
      values: [8, 9, 11],
      possibilities: [0.4, 0.5, 0.6],
      reliabilities: [0.8, 0.8, 0.8]
    };
    const rightB = {
      relations: ['limit_b1', 'limit_b2', 'limit_b3'],
      values: [9, 10, 12],
      possibilities: [0.7, 0.6, 0.5],
      reliabilities: [0.9, 0.9, 0.9]
    };

    leftA.relations.forEach((relation, idx) => {
      addValueRelation(arbiter, 'user:alice', relation, 'doc:1', leftA.values[idx], leftA.possibilities[idx], leftA.reliabilities[idx]);
    });
    rightA.relations.forEach((relation, idx) => {
      addValueRelation(arbiter, 'doc:1', relation, 'doc:1', rightA.values[idx], rightA.possibilities[idx], rightA.reliabilities[idx]);
    });
    leftB.relations.forEach((relation, idx) => {
      addValueRelation(arbiter, 'user:alice', relation, 'doc:1', leftB.values[idx], leftB.possibilities[idx], leftB.reliabilities[idx]);
    });
    rightB.relations.forEach((relation, idx) => {
      addValueRelation(arbiter, 'doc:1', relation, 'doc:1', rightB.values[idx], rightB.possibilities[idx], rightB.reliabilities[idx]);
    });

    const comparatorA = buildComparatorRule(leftA.relations, rightA.relations, weights);
    const comparatorB = buildComparatorRule(leftB.relations, rightB.relations, weights);

    arbiter.setRelationConfig('risk_union', {
      union: {
        rules: [comparatorA, comparatorB],
        aggregator: 'owa',
        owaWeights: unionWeights
      }
    });

    const outcomeA = computeComparatorOutcome(leftA, rightA, weights);
    const outcomeB = computeComparatorOutcome(leftB, rightB, weights);
    const expectedUnion = OWAFusion.fuseWithMeta(
      [outcomeA.possibility, outcomeB.possibility],
      [{}, {}],
      unionWeights,
      'owa',
      true
    ).value;

    const result = arbiter.check('user:alice', 'risk_union', 'doc:1', { fastPath: false });
    approxEqual(result.possibility, expectedUnion);
  });
});
