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

test('reducers: direct rule uses reduced same-triple fact', () => {
  const arbiter = new Arbiter();
  arbiter.addNode('user:1', 'user');
  arbiter.addNode('ts:1', 'timestamp');
  arbiter.setRelationConfig('request_has_timestamp', { type: 'direct' });

  const partialGraph = {
    options: { reducers: { request_has_timestamp: 'latest' } },
    relations: [
      {
        src: 'user:1',
        relation: 'request_has_timestamp',
        dst: 'ts:1',
        possibility: 0.9,
        value: 100,
        updated_last_at: 100,
        layer_name: 'request_observed'
      },
      {
        src: 'user:1',
        relation: 'request_has_timestamp',
        dst: 'ts:1',
        possibility: 0.2,
        value: 200,
        updated_last_at: 200,
        layer_name: 'request_observed'
      }
    ]
  };

  const result = arbiter.check('user:1', 'request_has_timestamp', 'ts:1', { partialGraph });
  assert.equal(result.possibility, 0.2);
});

test('reducers: chain rule consumes reduced edge selections', () => {
  const arbiter = new Arbiter();
  arbiter.addNode('user:1', 'user');
  arbiter.addNode('mid:1', 'mid');
  arbiter.addNode('obj:1', 'obj');
  arbiter.setRelationConfig('request_has_timestamp', { type: 'direct' });
  arbiter.setRelationConfig('caller_risk_hint', { type: 'direct' });
  arbiter.setRelationConfig('can_chain', {
    type: 'chain',
    steps: [
      { relation: 'request_has_timestamp', direction: 'out' },
      { relation: 'caller_risk_hint', direction: 'out' }
    ]
  });

  const partialGraph = {
    options: {
      reducers: {
        request_has_timestamp: 'latest',
        caller_risk_hint: 'strongest'
      }
    },
    relations: [
      {
        src: 'user:1',
        relation: 'request_has_timestamp',
        dst: 'mid:1',
        possibility: 0.95,
        updated_last_at: 100,
        layer_name: 'request_observed'
      },
      {
        src: 'user:1',
        relation: 'request_has_timestamp',
        dst: 'mid:1',
        possibility: 0.2,
        updated_last_at: 200,
        layer_name: 'request_observed'
      },
      {
        src: 'mid:1',
        relation: 'caller_risk_hint',
        dst: 'obj:1',
        possibility: 0.9,
        layer_name: 'caller_declared'
      }
    ]
  };

  const result = arbiter.check('user:1', 'can_chain', 'obj:1', { partialGraph });
  assert.equal(result.possibility, 0.2);
});

test('reducers: multi-hop rule consumes reduced edge selections', () => {
  const arbiter = new Arbiter();
  arbiter.addNode('user:1', 'user');
  arbiter.addNode('hop:1', 'hop');
  arbiter.addNode('obj:1', 'obj');
  arbiter.setRelationConfig('caller_risk_hint', { type: 'direct' });
  arbiter.setRelationConfig('can_reach', { type: 'multi_hop', relation: 'caller_risk_hint', maxDepth: 3 });

  const partialGraph = {
    options: { reducers: { caller_risk_hint: 'strongest' } },
    relations: [
      {
        src: 'user:1',
        relation: 'caller_risk_hint',
        dst: 'hop:1',
        possibility: 0.2,
        layer_name: 'caller_declared'
      },
      {
        src: 'user:1',
        relation: 'caller_risk_hint',
        dst: 'hop:1',
        possibility: 0.85,
        layer_name: 'caller_declared'
      },
      {
        src: 'hop:1',
        relation: 'caller_risk_hint',
        dst: 'obj:1',
        possibility: 0.9,
        layer_name: 'caller_declared'
      }
    ]
  };

  const result = arbiter.check('user:1', 'can_reach', 'obj:1', { partialGraph });
  assert.equal(result.possibility, 0.85);
});

test('reducers: tuple_to_userset rule consumes reduced userset relation', () => {
  const arbiter = new Arbiter();
  arbiter.addNode('user:1', 'user');
  arbiter.addNode('group:1', 'group');
  arbiter.addNode('doc:1', 'doc');

  arbiter.setRelationConfig('request_has_timestamp', { type: 'direct' });
  arbiter.setRelationConfig('request_has_id', { type: 'direct' });
  arbiter.setRelationConfig('can_access', {
    type: 'tuple_to_userset',
    tuplesetRelation: 'request_has_id',
    computedRelation: 'request_has_timestamp',
    reverse: false
  });

  const partialGraph = {
    options: { reducers: { request_has_timestamp: 'latest' } },
    relations: [
      {
        src: 'doc:1',
        relation: 'request_has_id',
        dst: 'group:1',
        possibility: 1,
        layer_name: 'request_observed'
      },
      {
        src: 'user:1',
        relation: 'request_has_timestamp',
        dst: 'group:1',
        possibility: 0.95,
        updated_last_at: 100,
        layer_name: 'request_observed'
      },
      {
        src: 'user:1',
        relation: 'request_has_timestamp',
        dst: 'group:1',
        possibility: 0.25,
        updated_last_at: 200,
        layer_name: 'request_observed'
      }
    ]
  };

  const result = arbiter.check('user:1', 'can_access', 'doc:1', { partialGraph });
  assert.equal(result.possibility, 0.25);
});

test('reducers: relational comparator uses reduced operand values', () => {
  const arbiter = new Arbiter();
  arbiter.addNode('user:1', 'user');
  arbiter.addNode('feature:1', 'feature');

  arbiter.setRelationConfig('request_has_timestamp', { type: 'direct' });
  arbiter.setRelationConfig('request_has_id', { type: 'direct' });
  arbiter.setRelationConfig('can_pay', {
    type: 'relational_comparator',
    left: {
      rule: { type: 'direct', relation: 'request_has_timestamp' },
      extractValue: true,
      aggregation: 'max',
      decayRate: 0,
      decayFunction: 'rational'
    },
    right: {
      rule: { type: 'direct', relation: 'request_has_id', evaluateFrom: 'object' },
      extractValue: true,
      aggregation: 'min',
      decayRate: 0,
      decayFunction: 'rational',
      evaluateFrom: 'object'
    },
    comparator: '>=',
    fallbackBehavior: 'deny'
  });

  const partialGraph = {
    options: { reducers: { request_has_timestamp: 'latest' } },
    relations: [
      {
        src: 'user:1',
        relation: 'request_has_timestamp',
        dst: 'feature:1',
        value: 120,
        possibility: 1,
        updated_last_at: 100,
        layer_name: 'request_observed'
      },
      {
        src: 'user:1',
        relation: 'request_has_timestamp',
        dst: 'feature:1',
        value: 20,
        possibility: 1,
        updated_last_at: 200,
        layer_name: 'request_observed'
      },
      {
        src: 'feature:1',
        relation: 'request_has_id',
        dst: 'feature:1',
        value: 50,
        possibility: 1,
        layer_name: 'request_observed'
      }
    ]
  };

  const result = arbiter.check('user:1', 'can_pay', 'feature:1', { partialGraph });
  assert.ok(result.possibility < 0.5);
});
