import { ComputedRule } from '../../src/authorization/rules/ComputedRule.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('ComputedRule - Recursive Authorization Delegation', () => {
  let computedRule;
  let arbiter;
  let ruleEvaluator;

  // Helper function to evaluate rules
  function evaluateRule(userKey, objectKey, rule, visited = new Set(), currentRelation = null, options = {}) {
    const userId = arbiter.nodeIdByKey.get(userKey);
    const objectId = arbiter.nodeIdByKey.get(objectKey);
    const finalOptions = { includeMeta: true, ...options };
    return computedRule._evaluateRule(userId, userKey, objectId, objectKey, rule, visited, currentRelation, finalOptions);
  }

  beforeEach(() => {
    // Create fresh arbiter for each test
    arbiter = new Arbiter({ embeddingDimensions: 256 });
    ruleEvaluator = new RuleEvaluator(arbiter);
    computedRule = new ComputedRule(arbiter);
    
    // 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('project:secret', 'project');
    arbiter.addNode('project:public', 'project');
    arbiter.addNode('document:classified', 'document');
    arbiter.addNode('document:public', 'document');
    
    // Configure relations
    arbiter.setRelationConfig('member_of', { type: 'direct' });
    arbiter.setRelationConfig('can_access', { type: 'direct' });
    arbiter.setRelationConfig('can_read', { type: 'direct' });
    arbiter.setRelationConfig('can_write', { type: 'direct' });
    arbiter.setRelationConfig('owns', { type: 'direct' });
  });

  describe('Basic Computed Relation Evaluation', () => {
    it('delegates to authorization checker for simple relations', () => {
      // Alice can access project:secret directly
      arbiter.addRelation('user:alice', 'can_access', 'project:secret', { 
        value: 1.0, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      
      const rule = { 
        type: 'computed', 
        relation: 'can_access'
      };
      
      const result = evaluateRule('user:alice', 'project:secret', rule);

      assert.ok(result.possibility > 0.9, `Expected high possibility, got ${result.possibility}`);
      // CI-001 fix: computed rule delegates to fast path → 'direct_match'
      assert.strictEqual(result.reason, 'direct_match');
      assert.ok(result.meta, 'Expected meta information');
      assert.strictEqual(result.meta.ruleType, 'computed');
      assert.strictEqual(result.meta.computedRelation, 'can_access');
      assert.strictEqual(result.meta.delegated, true);
    });

    it('handles denied access through computed relations', () => {
      // Bob has no access to project:secret
      const rule = {
        type: 'computed',
        relation: 'can_access'
      };

      const result = evaluateRule('user:bob', 'project:secret', rule);

      assert.strictEqual(result.possibility, 0, 'Expected no access');
      // CI-001 fix: fast path → 'no_relation'
      assert.strictEqual(result.reason, 'no_relation');
    });

    it('propagates collected values from delegated evaluation', () => {
      // Alice can access project:secret with a value
      arbiter.addRelation('user:alice', 'can_access', 'project:secret', { 
        value: 100, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      
      const rule = { 
        type: 'computed', 
        relation: 'can_access'
      };
      
      const result = evaluateRule('user:alice', 'project:secret', rule);
      
      assert.ok(result.possibility > 0.9, `Expected high possibility, got ${result.possibility}`);
      // The collectedValues should be propagated from the delegated evaluation
      assert.ok(Array.isArray(result.collectedValues), 'Expected collectedValues array');
    });
  });

  describe('Complex Authorization Scenarios', () => {
    it('handles team membership through computed relations', () => {
      // Alice is member of engineering team, team can access project
      arbiter.addRelation('user:alice', 'member_of', 'team:engineering', { 
        value: 1.0, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      arbiter.addRelation('team:engineering', 'can_access', 'project:secret', { 
        value: 1.0, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      
      // Configure can_access to use chain rule for team membership
      arbiter.setRelationConfig('can_access', { 
        type: 'chain',
        steps: [
          { relation: 'member_of', direction: 'out' },
          { relation: 'can_access', direction: 'out' }
        ]
      });
      
      const rule = { 
        type: 'computed', 
        relation: 'can_access'
      };
      
      const result = evaluateRule('user:alice', 'project:secret', rule);
      
      assert.ok(result.possibility > 0.9, `Expected high possibility through team membership, got ${result.possibility}`);
    });

    it('handles nested computed relations', () => {
      // Alice can read documents that she can access
      arbiter.addRelation('user:alice', 'can_access', 'document:classified', { 
        value: 1.0, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      
      // Configure can_read to use computed relation
      arbiter.setRelationConfig('can_read', { 
        type: 'computed',
        relation: 'can_access'
      });
      
      const rule = { 
        type: 'computed', 
        relation: 'can_read'
      };
      
      const result = evaluateRule('user:alice', 'document:classified', rule);
      
      assert.ok(result.possibility > 0.9, `Expected high possibility through nested computation, got ${result.possibility}`);
    });
  });

  describe('Performance and Optimization', () => {
    it('tracks evaluation performance', () => {
      arbiter.addRelation('user:alice', 'can_access', 'project:secret', { 
        value: 1.0, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      
      const rule = { 
        type: 'computed', 
        relation: 'can_access'
      };
      
      const result = evaluateRule('user:alice', 'project:secret', rule, new Set(), null, { trackEvaluation: true });
      
      assert.ok(result.evaluation, 'Expected evaluation tracking');
      assert.ok(result.evaluation.evaluationStarted, 'Expected start time');
      assert.ok(result.evaluation.evaluationCompleted, 'Expected completion time');
      assert.ok(result.evaluation.evaluationDuration >= 0, 'Expected positive duration');
      assert.strictEqual(result.evaluation.type, 'computed');
      assert.strictEqual(result.evaluation.computedRelation, 'can_access');
    });

  });

  describe('Error Handling and Edge Cases', () => {
    it('handles missing relation configuration', () => {
      const rule = { 
        type: 'computed', 
        relation: 'nonexistent_relation'
      };
      
      const result = evaluateRule('user:alice', 'project:secret', rule);
      
      assert.strictEqual(result.possibility, 0, 'Expected no access for missing relation');
      assert.strictEqual(result.reason, 'no_config');
    });

    it('handles circular dependencies gracefully', () => {
      // Set up circular dependency: can_access -> can_read -> can_access
      arbiter.setRelationConfig('can_access', { 
        type: 'computed',
        relation: 'can_read'
      });
      arbiter.setRelationConfig('can_read', { 
        type: 'computed',
        relation: 'can_access'
      });
      
      const rule = { 
        type: 'computed', 
        relation: 'can_access'
      };
      
      const result = evaluateRule('user:alice', 'project:secret', rule);
      
      // Should handle circular dependency without infinite recursion
      assert.ok(result.possibility >= 0, 'Expected valid result despite circular dependency');
      assert.ok(result.possibility <= 1, 'Expected valid possibility range');
    });

    it('preserves visited set for cycle detection', () => {
      const visited = new Set();
      visited.add('user:alice:can_access:project:secret');
      
      const rule = { 
        type: 'computed', 
        relation: 'can_access'
      };
      
      const result = evaluateRule('user:alice', 'project:secret', rule, visited);
      
      // Should respect the visited set to prevent cycles
      assert.ok(result.possibility >= 0, 'Expected valid result with visited set');
    });
  });

  describe('Value Context and Metadata', () => {
    it('preserves value context through delegation', () => {
      arbiter.addRelation('user:alice', 'can_access', 'project:secret', { 
        value: 100, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      
      const valueContext = { someValue: 42 };
      const rule = { 
        type: 'computed', 
        relation: 'can_access'
      };
      
      const result = evaluateRule('user:alice', 'project:secret', rule, new Set(), null, { 
        valueContext,
        trackEvaluation: true 
      });
      
      assert.ok(result.possibility > 0.9, 'Expected successful evaluation');
      assert.ok(result.evaluation, 'Expected evaluation tracking');
    });

    it('handles different relation types through delegation', () => {
      // Test with different relation types that might be configured
      const relations = ['can_access', 'can_read', 'can_write', 'owns'];
      
      for (const relation of relations) {
        arbiter.addRelation('user:alice', relation, 'project:secret', { 
          value: 1.0, 
          possibility: 1.0,
          changed_last_at: Date.now()
        });
        
        const rule = { 
          type: 'computed', 
          relation: relation
        };
        
        const result = evaluateRule('user:alice', 'project:secret', rule);
        
        assert.ok(result.possibility > 0.9, `Expected high possibility for ${relation}, got ${result.possibility}`);
        assert.strictEqual(result.meta.computedRelation, relation);
      }
    });
  });

  describe('Integration with Other Rules', () => {
    it('works with logical operators', () => {
      // Alice can access through multiple paths
      arbiter.addRelation('user:alice', 'can_access', 'project:secret', { 
        value: 1.0, 
        possibility: 0.8,
        changed_last_at: Date.now()
      });
      arbiter.addRelation('user:alice', 'owns', 'project:secret', { 
        value: 1.0, 
        possibility: 0.9,
        changed_last_at: Date.now()
      });
      
      // Configure can_access to use union of direct and computed relations
      arbiter.setRelationConfig('can_access', { 
        type: 'logical',
        union: {
          rules: [
            { type: 'direct' },
            { type: 'computed', relation: 'owns' }
          ]
        }
      });
      
      const rule = { 
        type: 'computed', 
        relation: 'can_access'
      };
      
      const result = evaluateRule('user:alice', 'project:secret', rule);
      
      assert.ok(result.possibility > 0.8, `Expected high possibility through union, got ${result.possibility}`);
    });

    it('handles complex nested rule configurations', () => {
      // Set up a complex scenario: user -> team -> project access
      arbiter.addRelation('user:alice', 'member_of', 'team:engineering', { 
        value: 1.0, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      arbiter.addRelation('team:engineering', 'can_access', 'project:secret', { 
        value: 1.0, 
        possibility: 1.0,
        changed_last_at: Date.now()
      });
      
      // Configure a complex rule that combines multiple approaches
      arbiter.setRelationConfig('can_access', { 
        type: 'logical',
        union: {
          rules: [
            { type: 'direct' },
            { 
              type: 'chain',
              steps: [
                { relation: 'member_of', direction: 'out' },
                { relation: 'can_access', direction: 'out' }
              ]
            }
          ]
        }
      });
      
      const rule = { 
        type: 'computed', 
        relation: 'can_access'
      };
      
      const result = evaluateRule('user:alice', 'project:secret', rule);
      
      assert.ok(result.possibility > 0.9, `Expected high possibility through complex rule, got ${result.possibility}`);
    });
  });
});
