import { test, describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { Arbiter } from '../../src/index.js';
import { isStringObject } from 'node:util/types';
// Helper to set up a test graph for relational comparator scenarios
function setupRelationalComparatorTestGraph() {
  const arbiter = new Arbiter({ embeddingDimensions: 256 });
  
  // Configure direct relation rules that RelationalComparatorRule will use
  arbiter.setRelationConfig('has_balance', { type: 'direct' });
  arbiter.setRelationConfig('has_price', { type: 'direct' });
  arbiter.setRelationConfig('last_2fa', { type: 'direct' });
  arbiter.setRelationConfig('belongs_to_plan', { type: 'direct' });
  
  // Create test entities
  arbiter.addNode('user:alice', 'user');
  arbiter.addNode('user:bob', 'user');
  arbiter.addNode('user:charlie', 'user');
  arbiter.addNode('feature:premium', 'feature');
  arbiter.addNode('feature:basic', 'feature');
  arbiter.addNode('feature:enterprise', 'feature');
  arbiter.addNode('plan:gold', 'plan');
  arbiter.addNode('action:sensitive', 'action');
  
  // Add target nodes for relations
  arbiter.addNode('account:checking', 'account');
  arbiter.addNode('account:savings', 'account');
  arbiter.addNode('price:basic', 'price');
  arbiter.addNode('price:premium', 'price');
  arbiter.addNode('price:enterprise', 'price');
  arbiter.addNode('price:gold', 'price');
  arbiter.addNode('2fa:recent', '2fa');
  arbiter.addNode('2fa:old', '2fa');
  arbiter.addNode('time:threshold', 'time');
  
  // Add value-carrying relations (balances, prices, etc.)
  const now = Date.now();
  const oneHourAgo = now - 60 * 60 * 1000;
  const twelveHoursAgo = now - 12 * 60 * 60 * 1000;
  const twoDaysAgo = now - 48 * 60 * 60 * 1000;
  
  // User balances (directly attached to feature targets)
  arbiter.addRelation('user:alice', 'has_balance', 'feature:basic', {
    value: 1500,
    updated_last_at: now,
    changed_last_at: now
  });
  arbiter.addRelation('user:alice', 'has_balance', 'feature:premium', {
    value: 1500,
    updated_last_at: now,
    changed_last_at: now
  });
  arbiter.addRelation('user:alice', 'has_balance', 'feature:enterprise', {
    value: 1500,
    updated_last_at: now,
    changed_last_at: now
  });

  arbiter.addRelation('user:bob', 'has_balance', 'feature:basic', {
    value: 500,
    updated_last_at: now,
    changed_last_at: now
  });
  arbiter.addRelation('user:bob', 'has_balance', 'feature:premium', {
    value: 500,
    updated_last_at: now,
    changed_last_at: now
  });
  arbiter.addRelation('user:bob', 'has_balance', 'feature:enterprise', {
    value: 500,
    updated_last_at: now,
    changed_last_at: now
  });
  
  // Feature prices
  arbiter.addRelation('feature:basic', 'has_price', 'feature:basic', {
    value: 100
  });

  arbiter.addRelation('feature:premium', 'has_price', 'feature:premium', {
    value: 1000
  });

  arbiter.addRelation('feature:enterprise', 'has_price', 'feature:enterprise', {
    value: 5000
  });
  
  // 2FA timestamps
  arbiter.addRelation('user:alice', 'last_2fa', '2fa:recent', {
    value: now - 5 * 60 * 1000  // 5 minutes ago
  });
  
  arbiter.addRelation('user:bob', 'last_2fa', '2fa:old', {
    value: now - 2 * 60 * 60 * 1000  // 2 hours ago
  });
  
  // Plan relationships
  arbiter.addRelation('feature:enterprise', 'belongs_to_plan', 'plan:gold');
  arbiter.addRelation('plan:gold', 'has_price', 'plan:gold', {
    value: 3000  // Discounted price through plan
  });

  return arbiter;
}

describe('RelationalComparatorRule Tests', () => {
  
  describe('Basic Value Comparisons', () => {
    it('allows access when user balance exceeds feature price', () => {
      // Arbiter.DEBUG = true;
      const arbiter = setupRelationalComparatorTestGraph();
      // No decay for this test
      arbiter.setRelationConfig('can_access', {
        type: 'relational_comparator',
        left: {
          rule: {
            type: 'direct',
            relation: 'has_balance'
          },
          extractValue: true,
          aggregation: 'max',
          decayRate: 0,
          decayFunction: 'rational'
        },
        right: {
          rule: {
            type: 'direct',
            relation: 'has_price',
            evaluateFrom: 'object'
          },
          extractValue: true,
          aggregation: 'min',
          decayRate: 0,
          decayFunction: 'rational',
          evaluateFrom: 'object'
        },
        comparator: '>=',
        marginOfSafety: 1.1,
        fallbackBehavior: 'deny'
      });
      // Alice ($1500) should afford basic feature ($100)

      const result1 = arbiter.check('user:alice', 'can_access', 'feature:basic');
      assert.ok(result1.possibility >= 0.999);
      assert.equal(result1.reason, 'allow_rule_matched');
      // Alice ($1500) should afford premium feature ($1000)
      const result2 = arbiter.check('user:alice', 'can_access', 'feature:premium');
      assert.ok(result2.possibility >= 0.999);
      assert.equal(result2.reason, 'allow_rule_matched');
      // Bob ($500) should afford basic feature ($100)
      const result3 = arbiter.check('user:bob', 'can_access', 'feature:basic');
      assert.ok(result3.possibility >= 0.999);
      assert.equal(result3.reason, 'allow_rule_matched');
    });

    it('denies access when user balance is insufficient', () => {
      const arbiter = setupRelationalComparatorTestGraph();
      
      arbiter.setRelationConfig('can_access', {
        type: 'relational_comparator',
        left: {
          rule: {
            type: 'direct',
            relation: 'has_balance'
          },
          extractValue: true,
          aggregation: 'max'
        },
        right: {
          rule: {
            type: 'direct',
            relation: 'has_price',
            evaluateFrom: 'object'
          },
          extractValue: true,
          evaluateFrom: 'object'
        },
        comparator: '>=',
        fallbackBehavior: 'deny'
      });
      
      // Bob ($500) should NOT afford enterprise feature ($5000)
      const result = arbiter.check('user:bob', 'can_access', 'feature:enterprise');
      assert.equal(result.possibility, 0);
    });

    it('handles missing values with fallback behavior', () => {
      const arbiter = setupRelationalComparatorTestGraph();
      
      arbiter.setRelationConfig('can_access', {
        type: 'relational_comparator',
        left: {
          rule: {
            type: 'direct',
            relation: 'has_balance'
          },
          extractValue: true,
          ttl: 30 * 24 * 60 * 60 * 1000
        },
        right: {
          rule: {
            type: 'direct',
            relation: 'has_price',
            evaluateFrom: 'object'
          },
          extractValue: true,
          evaluateFrom: 'object'
        },
        comparator: '>=',
        fallbackBehavior: 'deny'
      });
      
      // Unknown user should be denied (charlie has no balance relation)
      const result = arbiter.check('user:charlie', 'can_access', 'feature:basic');
      assert.equal(result.possibility, 0);
      assert.equal(result.reason, 'no_matching_rule'); // No balance = deny rule matches
    });
  });

  describe('Value Extraction vs Possibility Comparison', () => {
    it('uses possibilities as values when extractValue is false', () => {
      const arbiter = setupRelationalComparatorTestGraph();
      
      // Add relations from Alice to test extractValue=false logic
      arbiter.addRelation('user:alice', 'has_balance', 'feature:basic', {
        value: 1500  // This value should be ignored when extractValue: false
      });
      
      arbiter.addRelation('user:alice', 'has_price', 'feature:basic', {
        value: 1000  // This value should also be ignored when extractValue: false
      });
      
      arbiter.setRelationConfig('privilege_comparison', {
        type: 'relational_comparator',
        left: {
          rule: {
            type: 'direct',
            relation: 'has_balance'
          },
          extractValue: false,  // Use possibility as value
          decayRate: 0 // No decay
        },
        right: {
          rule: {
            type: 'direct',
            relation: 'has_price',
            evaluateFrom: 'object'
          },
          extractValue: false,  // Use possibility as value
          decayRate: 0, // No decay
          evaluateFrom: 'object'
        },
        comparator: '>=',
        fallbackBehavior: 'deny'
      });
      // Both rules should return possibility 1.0, so 1.0 >= 1.0 = true
      const result = arbiter.check('user:alice', 'privilege_comparison', 'feature:basic');
      assert.equal(result.possibility, 1);
      assert.equal(result.reason, 'allow_rule_matched');
    });

    it('extracts actual values when extractValue is true', () => {
      const arbiter = setupRelationalComparatorTestGraph();
      
      arbiter.setRelationConfig('value_comparison', {
        type: 'relational_comparator',
        left: {
          rule: {
            type: 'direct',
            relation: 'has_balance'
          },
          extractValue: true
        },
        right: {
          rule: {
            type: 'direct',
            relation: 'has_price',
            evaluateFrom: 'object'
          },
          extractValue: true,
          evaluateFrom: 'object'
        },
        comparator: '>=',
        fallbackBehavior: 'deny'
      });
      
      const result = arbiter.check('user:alice', 'value_comparison', 'feature:basic');
      assert.ok(result.possibility >= 0.999);
      

    });
  });

  describe('Time-Based Decay', () => {
    it('ignores stale timestamps when no decay is applied', () => {
      const now = 1_000_000_000_000;

      const arbiter = setupRelationalComparatorTestGraph();
      arbiter.addNode('user:decay-test', 'user');
      arbiter.addNode('feature:decay-test', 'feature');
      arbiter.addNode('account:decay', 'account');
      arbiter.addNode('price:decay', 'price');
      arbiter.addRelation('feature:decay-test', 'has_price', 'feature:decay-test', {
        value: 1000,
        updated_last_at: now,
        changed_last_at: now
      });
      arbiter.addRelation('user:decay-test', 'has_balance', 'feature:decay-test', {
        value: 1000,
        updated_last_at: now - 24 * 60 * 60 * 1000,
        changed_last_at: now - 24 * 60 * 60 * 1000
      });
      arbiter.setRelationConfig('can_access_decay_test', {
        type: 'relational_comparator',
        left: {
          rule: {
            type: 'direct',
            relation: 'has_balance'
          },
          extractValue: true
        },
        right: {
          rule: {
            type: 'direct',
            relation: 'has_price',
            evaluateFrom: 'object'
          },
          extractValue: true,
          evaluateFrom: 'object'
        },
        comparator: '>=',
        marginOfSafety: 1.0,
        fallbackBehavior: 'deny'
      });
      const result = arbiter.check('user:decay-test', 'can_access_decay_test', 'feature:decay-test', { now });
      assert.equal(result.possibility, 1);
    });
    it('keeps comparison crisp even for older timestamps', () => {
      const arbiter = setupRelationalComparatorTestGraph();
      const now = 1_000_000_000_000;

      arbiter.addNode('user:usage-test', 'user');
      arbiter.addNode('service:limited', 'service');
      arbiter.addNode('usage:current', 'usage');
      arbiter.addNode('quota:monthly', 'quota');
      arbiter.addRelation('user:usage-test', 'has_usage', 'service:limited', {
        value: 100,
        updated_last_at: now - 12 * 60 * 60 * 1000,
        changed_last_at: now - 12 * 60 * 60 * 1000
      });
      arbiter.addRelation('service:limited', 'has_quota', 'service:limited', {
        value: 500,
        updated_last_at: now,
        changed_last_at: now
      });
      arbiter.setRelationConfig('has_usage', { type: 'direct' });
      arbiter.setRelationConfig('has_quota', { type: 'direct' });
      arbiter.setRelationConfig('within_quota', {
        type: 'relational_comparator',
        left: {
          rule: {
            type: 'direct',
            relation: 'has_usage'
          },
          extractValue: true,
          ttl: 30 * 24 * 60 * 60 * 1000
        },
        right: {
          rule: {
            type: 'direct',
            relation: 'has_quota',
            evaluateFrom: 'object'
          },
          extractValue: true,
          evaluateFrom: 'object'
        },
        comparator: '<=',
        fallbackBehavior: 'deny'
      });
      const result = arbiter.check('user:usage-test', 'within_quota', 'service:limited', { now });
      assert.equal(result.possibility, 1);
    });
  });

  describe('Complex Nested Rules', () => {
    it('supports nested logical operations in operands', () => {
      const arbiter = setupRelationalComparatorTestGraph();
      arbiter.setRelationConfig('has_balance_savings', { type: 'direct' });
      // Add a second balance for Alice
      arbiter.addRelation('user:alice', 'has_balance_savings', 'feature:enterprise', {
        value: 2000,
        updated_last_at: Date.now() - 12 * 60 * 60 * 1000,
        changed_last_at: Date.now() - 12 * 60 * 60 * 1000
      });
      arbiter.setRelationConfig('can_purchase', {
        type: 'relational_comparator',
        left: {
          rule: {
            union: {
              rules: [
                { type: 'direct', relation: 'has_balance' },
                { type: 'direct', relation: 'has_balance_savings' }
              ],
              aggregator: 'sum'
            }
          },
          extractValue: true,
          aggregation: 'sum',
          decayRate: 0,
          decayDirection: 'down'
        },
        right: {
          rule: {
            type: 'chain',
            steps: [
              { relation: 'belongs_to_plan', direction: 'out' },  // feature → plan
              { relation: 'has_price', direction: 'out' }         // plan → price
            ],
            collectValues: true,
            valueFilters: {
              steps: [1],
              relations: ['has_price']
            },
            valueAggregation: 'min',         // Use minimum plan price if multiple
            evaluateFrom: 'object'
          },
          extractValue: true,
          evaluateFrom: 'object',          // Evaluate from feature's perspective  
          decayRate: 0,        // No decay
          decayDirection: 'up'
        },
        comparator: '>=',
        marginOfSafety: 1.0,
        fallbackBehavior: 'deny'
      });
      // Alice should be able to purchase enterprise feature through plan pricing
      const result = arbiter.check('user:alice', 'can_purchase', 'feature:enterprise');
      // Should work: Alice has $1500 + $2000 = $3500 total (with some decay)
      // Enterprise through plan costs $3000, so should be affordable
      assert.equal(result.possibility, 1.0);
      assert.equal(result.reason, 'allow_rule_matched');
    });
  });

  describe('Aggregation Strategies', () => {
    it('uses max aggregation correctly', () => {
      const arbiter = setupRelationalComparatorTestGraph();
      
      arbiter.setRelationConfig('has_balance_savings', { type: 'direct' });
      // Alice has two balances, we want the max
      arbiter.addRelation('user:alice', 'has_balance_savings', 'feature:premium', {
        value: 2000  // Higher than checking balance
      });
      
      arbiter.setRelationConfig('max_balance_check', {
        type: 'relational_comparator',
        left: {
          rule: {
            union: {
              rules: [
                { type: 'direct', relation: 'has_balance' },
                { type: 'direct', relation: 'has_balance_savings' }
              ],
              aggregator: 'max'
            }
          },
          extractValue: true,
          aggregation: 'max'  // Use highest balance
        },
        right: {
          rule: {
            type: 'direct',
            relation: 'has_price',
            evaluateFrom: 'object'
          },
          extractValue: true,
          evaluateFrom: 'object'
        },
        comparator: '>=',
        fallbackBehavior: 'deny'
      });
      
      const result = arbiter.check('user:alice', 'max_balance_check', 'feature:premium', { includeMeta: true });
      assert.equal(result.possibility, 1);
      
      // Should use the max balance (2000) not the first one (1500)
      assert.equal(result.meta.allow.leftValue, 2000);
      assert.equal(result.meta.allow.rightValue, 1000);
    });

    it('uses sum aggregation correctly', () => {
      const arbiter = setupRelationalComparatorTestGraph();
      arbiter.setRelationConfig('has_balance_savings', { type: 'direct' });
      arbiter.addRelation('user:alice', 'has_balance_savings', 'feature:enterprise', {
        value: 4000  // Increased from 2000 so total becomes 5500 > 5000
      });
      arbiter.setRelationConfig('total_balance_check', {
        type: 'relational_comparator',
        left: {
          rule: {
            union: {
              rules: [
                { type: 'direct', relation: 'has_balance' },
                { type: 'direct', relation: 'has_balance_savings' }
              ],
              aggregator: 'sum'
            }
          },
          extractValue: true,
          aggregation: 'sum',
          decayRate: 0,        // No decay for this test
          decayFunction: 'rational'
        },
        right: {
          rule: {
            type: 'direct',
            relation: 'has_price',
            evaluateFrom: 'object'
          },
          extractValue: true,
          evaluateFrom: 'object',
          decayRate: 0,
          decayFunction: 'rational'
        },
        comparator: '>=',
        fallbackBehavior: 'deny'
      });
      const result = arbiter.check('user:alice', 'total_balance_check', 'feature:enterprise', { includeMeta: true });
      assert.equal(result.possibility, 1);
      // Should use sum of all balances (1500 + 4000 = 5500)
      assert.equal(result.meta.allow.leftValue, 5500);
      assert.equal(result.meta.allow.rightValue, 5000);
    });

    it('checks balance on accounts with debit rights', () => {
      const arbiter = setupRelationalComparatorTestGraph();
      
      // Set up multiple accounts with different balances
      arbiter.addNode('account:business', 'account');
      arbiter.addNode('account:personal', 'account');
      arbiter.addNode('account:restricted', 'account');
      arbiter.addNode('unit:usd', 'currency');
      
      // Create a fresh user to avoid conflicts with Alice's existing balance relations
      arbiter.addNode('user:test', 'user');
      
      // Accounts have balances (not users)
      arbiter.addRelation('account:business', 'has_balance', 'unit:usd', { value: 3000 });
      arbiter.addRelation('account:personal', 'has_balance', 'unit:usd', { value: 1500 });
      arbiter.addRelation('account:restricted', 'has_balance', 'unit:usd', { value: 10000 }); // Large balance but no debit rights
      
      // Test user has debit rights to some accounts  
      arbiter.setRelationConfig('can_debit', { type: 'direct' });
      arbiter.addRelation('user:test', 'can_debit', 'account:business');
      arbiter.addRelation('user:test', 'can_debit', 'account:personal');
      // Note: No debit rights to account:restricted
      
      // Configure a rule that checks balance on accounts she can debit from
      arbiter.setRelationConfig('authorized_balance_check', {
        type: 'relational_comparator',
        left: {
          rule: {
            type: 'chain',
            steps: [
              { relation: 'can_debit', direction: 'out' },    // Alice → accounts
              { relation: 'has_balance', direction: 'out' }   // accounts → currency
            ],
            extractValues: true,
            extractFrom: 1,  // Extract from step 1 (the accounts)
            extractRelation: 'has_balance',
            valueAggregation: 'sum',         // Sum all authorized balances
            evaluateFrom: 'user'
          },
          extractValue: true,
          aggregator: 'sum',
          evaluateFrom: 'user',            // Evaluate from Alice's perspective
          decayRate: 0,
          decayFunction: 'rational'
        },
        right: {
          rule: {
            type: 'direct',
            relation: 'has_price',
            evaluateFrom: 'object'
          },
          extractValue: true,
          evaluateFrom: 'object',          // Evaluate from feature's perspective  
          decayRate: 0,
          decayFunction: 'rational'
        },
        comparator: '>=',
        fallbackBehavior: 'deny'
      });
      
      // Alice should be able to afford premium feature with authorized accounts
      // Total authorized balance: 3000 + 1500 = 4500
      const result1 = arbiter.check('user:test', 'authorized_balance_check', 'feature:premium', { includeMeta: true });
      assert.equal(result1.possibility, 1);
      // Sum of ALL authorized balances: business (3000) + personal (1500) = 4500.
      // (Previously only the best-path value was collected, dropping the
      // personal account — fixed in ChainRule value collection.)
      assert.equal(result1.meta.allow.leftValue, 4500);
      assert.equal(result1.meta.allow.rightValue, 1000);
      
      // But she should NOT be able to afford enterprise feature
      // $4500 < enterprise price ($5000) - so this should fail
      const result2 = arbiter.check('user:test', 'authorized_balance_check', 'feature:enterprise', { includeMeta: true });
      assert.equal(result2.possibility, 0); // Should fail: 4500 < 5000
      // When authorization fails, check deny metadata instead of allow
      if (result2.meta.deny) {
        assert.equal(result2.meta.deny.leftValue, 4500);
        assert.equal(result2.meta.deny.rightValue, 5000);
      }
    });

    it('supports OWA Sum operator for scaling aggregation', () => {
      const arbiter = setupRelationalComparatorTestGraph();
      
      arbiter.setRelationConfig('has_balance_savings', { type: 'direct' });
      // Alice has multiple balances, we want to sum them with scaling
      arbiter.addRelation('user:alice', 'has_balance_savings', 'feature:enterprise', {
        value: 4000  // Increased from 2000 to make total 5500 > enterprise price 5000
      });
      
      arbiter.setRelationConfig('scaled_sum_check', {
        type: 'relational_comparator',
        left: {
          rule: {
            union: {
              rules: [
                { type: 'direct', relation: 'has_balance' },
                { type: 'direct', relation: 'has_balance_savings' }
              ],
              aggregator: 'sum'
            }
          },
          extractValue: true,
          aggregation: 'sum',  // Add this for compatibility with RelationalComparatorRule
          aggregator: 'sum',  // Sum operator - weights don't normalize to 1.0
          decayRate: 0,       // No decay for this test
          decayFunction: 'rational'
        },
        right: {
          rule: {
            type: 'direct',
            relation: 'has_price',
            evaluateFrom: 'object'
          },
          extractValue: true,
          evaluateFrom: 'object',
          decayRate: 0,
          decayFunction: 'rational'
        },
        comparator: '>=',
        fallbackBehavior: 'deny'
      });
      
      const result = arbiter.check('user:alice', 'scaled_sum_check', 'feature:enterprise', { includeMeta: true });
      assert.equal(result.possibility, 1);
      
      // Should sum all balances (1500 + 4000 = 5500)
      assert.equal(result.meta.allow.leftValue, 5500);
      assert.equal(result.meta.allow.rightValue, 5000);
    });

    it('supports majority aggregation', () => {
      const arbiter = setupRelationalComparatorTestGraph();
      
      arbiter.setRelationConfig('has_balance_primary', { type: 'direct' });
      arbiter.setRelationConfig('has_balance_secondary', { type: 'direct' });
      arbiter.setRelationConfig('has_balance_tertiary', { type: 'direct' });
      // Add multiple balance relations to Alice
      arbiter.addRelation('user:alice', 'has_balance_primary', 'feature:premium', { value: 2500 });
      arbiter.addRelation('user:alice', 'has_balance_secondary', 'feature:premium', { value: 800 });
      arbiter.addRelation('user:alice', 'has_balance_tertiary', 'feature:premium', { value: 1200 });
      
      arbiter.setRelationConfig('majority_balance_check', {
        type: 'relational_comparator',
        left: {
          rule: {
            union: {
              rules: [
                { type: 'direct', relation: 'has_balance_primary' },
                { type: 'direct', relation: 'has_balance_secondary' },
                { type: 'direct', relation: 'has_balance_tertiary' }
              ],
              aggregator: 'majority'
            }
          },
          extractValue: true,
          aggregator: 'majority',  // Add this for RelationalComparatorRule
          aggregator: 'majority'  // Majority aggregation - focuses on typical values
        },
        right: {
          rule: {
            type: 'direct',
            relation: 'has_price',
            evaluateFrom: 'object'
          },
          extractValue: true,
          evaluateFrom: 'object'
        },
        comparator: '>=',
        fallbackBehavior: 'deny'
      });
      
      const result = arbiter.check('user:alice', 'majority_balance_check', 'feature:premium', { includeMeta: true });
      assert.equal(result.possibility, 1);
      
      // Should use majority-focused aggregation (not simply max or min)
      assert.ok(result.meta.allow.leftValue > 1000);
      assert.ok(result.meta.allow.leftValue < 3000); // Not the max value
    });

    it('supports optimistic aggregation', () => {
      const arbiter = setupRelationalComparatorTestGraph();
      
      arbiter.setRelationConfig('has_balance_primary', { type: 'direct' });
      arbiter.setRelationConfig('has_balance_secondary', { type: 'direct' });
      arbiter.addRelation('user:alice', 'has_balance_primary', 'feature:premium', { value: 2000 });
      arbiter.addRelation('user:alice', 'has_balance_secondary', 'feature:premium', { value: 500 });
      
      arbiter.setRelationConfig('optimistic_balance_check', {
        type: 'relational_comparator',
        left: {
          rule: {
            union: {
              rules: [
                { type: 'direct', relation: 'has_balance_primary' },
                { type: 'direct', relation: 'has_balance_secondary' }
              ],
              aggregator: 'optimistic'
            }
          },
          extractValue: true,
          aggregation: 'optimistic',  // Add this for RelationalComparatorRule
          aggregator: 'optimistic',  // Optimistic - weights higher values more
          decayRate: 0,
          decayFunction: 'rational'
        },
        right: {
          rule: {
            type: 'direct',
            relation: 'has_price',
            evaluateFrom: 'object'
          },
          extractValue: true,
          evaluateFrom: 'object'
        },
        comparator: '>=',
        fallbackBehavior: 'deny'
      });
      
      const result = arbiter.check('user:alice', 'optimistic_balance_check', 'feature:premium', { includeMeta: true });
      assert.equal(result.possibility, 1);
      
      // Should bias toward higher values
      assert.ok(result.meta.allow.leftValue > 1250); // Weighted toward higher values
    });

    it('supports custom OWA weights', () => {
      const arbiter = setupRelationalComparatorTestGraph();
      
      arbiter.setRelationConfig('has_balance_primary', { type: 'direct' });
      arbiter.setRelationConfig('has_balance_secondary', { type: 'direct' });
      arbiter.addRelation('user:alice', 'has_balance_primary', 'feature:premium', { value: 2000 });
      arbiter.addRelation('user:alice', 'has_balance_secondary', 'feature:premium', { value: 1500 });
      
      arbiter.setRelationConfig('custom_weighted_check', {
        type: 'relational_comparator',
        left: {
          rule: {
            union: {
              rules: [
                { type: 'direct', relation: 'has_balance_primary' },
                { type: 'direct', relation: 'has_balance_secondary' }
              ],
              aggregator: 'custom'
            }
          },
          extractValue: true,
          aggregation: 'custom',  // Add this for RelationalComparatorRule
          aggregator: 'custom',
          owaWeights: [0.8, 0.2]  // 80% weight on highest, 20% on second
        },
        right: {
          rule: {
            type: 'direct',
            relation: 'has_price',
            evaluateFrom: 'object'
          },
          extractValue: true,
          evaluateFrom: 'object'
        },
        comparator: '>=',
        fallbackBehavior: 'deny'
      });
      
      const result = arbiter.check('user:alice', 'custom_weighted_check', 'feature:premium', { includeMeta: true });
      assert.equal(result.possibility, 1);
      
      // Should calculate: (0.8 * 2000) + (0.2 * 1500) = 1900
      assert.ok(Math.abs(result.meta.allow.leftValue - 1900) < 50); // Allow some precision variance
    });


    it('includes aggregation metadata in results', () => {
      const arbiter = setupRelationalComparatorTestGraph();
      
      arbiter.setRelationConfig('has_balance_primary', { type: 'direct' });
      arbiter.setRelationConfig('has_balance_secondary', { type: 'direct' });
      arbiter.addRelation('user:alice', 'has_balance_primary', 'feature:basic', { value: 2000 });
      arbiter.addRelation('user:alice', 'has_balance_secondary', 'feature:basic', { value: 800 });
      
      arbiter.setRelationConfig('metadata_test_check', {
        type: 'relational_comparator',
        left: {
          rule: {
            union: {
              rules: [
                { type: 'direct', relation: 'has_balance_primary' },
                { type: 'direct', relation: 'has_balance_secondary' }
              ],
              aggregator: 'majority'
            }
          },
          extractValue: true,
          aggregator: 'majority'
        },
        right: {
          rule: {
            type: 'direct',
            relation: 'has_price',
            evaluateFrom: 'object'
          },
          extractValue: true,
          evaluateFrom: 'object'
        },
        comparator: '>=',
        fallbackBehavior: 'deny'
      });
      
      const result = arbiter.check('user:alice', 'metadata_test_check', 'feature:basic', { includeMeta: true });
      assert.equal(result.possibility, 1);
      
      // Check that aggregation metadata is included in the result
      assert.ok(result.meta.allow);
      assert.equal(result.meta.allow.type, 'relational_comparator');
      // The aggregation metadata should be available in the internal processing
      // (though not necessarily exposed in the final result structure)
    });
  });

  describe('Comparison Operators', () => {
    it('supports all comparison operators', () => {
      const arbiter = setupRelationalComparatorTestGraph();
      
      // Test exact equality
      arbiter.addNode('user:exact', 'user');
      arbiter.addNode('feature:exact', 'feature');
      arbiter.addNode('account:exact', 'account');
      arbiter.addNode('price:exact', 'price');
      
      arbiter.addRelation('user:exact', 'has_balance', 'feature:exact', { value: 1000 });
      arbiter.addRelation('feature:exact', 'has_price', 'feature:exact', { value: 1000 });
      
      // Test == operator
      arbiter.setRelationConfig('exact_match', {
        type: 'relational_comparator',
        left: {
          rule: {
            type: 'direct',
            relation: 'has_balance'
          },
          extractValue: true
        },
        right: {
          rule: {
            type: 'direct',
            relation: 'has_price',
            evaluateFrom: 'object'
          },
          extractValue: true,
          evaluateFrom: 'object'
        },
        comparator: '==',
        fallbackBehavior: 'deny'
      });
      
      const result1 = arbiter.check('user:exact', 'exact_match', 'feature:exact');
      assert.ok(result1.possibility > 0.5, `Expected reasonable possibility for exact match, got ${result1.possibility}`);
      
      // Test != operator
      arbiter.setRelationConfig('not_equal', {
        type: 'relational_comparator',
        left: {
          rule: {
            type: 'direct',
            relation: 'has_balance'
          },
          extractValue: true
        },
        right: {
          rule: {
            type: 'direct',
            relation: 'has_price',
            evaluateFrom: 'object'
          },
          extractValue: true,
          evaluateFrom: 'object'
        },
        comparator: '!=',
        fallbackBehavior: 'deny'
      });
      
      const result2 = arbiter.check('user:alice', 'not_equal', 'feature:basic');
      assert.equal(result2.possibility, 1); // 1500 != 100
      
      // Test < operator
      arbiter.setRelationConfig('less_than', {
        type: 'relational_comparator',
        left: {
          rule: {
            type: 'direct',
            relation: 'has_price',
            evaluateFrom: 'object'
          },
          extractValue: true
        },
        right: {
          rule: {
            type: 'direct',
            relation: 'has_balance'
          },
          extractValue: true
        },
        comparator: '<',
        fallbackBehavior: 'deny'
      });
      
      const result3 = arbiter.check('user:alice', 'less_than', 'feature:basic');
      assert.equal(result3.possibility, 1); // 100 < 1500
    });
  });


  describe('OWA Sum Operator and Standardized Aggregation', () => {
    it('supports OWA Sum operator for scaling aggregation', () => {
      const arbiter = setupRelationalComparatorTestGraph();
      
      arbiter.setRelationConfig('has_balance_savings', { type: 'direct' });
      // Alice has multiple balances, we want to sum them with scaling
      arbiter.addRelation('user:alice', 'has_balance_savings', 'feature:enterprise', {
        value: 4000  // Increased from 2000 to make total 5500 > enterprise price 5000
      });
      
      arbiter.setRelationConfig('scaled_sum_check', {
        type: 'relational_comparator',
        left: {
          rule: {
            union: {
              rules: [
                { type: 'direct', relation: 'has_balance' },
                { type: 'direct', relation: 'has_balance_savings' }
              ],
              aggregator: 'sum'
            }
          },
          extractValue: true,
          aggregation: 'sum',  // Add this for compatibility with RelationalComparatorRule
          aggregator: 'sum',  // Sum operator - weights don't normalize to 1.0
          decayRate: 0,       // No decay for this test
          decayFunction: 'rational'
        },
        right: {
          rule: {
            type: 'direct',
            relation: 'has_price',
            evaluateFrom: 'object'
          },
          extractValue: true,
          evaluateFrom: 'object',
          decayRate: 0,
          decayFunction: 'rational'
        },
        comparator: '>=',
        fallbackBehavior: 'deny'
      });
      
      const result = arbiter.check('user:alice', 'scaled_sum_check', 'feature:enterprise', { includeMeta: true });
      assert.equal(result.possibility, 1);
      
      // Should sum all balances (1500 + 4000 = 5500)
      assert.equal(result.meta.allow.leftValue, 5500);
      assert.equal(result.meta.allow.rightValue, 5000);
    });

    it('supports majority aggregation', () => {
      const arbiter = setupRelationalComparatorTestGraph();
      
      arbiter.setRelationConfig('has_balance_primary', { type: 'direct' });
      arbiter.setRelationConfig('has_balance_secondary', { type: 'direct' });
      arbiter.setRelationConfig('has_balance_tertiary', { type: 'direct' });
      // Add multiple balance relations to Alice
      arbiter.addRelation('user:alice', 'has_balance_primary', 'feature:premium', { value: 2500 });
      arbiter.addRelation('user:alice', 'has_balance_secondary', 'feature:premium', { value: 800 });
      arbiter.addRelation('user:alice', 'has_balance_tertiary', 'feature:premium', { value: 1200 });
      
      arbiter.setRelationConfig('majority_balance_check', {
        type: 'relational_comparator',
        left: {
          rule: {
            union: {
              rules: [
                { type: 'direct', relation: 'has_balance_primary' },
                { type: 'direct', relation: 'has_balance_secondary' },
                { type: 'direct', relation: 'has_balance_tertiary' }
              ],
              aggregator: 'majority'
            }
          },
          extractValue: true,
          aggregator: 'majority',  // Add this for RelationalComparatorRule
          aggregator: 'majority'  // Majority aggregation - focuses on typical values
        },
        right: {
          rule: {
            type: 'direct',
            relation: 'has_price',
            evaluateFrom: 'object'
          },
          extractValue: true,
          evaluateFrom: 'object'
        },
        comparator: '>=',
        fallbackBehavior: 'deny'
      });
      
      const result = arbiter.check('user:alice', 'majority_balance_check', 'feature:premium', { includeMeta: true });
      assert.equal(result.possibility, 1);
      
      // Should use majority-focused aggregation (not simply max or min)
      assert.ok(result.meta.allow.leftValue > 1000);
      assert.ok(result.meta.allow.leftValue < 3000); // Not the max value
    });

    it('supports optimistic aggregation', () => {
      const arbiter = setupRelationalComparatorTestGraph();
      
      arbiter.setRelationConfig('has_balance_primary', { type: 'direct' });
      arbiter.setRelationConfig('has_balance_secondary', { type: 'direct' });
      arbiter.addRelation('user:alice', 'has_balance_primary', 'feature:premium', { value: 2000 });
      arbiter.addRelation('user:alice', 'has_balance_secondary', 'feature:premium', { value: 500 });
      
      arbiter.setRelationConfig('optimistic_balance_check', {
        type: 'relational_comparator',
        left: {
          rule: {
            union: {
              rules: [
                { type: 'direct', relation: 'has_balance_primary' },
                { type: 'direct', relation: 'has_balance_secondary' }
              ],
              aggregator: 'optimistic'
            }
          },
          extractValue: true,
          aggregation: 'optimistic',  // Add this for RelationalComparatorRule
          aggregator: 'optimistic',  // Optimistic - weights higher values more
          decayRate: 0,
          decayFunction: 'rational'
        },
        right: {
          rule: {
            type: 'direct',
            relation: 'has_price',
            evaluateFrom: 'object'
          },
          extractValue: true,
          evaluateFrom: 'object'
        },
        comparator: '>=',
        fallbackBehavior: 'deny'
      });
      
      const result = arbiter.check('user:alice', 'optimistic_balance_check', 'feature:premium', { includeMeta: true });
      assert.equal(result.possibility, 1);
      
      // Should bias toward higher values
      assert.ok(result.meta.allow.leftValue > 1250); // Weighted toward higher values
    });

    it('supports custom OWA weights', () => {
      const arbiter = setupRelationalComparatorTestGraph();
      
      arbiter.setRelationConfig('has_balance_primary', { type: 'direct' });
      arbiter.setRelationConfig('has_balance_secondary', { type: 'direct' });
      arbiter.addRelation('user:alice', 'has_balance_primary', 'feature:premium', { value: 2000 });
      arbiter.addRelation('user:alice', 'has_balance_secondary', 'feature:premium', { value: 1500 });
      
      arbiter.setRelationConfig('custom_weighted_check', {
        type: 'relational_comparator',
        left: {
          rule: {
            union: {
              rules: [
                { type: 'direct', relation: 'has_balance_primary' },
                { type: 'direct', relation: 'has_balance_secondary' }
              ],
              aggregator: 'custom'
            }
          },
          extractValue: true,
          aggregation: 'custom',  // Add this for RelationalComparatorRule
          aggregator: 'custom',
          owaWeights: [0.8, 0.2]  // 80% weight on highest, 20% on second
        },
        right: {
          rule: {
            type: 'direct',
            relation: 'has_price',
            evaluateFrom: 'object'
          },
          extractValue: true,
          evaluateFrom: 'object'
        },
        comparator: '>=',
        fallbackBehavior: 'deny'
      });
      
      const result = arbiter.check('user:alice', 'custom_weighted_check', 'feature:premium', { includeMeta: true });
      assert.equal(result.possibility, 1);
      
      // Should calculate: (0.8 * 2000) + (0.2 * 1500) = 1900
      assert.ok(Math.abs(result.meta.allow.leftValue - 1900) < 50); // Allow some precision variance
    });


    it('includes aggregation metadata in results', () => {
      const arbiter = setupRelationalComparatorTestGraph();
      
      arbiter.setRelationConfig('has_balance_primary', { type: 'direct' });
      arbiter.setRelationConfig('has_balance_secondary', { type: 'direct' });
      arbiter.addRelation('user:alice', 'has_balance_primary', 'feature:basic', { value: 2000 });
      arbiter.addRelation('user:alice', 'has_balance_secondary', 'feature:basic', { value: 800 });
      
      arbiter.setRelationConfig('metadata_test_check', {
        type: 'relational_comparator',
        left: {
          rule: {
            union: {
              rules: [
                { type: 'direct', relation: 'has_balance_primary' },
                { type: 'direct', relation: 'has_balance_secondary' }
              ],
              aggregator: 'majority'
            }
          },
          extractValue: true,
          aggregator: 'majority'
        },
        right: {
          rule: {
            type: 'direct',
            relation: 'has_price',
            evaluateFrom: 'object'
          },
          extractValue: true,
          evaluateFrom: 'object'
        },
        comparator: '>=',
        fallbackBehavior: 'deny'
      });
      
      const result = arbiter.check('user:alice', 'metadata_test_check', 'feature:basic', { includeMeta: true });
      assert.equal(result.possibility, 1);
      
      // Check that aggregation metadata is included in the result
      assert.ok(result.meta.allow);
      assert.equal(result.meta.allow.type, 'relational_comparator');
      // The aggregation metadata should be available in the internal processing
      // (though not necessarily exposed in the final result structure)
    });
  });
}); 
