import { RelationalComparatorRule } from '../../src/authorization/rules/RelationalComparatorRule.js';
import { Arbiter } from '../../src/core/Arbiter.js';
import { RuleEvaluator } from '../../src/authorization/RuleEvaluator.js';
import { describe, it, beforeEach } from 'node:test';
import assert from 'node:assert/strict';

describe('RelationalComparatorRule - Real-world Financial & Authorization Scenarios', () => {
  let comparatorRule;
  let arbiter;
  let ruleEvaluator;

  // Helper function to evaluate rules
  function evaluateRule(userKey, objectKey, rule, visited = {}, currentRelation = null, options = {}) {
    const userId = arbiter.resolveNodeId(userKey);
    const objectId = arbiter.resolveNodeId(objectKey);
    const mergedOptions = { collectValues: true, includeMeta: true, ...options };
    return comparatorRule._evaluateRule(userId, userKey, objectId, objectKey, rule, visited, currentRelation, mergedOptions);
  }

  beforeEach(() => {
    // Create fresh arbiter for each test
    arbiter = new Arbiter({ embeddingDimensions: 256 });
    ruleEvaluator = new RuleEvaluator(arbiter);
    comparatorRule = new RelationalComparatorRule(arbiter, ruleEvaluator);
    
    // Set up realistic entities
    arbiter.addNode('user:alice', 'user');
    arbiter.addNode('user:bob', 'user');
    arbiter.addNode('user:charlie', 'user');
    arbiter.addNode('team:engineering', 'team');
    arbiter.addNode('team:marketing', 'team');
    arbiter.addNode('account:alice_checking', 'account');
    arbiter.addNode('account:alice_savings', 'account');
    arbiter.addNode('account:team_budget', 'account');
    arbiter.addNode('feature:premium', 'feature');
    arbiter.addNode('feature:basic', 'feature');
    arbiter.addNode('transaction:large_purchase', 'transaction');
    
    // Configure relations
    arbiter.setRelationConfig('has_balance', { type: 'direct' });
    arbiter.setRelationConfig('has_price', { type: 'direct' });
    arbiter.setRelationConfig('member_of', { type: 'direct' });
    arbiter.setRelationConfig('has_reputation', { type: 'direct' });
    arbiter.setRelationConfig('has_risk_score', { type: 'direct' });
    arbiter.setRelationConfig('has_amount', { type: 'direct' });
    arbiter.setRelationConfig('has_budget', { type: 'direct' });
  });

  describe('Balance Inquiries - Can User Afford Feature?', () => {
    it('allows access when user balance exceeds feature price', () => {
      // Alice has $1000, premium feature costs $800
      arbiter.addRelation('user:alice', 'has_balance', 'feature:premium', { 
        value: 1000, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      arbiter.addRelation('feature:premium', 'has_price', 'feature:premium', { 
        value: 800, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      
      const rule = { 
        type: 'relational_comparator', 
        comparator: '>', 
        left: { 
          rule: { type: 'direct', relation: 'has_balance' },
          extractValue: true,
          ttl: 14 * 24 * 60 * 60 * 1000
        }, 
        right: { 
          evaluateFrom: 'object',
          rule: { type: 'direct', relation: 'has_price' },
          extractValue: true
        } 
      };
      
      const result = evaluateRule('user:alice', 'feature:premium', rule);
      assert.ok(result.possibility > 0.9, `Expected high possibility, got ${result.possibility}`);
      assert.strictEqual(result.reason, 'values_compared_comparison_true');
    });

    it('denies access when user balance is insufficient', () => {
      // Bob has $500, premium feature costs $800
      arbiter.addRelation('user:bob', 'has_balance', 'feature:premium', { 
        value: 500, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      arbiter.addRelation('feature:premium', 'has_price', 'feature:premium', { 
        value: 800, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      
      const rule = { 
        type: 'relational_comparator', 
        comparator: '>', 
        left: { 
          rule: { type: 'direct', relation: 'has_balance' },
          extractValue: true,
          ttl: 14 * 24 * 60 * 60 * 1000
        }, 
        right: { 
          evaluateFrom: 'object',
          rule: { type: 'direct', relation: 'has_price' },
          extractValue: true
        } 
      };
      
      const result = evaluateRule('user:bob', 'feature:premium', rule);
      assert.ok(result.possibility < 0.1, `Expected low possibility, got ${result.possibility}`);
      assert.strictEqual(result.reason, 'values_compared_comparison_false');
    });
  });

  describe('Data Freshness - No Decay', () => {
    it('does not reduce confidence for stale balance data', () => {
      const staleTime = Date.now() - (60 * 60 * 1000);
      
      arbiter.addRelation('user:alice', 'has_balance', 'feature:premium', { 
        value: 1000, 
        possibility: 1.0,
        changed_last_at: staleTime
      });
      arbiter.addRelation('feature:premium', 'has_price', 'feature:premium', { 
        value: 800, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      
      const rule = { 
        type: 'relational_comparator', 
        comparator: '>', 
        left: { 
          rule: { type: 'direct', relation: 'has_balance' },
          extractValue: true,
          ttl: 14 * 24 * 60 * 60 * 1000
        }, 
        right: { 
          evaluateFrom: 'object',
          rule: { type: 'direct', relation: 'has_price' },
          extractValue: true
        } 
      };
      
      const result = evaluateRule('user:alice', 'feature:premium', rule);
      assert.ok(result.possibility > 0.9, `Expected high possibility without decay, got ${result.possibility}`);
    });

    it('treats very stale data the same as fresh data', () => {
      const veryStaleTime = Date.now() - (7 * 24 * 60 * 60 * 1000);
      
      arbiter.addRelation('user:alice', 'has_balance', 'feature:premium', { 
        value: 1000, 
        possibility: 1.0,
        changed_last_at: veryStaleTime
      });
      arbiter.addRelation('feature:premium', 'has_price', 'feature:premium', { 
        value: 800, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      
      const rule = { 
        type: 'relational_comparator', 
        comparator: '>', 
        left: { 
          rule: { type: 'direct', relation: 'has_balance' },
          extractValue: true,
          ttl: 14 * 24 * 60 * 60 * 1000
        }, 
        right: { 
          evaluateFrom: 'object',
          rule: { type: 'direct', relation: 'has_price' },
          extractValue: true
        } 
      };
      
      const result = evaluateRule('user:alice', 'feature:premium', rule);
      assert.ok(result.possibility > 0.9, `Expected high possibility without decay, got ${result.possibility}`);
    });
  });

  describe('Reputation and Risk Scoring', () => {
    it('evaluates user reputation against threshold', () => {
      // Alice has high reputation (0.9), we want to check if it's >= 0.8
      arbiter.addRelation('user:alice', 'has_reputation', 'user:alice', { 
        value: 0.9, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      
      // Create a threshold entity with the minimum required reputation
      arbiter.addNode('threshold:reputation', 'threshold');
      arbiter.addRelation('threshold:reputation', 'has_value', 'threshold:reputation', { 
        value: 0.8, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      
      const rule = { 
        type: 'relational_comparator', 
        comparator: '>=', 
        left: { 
          rule: { type: 'direct', relation: 'has_reputation' },
          extractValue: true
        }, 
        right: { 
          evaluateFrom: 'object',
          rule: { type: 'direct', relation: 'has_value' },
          extractValue: true
        } 
      };
      
      const result = evaluateRule('user:alice', 'threshold:reputation', rule);
      assert.ok(result.possibility > 0.5, `Expected reasonable possibility for reputation check, got ${result.possibility}`);
    });

    it('evaluates risk score with margin of safety', () => {
      // Bob has risk score 0.3, but we want 10% margin of safety
      arbiter.addRelation('user:bob', 'has_risk_score', 'user:bob', { 
        value: 0.3, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      
      const rule = { 
        type: 'relational_comparator', 
        comparator: '<', 
        left: { 
          rule: { type: 'direct', relation: 'has_risk_score' },
          extractValue: true
        }, 
        right: { 
          rule: { type: 'direct', relation: 'has_risk_score' },
          extractValue: false // Use rule possibility as value (0.4 threshold)
        },
        marginOfSafety: 1.1 // 10% margin
      };
      
      const result = evaluateRule('user:bob', 'user:bob', rule);
      // 0.3 < (0.4 * 1.1) = 0.44, so should pass
      assert.ok(result.possibility > 0.8, `Expected high possibility with margin of safety, got ${result.possibility}`);
    });
  });

  describe('Team Budget and Membership', () => {
    it('checks if team has sufficient budget for transaction', () => {
      // Engineering team has $5000 budget, transaction costs $3000
      arbiter.addRelation('team:engineering', 'has_budget', 'transaction:large_purchase', { 
        value: 5000, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      arbiter.addRelation('transaction:large_purchase', 'has_amount', 'transaction:large_purchase', { 
        value: 3000, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      
      const rule = { 
        type: 'relational_comparator', 
        comparator: '>=', 
        left: { 
          rule: { type: 'direct', relation: 'has_budget' },
          extractValue: true
        }, 
        right: { 
          evaluateFrom: 'object',
          rule: { type: 'direct', relation: 'has_amount' },
          extractValue: true
        } 
      };
      
      const result = evaluateRule('team:engineering', 'transaction:large_purchase', rule);
      assert.ok(result.possibility > 0.9, `Expected high possibility for team budget check, got ${result.possibility}`);
    });

    it('handles team membership with nested rules', () => {
      // Alice is member of engineering team, team has budget
      arbiter.addRelation('user:alice', 'member_of', 'team:engineering', { 
        value: 1.0, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      arbiter.addRelation('team:engineering', 'has_budget', 'transaction:large_purchase', { 
        value: 5000, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      arbiter.addRelation('transaction:large_purchase', 'has_amount', 'transaction:large_purchase', { 
        value: 3000, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      
      // This would typically be handled by a chain rule, but we can test the comparator part
      const rule = { 
        type: 'relational_comparator', 
        comparator: '>=', 
        left: { 
          rule: { type: 'direct', relation: 'has_budget' },
          extractValue: true
        }, 
        right: { 
          evaluateFrom: 'object',
          rule: { type: 'direct', relation: 'has_amount' },
          extractValue: true
        } 
      };
      
      const result = evaluateRule('team:engineering', 'transaction:large_purchase', rule);
      assert.ok(result.possibility > 0.9, `Expected high possibility for team budget, got ${result.possibility}`);
    });
  });

  describe('OWA Aggregation of Multiple Values', () => {
    it('aggregates multiple account balances using max', () => {
      arbiter.setRelationConfig('has_balance_primary', { type: 'direct' });
      arbiter.setRelationConfig('has_balance_secondary', { type: 'direct' });
      // Alice has multiple balances, we want the max
      arbiter.addRelation('user:alice', 'has_balance_primary', 'feature:premium', { 
        value: 500, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      arbiter.addRelation('user:alice', 'has_balance_secondary', 'feature:premium', { 
        value: 1500, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      arbiter.addRelation('feature:premium', 'has_price', 'feature:premium', { 
        value: 1000, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      
      const rule = { 
        type: 'relational_comparator', 
        comparator: '>', 
        left: { 
          rule: { 
            union: { 
              rules: [
                { type: 'direct', relation: 'has_balance_primary' },
                { type: 'direct', relation: 'has_balance_secondary' }
              ],
              aggregator: 'max'
            }
          },
          extractValue: true,
          aggregator: 'max' // Use max of all balances
        }, 
        right: { 
          evaluateFrom: 'object',
          rule: { type: 'direct', relation: 'has_price' },
          extractValue: true
        } 
      };
      
      const result = evaluateRule('user:alice', 'feature:premium', rule);
      // Should use max balance (1500) > price (1000)
      assert.ok(result.possibility > 0.9, `Expected high possibility using max aggregation, got ${result.possibility}`);
    });

    it('aggregates multiple account balances using sum', () => {
      arbiter.setRelationConfig('has_balance_primary', { type: 'direct' });
      arbiter.setRelationConfig('has_balance_secondary', { type: 'direct' });
      // Alice has multiple balances, we want total available funds
      arbiter.addRelation('user:alice', 'has_balance_primary', 'feature:premium', { 
        value: 500, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      arbiter.addRelation('user:alice', 'has_balance_secondary', 'feature:premium', { 
        value: 300, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      arbiter.addRelation('feature:premium', 'has_price', 'feature:premium', { 
        value: 1000, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      
      const rule = { 
        type: 'relational_comparator', 
        comparator: '>', 
        left: { 
          rule: { 
            union: { 
              rules: [
                { type: 'direct', relation: 'has_balance_primary' },
                { type: 'direct', relation: 'has_balance_secondary' }
              ],
              aggregator: 'sum'
            }
          },
          extractValue: true,
          aggregator: 'sum' // Use sum of all balances
        }, 
        right: { 
          evaluateFrom: 'object',
          rule: { type: 'direct', relation: 'has_price' },
          extractValue: true
        } 
      };
      
      const result = evaluateRule('user:alice', 'feature:premium', rule);
      // Should use sum balance (800) < price (1000), so should fail
      assert.ok(result.possibility < 0.1, `Expected low possibility using sum aggregation, got ${result.possibility}`);
    });
  });

  describe('Interval Blurring for Uncertain Values', () => {
    it('keeps crisp comparisons without blur', () => {
      const staleTime = Date.now() - (2 * 60 * 60 * 1000);
      arbiter.addRelation('user:alice', 'has_balance', 'feature:premium', { 
        value: 1000, 
        possibility: 0.7, // Reduced confidence due to uncertainty
        changed_last_at: staleTime
      });
      arbiter.addRelation('feature:premium', 'has_price', 'feature:premium', { 
        value: 800, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      
      const rule = { 
        type: 'relational_comparator', 
        comparator: '>', 
        left: { 
          rule: { type: 'direct', relation: 'has_balance' },
          extractValue: true
        }, 
        right: { 
          evaluateFrom: 'object',
          rule: { type: 'direct', relation: 'has_price' },
          extractValue: true
        } 
      };
      
      const result = evaluateRule('user:alice', 'feature:premium', rule);
      assert.equal(result.possibility, 1);
    });
  });

  describe('Edge Cases and Error Handling', () => {
    it('handles missing values gracefully', () => {
      // Alice has no balance relation
      arbiter.addRelation('feature:premium', 'has_price', 'feature:premium', { 
        value: 1000, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      
      const rule = { 
        type: 'relational_comparator', 
        comparator: '>', 
        left: { 
          rule: { type: 'direct', relation: 'has_balance' },
          extractValue: true
        }, 
        right: { 
          evaluateFrom: 'object',
          rule: { type: 'direct', relation: 'has_price' },
          extractValue: true
        },
        fallbackBehavior: 'deny'
      };
      
      const result = evaluateRule('user:alice', 'feature:premium', rule);
      assert.strictEqual(result.possibility, 0, 'Expected 0 possibility for missing balance');
      assert.strictEqual(result.reason, 'left_operand_missing_comparison_false');
    });

    it('handles both operands missing', () => {
      const rule = { 
        type: 'relational_comparator', 
        comparator: '>', 
        left: { 
          rule: { type: 'direct', relation: 'has_balance' },
          extractValue: true
        }, 
        right: { 
          rule: { type: 'direct', relation: 'has_price' },
          extractValue: true
        },
        fallbackBehavior: 'deny'
      };
      
      const result = evaluateRule('user:alice', 'feature:premium', rule);
      assert.strictEqual(result.possibility, 0, 'Expected 0 possibility for missing operands');
      assert.strictEqual(result.reason, 'both_operands_missing_fallback_deny');
    });

    it('handles inequality with missing operand', () => {
      // Missing balance but checking != (should return true)
      arbiter.addRelation('feature:premium', 'has_price', 'feature:premium', { 
        value: 1000, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      
      const rule = { 
        type: 'relational_comparator', 
        comparator: '!=', 
        left: { 
          rule: { type: 'direct', relation: 'has_balance' },
          extractValue: true
        }, 
        right: { 
          evaluateFrom: 'object',
          rule: { type: 'direct', relation: 'has_price' },
          extractValue: true
        }
      };
      
      const result = evaluateRule('user:alice', 'feature:premium', rule);
      assert.strictEqual(result.possibility, 1.0, 'Expected 1.0 possibility for inequality with missing operand');
      assert.strictEqual(result.reason, 'left_operand_missing_inequality_true');
    });
  });

  describe('Performance and Optimization', () => {
    it('supports fast path optimization', () => {
      // Simple case that should use fast path
      arbiter.addRelation('user:alice', 'has_balance', 'feature:premium', { 
        value: 1000, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      arbiter.addRelation('feature:premium', 'has_price', 'feature:premium', { 
        value: 800, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      
      const rule = { 
        type: 'relational_comparator', 
        comparator: '>', 
        left: { 
          rule: { type: 'direct', relation: 'has_balance' },
          extractValue: true
        }, 
        right: { 
          evaluateFrom: 'object',
          rule: { type: 'direct', relation: 'has_price' },
          extractValue: true
        } 
      };
      
      const result = evaluateRule('user:alice', 'feature:premium', rule, {}, null, { fastPath: true });
      assert.ok(result.possibility > 0.9, `Expected high possibility with fast path, got ${result.possibility}`);
    });
  });
});
