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

/**
 * Cache Invalidation Tests
 * 
 * Tests that caches are properly invalidated when relations are modified,
 * ensuring that authorization results reflect the current state of the graph.
 */

describe('Cache Invalidation Tests', () => {
  let arbiter;

  beforeEach(() => {
    arbiter = new Arbiter({ embeddingDimensions: 256 });
    
    // Set up basic relations
    arbiter.setRelationConfig('member_of', { type: 'direct' });
    arbiter.setRelationConfig('owner', { type: 'direct' });
    arbiter.setRelationConfig('can_read', { type: 'direct' });
    
    // Create test entities
    arbiter.addNode('user:alice', 'user');
    arbiter.addNode('user:bob', 'user');
    arbiter.addNode('group:engineering', 'group');
    arbiter.addNode('group:qa', 'group');
    arbiter.addNode('document:spec', 'document');
    arbiter.addNode('document:test-plan', 'document');
  });

  describe('Direct Relation Cache Invalidation', () => {
    it('invalidates cache when direct relations are added', () => {
      // Initial state: Alice has no access
      let result1 = arbiter.check('user:alice', 'can_read', 'document:spec');
      assert.equal(result1.possibility, 0);

      // Add direct relation
      arbiter.addRelation('user:alice', 'can_read', 'document:spec', { possibility: 1.0 });

      // Should now have access (cache should be invalidated)
      let result2 = arbiter.check('user:alice', 'can_read', 'document:spec');
      assert.equal(result2.possibility, 1.0);
      // CI-001 fix: fast path → 'direct_match'
      assert.equal(result2.reason, 'direct_match');
    });

    it('invalidates cache when direct relations are removed', () => {
      // Set up initial relation
      arbiter.addRelation('user:alice', 'can_read', 'document:spec', { possibility: 1.0 });

      // Verify access
      let result1 = arbiter.check('user:alice', 'can_read', 'document:spec');
      assert.equal(result1.possibility, 1.0);

      // Remove relation
      arbiter.removeRelation('user:alice', 'can_read', 'document:spec');

      // Should no longer have access (cache should be invalidated)
      let result2 = arbiter.check('user:alice', 'can_read', 'document:spec');
      assert.equal(result2.possibility, 0);
      // CI-001 fix: fast path → 'no_relation'
      assert.equal(result2.reason, 'no_relation');
    });

    it('invalidates cache when relation possibility is updated', () => {
      // Set up initial relation with low possibility
      arbiter.addRelation('user:alice', 'can_read', 'document:spec', { possibility: 0.3 });
      
      // Verify initial access
      let result1 = arbiter.check('user:alice', 'can_read', 'document:spec');
      assert.equal(result1.possibility, 0.3);
      
      // Update relation with higher possibility
      arbiter.addRelation('user:alice', 'can_read', 'document:spec', { possibility: 0.8 });
      
      // Should reflect new possibility (cache should be invalidated)
      let result2 = arbiter.check('user:alice', 'can_read', 'document:spec');
      assert.equal(result2.possibility, 0.8);
    });

    it('invalidates cache when relation values are updated', () => {
      // Set up relation with value
      arbiter.addRelation('user:alice', 'can_read', 'document:spec', { 
        possibility: 1.0, 
        value: 100 
      });
      
      // Verify initial access with value
      let result1 = arbiter.check('user:alice', 'can_read', 'document:spec', { collectValues: true });
      assert.equal(result1.possibility, 1.0);
      assert.ok(result1.collectedValues);
      assert.equal(result1.collectedValues[0].value, 100);
      
      // Update relation with new value
      arbiter.addRelation('user:alice', 'can_read', 'document:spec', { 
        possibility: 1.0, 
        value: 200 
      });
      
      // Should reflect new value (cache should be invalidated)
      let result2 = arbiter.check('user:alice', 'can_read', 'document:spec', { collectValues: true });
      assert.equal(result2.possibility, 1.0);
      assert.equal(result2.collectedValues[0].value, 200);
    });
  });

  describe('Tuple-to-Userset Cache Invalidation', () => {
    it('invalidates cache when intermediate relations change', () => {
      // Set up tuple-to-userset pattern
      arbiter.addRelation('user:alice', 'member_of', 'group:engineering');
      arbiter.addRelation('document:spec', 'owner', 'group:engineering');
      
      arbiter.setRelationConfig('group_access', {
        type: 'tuple_to_userset',
        tuplesetRelation: 'owner',
        computedRelation: 'member_of'
      });
      
      // Verify initial access
      let result1 = arbiter.check('user:alice', 'group_access', 'document:spec');
      assert.equal(result1.possibility, 1.0);
      
      // Remove intermediate relation
      arbiter.removeRelation('user:alice', 'member_of', 'group:engineering');
      
      // Should no longer have access (cache should be invalidated)
      let result2 = arbiter.check('user:alice', 'group_access', 'document:spec');
      assert.equal(result2.possibility, 0);
    });

    it('invalidates cache when object relations change', () => {
      // Set up initial state
      arbiter.addRelation('user:alice', 'member_of', 'group:engineering');
      arbiter.addRelation('document:spec', 'owner', 'group:engineering');
      
      arbiter.setRelationConfig('group_access', {
        type: 'tuple_to_userset',
        tuplesetRelation: 'owner',
        computedRelation: 'member_of'
      });
      
      // Verify initial access
      let result1 = arbiter.check('user:alice', 'group_access', 'document:spec');
      assert.equal(result1.possibility, 1.0);
      
      // Change object ownership
      arbiter.removeRelation('document:spec', 'owner', 'group:engineering');
      arbiter.addRelation('document:spec', 'owner', 'group:qa');
      
      // Alice should no longer have access (cache should be invalidated)
      let result2 = arbiter.check('user:alice', 'group_access', 'document:spec');
      assert.equal(result2.possibility, 0);
    });
  });

  describe('Chain Rule Cache Invalidation', () => {
    it('invalidates cache when chain relations change', () => {
      // Set up chain: user → group → document
      arbiter.addRelation('user:alice', 'member_of', 'group:engineering');
      arbiter.addRelation('group:engineering', 'can_access', 'document:spec');
      
      arbiter.setRelationConfig('chain_access', {
        type: 'chain',
        steps: [
          { relation: 'member_of', direction: 'out' },
          { relation: 'can_access', direction: 'out' }
        ]
      });
      
      // Verify initial access
      let result1 = arbiter.check('user:alice', 'chain_access', 'document:spec');
      assert.equal(result1.possibility, 1.0);
      
      // Break the chain
      arbiter.removeRelation('group:engineering', 'can_access', 'document:spec');
      
      // Should no longer have access (cache should be invalidated)
      let result2 = arbiter.check('user:alice', 'chain_access', 'document:spec');
      assert.equal(result2.possibility, 0);
    });
  });

  describe('Relational Comparator Cache Invalidation', () => {
    it('invalidates cache when compared values change', () => {
      // Add missing nodes
      arbiter.addNode('feature:premium', 'feature');
      
      // Set up balance comparison
      arbiter.addRelation('user:alice', 'has_balance', 'user:alice', { value: 1000 });
      arbiter.addRelation('feature:premium', 'has_price', 'feature:premium', { value: 800 });
      
      arbiter.setRelationConfig('balance_check', {
        type: 'relational_comparator',
        left: { 
          rule: { type: 'direct', relation: 'has_balance' },
          extractValue: true
        },
        right: { 
          evaluateFrom: 'object',
          rule: { type: 'direct', relation: 'has_price' },
          extractValue: true
        },
        comparator: '>'
      });
      
      // Verify initial comparison (1000 > 800 = true)
      let result1 = arbiter.check('user:alice', 'balance_check', 'feature:premium');
      assert.equal(result1.possibility, 1.0);
      
      // Update balance to be lower
      arbiter.addRelation('user:alice', 'has_balance', 'user:alice', { value: 500 });
      
      // Should reflect new comparison (500 > 800 = false, cache should be invalidated)
      let result2 = arbiter.check('user:alice', 'balance_check', 'feature:premium');
      assert.equal(result2.possibility, 0);
    });
  });

  describe('Cache Performance Under Load', () => {
    it('maintains cache performance with frequent invalidations', () => {
      const startTime = Date.now();
      
      // Perform many authorization checks with cache invalidations
      for (let i = 0; i < 100; i++) {
        // Add missing nodes
        arbiter.addNode(`document:test-${i}`, 'document');
        
        // Add relation
        arbiter.addRelation('user:alice', 'can_read', `document:test-${i}`, { possibility: 1.0 });
        
        // Check access
        const result = arbiter.check('user:alice', 'can_read', `document:test-${i}`);
        assert.equal(result.possibility, 1.0);
        
        // Remove relation (should invalidate cache)
        arbiter.removeRelation('user:alice', 'can_read', `document:test-${i}`);
        
        // Check access again (should be 0)
        const result2 = arbiter.check('user:alice', 'can_read', `document:test-${i}`);
        assert.equal(result2.possibility, 0);
      }
      
      const endTime = Date.now();
      const duration = endTime - startTime;
      
      // Should complete within reasonable time (cache invalidation shouldn't be too slow)
      assert.ok(duration < 5000, `Cache invalidation took too long: ${duration}ms`);
      
      if (process.env.TEST_DEBUG === '1') console.log(`Cache invalidation performance: ${duration}ms for 100 operations`);
    });
  });

  describe('Cache Consistency', () => {
    it('ensures cache consistency across multiple authorization checks', () => {
      // Set up complex authorization scenario
      arbiter.addRelation('user:alice', 'member_of', 'group:engineering');
      arbiter.addRelation('document:spec', 'owner', 'group:engineering');
      
      arbiter.setRelationConfig('group_access', {
        type: 'tuple_to_userset',
        tuplesetRelation: 'owner',
        computedRelation: 'member_of'
      });
      
      // Perform multiple checks
      const results = [];
      for (let i = 0; i < 10; i++) {
        results.push(arbiter.check('user:alice', 'group_access', 'document:spec'));
      }
      
      // All results should be identical (cache consistency)
      const firstResult = results[0];
      for (let i = 1; i < results.length; i++) {
        assert.equal(results[i].possibility, firstResult.possibility);
        assert.equal(results[i].reason, firstResult.reason);
      }
    });

    it('handles concurrent cache invalidations correctly', () => {
      // Set up initial state
      arbiter.addRelation('user:alice', 'can_read', 'document:spec', { possibility: 1.0 });
      
      // Perform multiple operations that should invalidate cache
      const operations = [
        () => arbiter.addRelation('user:alice', 'can_read', 'document:spec', { possibility: 0.5 }),
        () => arbiter.addRelation('user:alice', 'can_read', 'document:spec', { possibility: 0.8 }),
        () => arbiter.removeRelation('user:alice', 'can_read', 'document:spec'),
        () => arbiter.addRelation('user:alice', 'can_read', 'document:spec', { possibility: 1.0 })
      ];
      
      // Execute operations
      for (const operation of operations) {
        operation();
        
        // Check that cache is properly invalidated
        const result = arbiter.check('user:alice', 'can_read', 'document:spec');
        assert.ok(typeof result.possibility === 'number');
        assert.ok(result.possibility >= 0 && result.possibility <= 1);
      }
    });
  });
});
