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

describe('Partial graph overlay', () => {
  test('direct relation resolves from partial graph', () => {
    const arbiter = new Arbiter();
    arbiter.addNode('user:1', 'user');
    arbiter.addNode('doc:1', 'doc');
    arbiter.setRelationConfig('can_read', { type: 'direct' });

    const missing = arbiter.check('user:1', 'can_read', 'doc:1');
    assert.strictEqual(missing.possibility, 0);

    const partialGraph = {
      relations: [
        { src: 'user:1', relation: 'can_read', dst: 'doc:1', possibility: 1.0 }
      ]
    };

    const result = arbiter.check('user:1', 'can_read', 'doc:1', { partialGraph });
    assert.strictEqual(result.possibility, 1.0);
  });

  test('persistent relation wins when partial conflicts on same triple', () => {
    const arbiter = new Arbiter();
    arbiter.addNode('user:1', 'user');
    arbiter.addNode('doc:1', 'doc');
    arbiter.setRelationConfig('can_read', { type: 'direct' });

    arbiter.addRelation('user:1', 'can_read', 'doc:1', 0.2);
    const baseline = arbiter.check('user:1', 'can_read', 'doc:1');
    assert.strictEqual(baseline.possibility, 0.2);

    const partialGraph = {
      relations: [
        { src: 'user:1', relation: 'can_read', dst: 'doc:1', possibility: 0.9 }
      ]
    };

    const result = arbiter.check('user:1', 'can_read', 'doc:1', { partialGraph });
    assert.strictEqual(result.possibility, 0.2);
  });

  test('mixed overlay precedence reports persistent source and audit conflict', () => {
    const arbiter = new Arbiter();
    arbiter.addNode('user:1', 'user');
    arbiter.addNode('doc:1', 'doc');
    arbiter.setRelationConfig('can_read', { type: 'direct' });

    arbiter.addRelation('user:1', 'can_read', 'doc:1', 0.2);

    const partialGraph = {
      relations: [
        { src: 'user:1', relation: 'can_read', dst: 'doc:1', possibility: 0.9 }
      ]
    };

    const result = arbiter.explain('user:1', 'can_read', 'doc:1', { partialGraph });
    assert.strictEqual(result.decision.possibility, 0.2);
    assert.strictEqual(result.trace.path[0].source, 'persistent');
    assert.strictEqual(result.audit.provenance.partial_fact_used, false);
    assert.strictEqual(result.audit.provenance.provenance_conflicts.length, 1);
  });

  test('explain marks partial provenance', () => {
    const arbiter = new Arbiter();
    arbiter.addNode('user:1', 'user');
    arbiter.addNode('doc:1', 'doc');
    arbiter.setRelationConfig('can_read', { type: 'direct' });

    const partialGraph = {
      relations: [
        { src: 'user:1', relation: 'can_read', dst: 'doc:1', possibility: 1.0 }
      ]
    };

    const result = arbiter.explain('user:1', 'can_read', 'doc:1', { partialGraph });
    assert.strictEqual(result.trace.path[0].source, 'partial');
  });

  test('partial nodes participate in chain rules', () => {
    const arbiter = new Arbiter();
    arbiter.addNode('user:1', 'user');
    arbiter.addNode('account:1', 'account');

    arbiter.setRelationConfig('device_link', { type: 'direct' });
    arbiter.setRelationConfig('logged_in_as', { type: 'direct' });
    arbiter.setRelationConfig('can_login', {
      type: 'chain',
      steps: [
        { relation: 'device_link', direction: 'out' },
        { relation: 'logged_in_as', direction: 'out' }
      ]
    });

    const partialGraph = {
      nodes: [
        { key: 'device:abc', type: 'device' }
      ],
      relations: [
        { src: 'user:1', relation: 'device_link', dst: 'device:abc', possibility: 1.0 },
        { src: 'device:abc', relation: 'logged_in_as', dst: 'account:1', possibility: 1.0 }
      ]
    };

    const result = arbiter.check('user:1', 'can_login', 'account:1', { partialGraph });
    assert.ok(result.possibility > 0);
  });

  test('multi-hop explain includes partial path sources', () => {
    const arbiter = new Arbiter();
    arbiter.addNode('user:1', 'user');
    arbiter.addNode('doc:1', 'doc');
    arbiter.setRelationConfig('path', { type: 'multi_hop', relation: 'link', maxDepth: 3 });

    const partialGraph = {
      nodes: [
        { key: 'mid:1', type: 'group' }
      ],
      relations: [
        { src: 'user:1', relation: 'link', dst: 'mid:1', possibility: 1.0 },
        { src: 'mid:1', relation: 'link', dst: 'doc:1', possibility: 1.0 }
      ]
    };

    const result = arbiter.explain('user:1', 'path', 'doc:1', { partialGraph });
    const pathSteps = result.trace.rulePaths[0]?.pathSteps || [];
    assert.ok(pathSteps.length > 0);
    for (const step of pathSteps) {
      assert.strictEqual(step.source, 'partial');
    }
  });

  test('chain collected values include source', () => {
    const arbiter = new Arbiter();
    arbiter.addNode('user:1', 'user');
    arbiter.addNode('account:1', 'account');

    arbiter.setRelationConfig('device_link', { type: 'direct' });
    arbiter.setRelationConfig('logged_in_as', { type: 'direct' });
    arbiter.setRelationConfig('can_login', {
      type: 'chain',
      steps: [
        { relation: 'device_link', direction: 'out' },
        { relation: 'logged_in_as', direction: 'out' }
      ]
    });

    const partialGraph = {
      nodes: [
        { key: 'device:abc', type: 'device' }
      ],
      relations: [
        { src: 'user:1', relation: 'device_link', dst: 'device:abc', possibility: 1.0, value: 1 },
        { src: 'device:abc', relation: 'logged_in_as', dst: 'account:1', possibility: 1.0, value: 1 }
      ]
    };

    const result = arbiter.explain('user:1', 'can_login', 'account:1', { partialGraph });
    const pathSteps = result.trace.rulePaths[0]?.pathSteps || [];
    assert.ok(pathSteps.length > 0);
    let sawPartial = false;
    for (const step of pathSteps) {
      assert.ok(step.source === 'partial' || step.source === 'persistent');
      if (step.source === 'partial') sawPartial = true;
    }
    assert.ok(sawPartial);
    const collectedValues = result.trace.values || [];
    assert.ok(collectedValues.length > 0);
    for (const cv of collectedValues) {
      assert.strictEqual(cv.metadata.source, 'partial');
    }
  });

  test('relational comparator uses partial value provenance', () => {
    const arbiter = new Arbiter();
    arbiter.addNode('user:1', 'user');
    arbiter.addNode('account:1', 'account');

    arbiter.setRelationConfig('device_risk', { type: 'direct' });
    arbiter.setRelationConfig('risk_limit', { type: 'direct' });
    arbiter.setRelationConfig('risk_ok', {
      type: 'relational_comparator',
      left: {
        rule: { type: 'direct', relation: 'device_risk' },
        extractValue: true,
        valueRelation: 'device_risk',
        evaluateFrom: 'auto'
      },
      right: {
        rule: { type: 'direct', relation: 'risk_limit' },
        extractValue: true,
        valueRelation: 'risk_limit',
        evaluateFrom: 'auto'
      },
      comparator: '<='
    });

    const partialGraph = {
      relations: [
        { src: 'user:1', relation: 'device_risk', dst: 'account:1', possibility: 1.0, value: 0.2 },
        { src: 'user:1', relation: 'risk_limit', dst: 'account:1', possibility: 1.0, value: 0.8 }
      ]
    };

    const result = arbiter.explain('user:1', 'risk_ok', 'account:1', { partialGraph });
    assert.ok(result.decision.possibility > 0);
    const comparatorNode = result.trace.path.find(node => node.type === 'relational_comparator');
    const details = comparatorNode?.details || {};
    assert.strictEqual(details.leftSource, 'partial');
    assert.strictEqual(details.rightSource, 'partial');
  });
});
