/**
 * Tests for EligibilityEvaluator
 */

import { EligibilityFilterDto } from '../types/Eligibility';
import { EligibilityEvaluator, RatingData } from './EligibilityEvaluator';

describe('EligibilityEvaluator', () => {
  let evaluator: EligibilityEvaluator;
  
  beforeEach(() => {
    evaluator = new EligibilityEvaluator();
  });

  describe('DSL Parsing', () => {
    it('should parse simple platform condition', () => {
      const result = evaluator.parseDSL('platform.driving >= 55');
      
      expect(result.logicalOperator).toBe('AND');
      expect(result.conditions).toHaveLength(1);
      expect(result.conditions[0]).toEqual({
        target: 'platform',
        dimension: 'driving',
        operator: '>=',
        expected: 55,
      });
    });

    it('should parse simple external condition', () => {
      const result = evaluator.parseDSL('external.iracing.iRating between 2000 2500');
      
      expect(result.conditions).toHaveLength(1);
      expect(result.conditions[0]).toEqual({
        target: 'external',
        game: 'iracing',
        dimension: 'iRating',
        operator: 'between',
        expected: [2000, 2500],
      });
    });

    it('should parse AND conditions', () => {
      const result = evaluator.parseDSL('platform.driving >= 55 AND external.iracing.iRating >= 2000');
      
      expect(result.logicalOperator).toBe('AND');
      expect(result.conditions).toHaveLength(2);
    });

    it('should parse OR conditions', () => {
      const result = evaluator.parseDSL('platform.driving >= 55 OR external.iracing.iRating between 2000 2500');
      
      expect(result.logicalOperator).toBe('OR');
      expect(result.conditions).toHaveLength(2);
    });

    it('should handle all comparison operators', () => {
      const operators = ['>=', '<=', '>', '<', '=', '!='];
      
      operators.forEach(op => {
        const result = evaluator.parseDSL(`platform.driving ${op} 55`);
        const condition = result.conditions[0];
        expect(condition).toBeDefined();
        if (condition) {
          expect(condition.operator).toBe(op);
        }
      });
    });

    it('should throw on invalid format', () => {
      expect(() => evaluator.parseDSL('invalid format')).toThrow();
    });

    it('should throw on mixed AND/OR', () => {
      expect(() => evaluator.parseDSL('a >= 1 AND b >= 2 OR c >= 3')).toThrow();
    });
  });

  describe('Evaluation', () => {
    const ratingData: RatingData = {
      platform: {
        driving: 65,
        admin: 70,
        trust: 80,
      },
      external: {
        iracing: {
          iRating: 2200,
          safetyRating: 4.5,
        },
        assetto: {
          rating: 85,
        },
      },
    };

    it('should evaluate simple platform condition - pass', () => {
      const filter: EligibilityFilterDto = {
        dsl: 'platform.driving >= 55',
      };
      
      const result = evaluator.evaluate(filter, ratingData);
      
      expect(result.eligible).toBe(true);
      expect(result.reasons).toHaveLength(1);
      expect(result.reasons[0].failed).toBe(false);
    });

    it('should evaluate simple platform condition - fail', () => {
      const filter: EligibilityFilterDto = {
        dsl: 'platform.driving >= 75',
      };
      
      const result = evaluator.evaluate(filter, ratingData);
      
      expect(result.eligible).toBe(false);
      expect(result.reasons[0].failed).toBe(true);
    });

    it('should evaluate external condition with between - pass', () => {
      const filter: EligibilityFilterDto = {
        dsl: 'external.iracing.iRating between 2000 2500',
      };
      
      const result = evaluator.evaluate(filter, ratingData);
      
      expect(result.eligible).toBe(true);
      expect(result.reasons[0].failed).toBe(false);
    });

    it('should evaluate external condition with between - fail', () => {
      const filter: EligibilityFilterDto = {
        dsl: 'external.iracing.iRating between 2500 3000',
      };
      
      const result = evaluator.evaluate(filter, ratingData);
      
      expect(result.eligible).toBe(false);
      expect(result.reasons[0].failed).toBe(true);
    });

    it('should evaluate AND conditions - all pass', () => {
      const filter: EligibilityFilterDto = {
        dsl: 'platform.driving >= 55 AND external.iracing.iRating >= 2000',
      };
      
      const result = evaluator.evaluate(filter, ratingData);
      
      expect(result.eligible).toBe(true);
      expect(result.reasons).toHaveLength(2);
      expect(result.reasons.every(r => !r.failed)).toBe(true);
    });

    it('should evaluate AND conditions - one fails', () => {
      const filter: EligibilityFilterDto = {
        dsl: 'platform.driving >= 55 AND external.iracing.iRating >= 3000',
      };
      
      const result = evaluator.evaluate(filter, ratingData);
      
      expect(result.eligible).toBe(false);
      expect(result.reasons.some(r => r.failed)).toBe(true);
    });

    it('should evaluate OR conditions - at least one passes', () => {
      const filter: EligibilityFilterDto = {
        dsl: 'platform.driving >= 75 OR external.iracing.iRating >= 2000',
      };
      
      const result = evaluator.evaluate(filter, ratingData);
      
      expect(result.eligible).toBe(true);
      expect(result.reasons.filter(r => !r.failed).length).toBeGreaterThanOrEqual(1);
    });

    it('should evaluate OR conditions - all fail', () => {
      const filter: EligibilityFilterDto = {
        dsl: 'platform.driving >= 75 OR external.iracing.iRating >= 3000',
      };
      
      const result = evaluator.evaluate(filter, ratingData);
      
      expect(result.eligible).toBe(false);
      expect(result.reasons.every(r => r.failed)).toBe(true);
    });

    it('should handle missing data gracefully', () => {
      const filter: EligibilityFilterDto = {
        dsl: 'external.missing.value >= 100',
      };
      
      const result = evaluator.evaluate(filter, ratingData);
      
      expect(result.eligible).toBe(false);
      expect(result.reasons[0].failed).toBe(true);
      expect(result.reasons[0].message).toContain('Missing data');
    });

    it('should include metadata with userId', () => {
      const filter: EligibilityFilterDto = {
        dsl: 'platform.driving >= 55',
        context: { userId: 'user-123' },
      };
      
      const result = evaluator.evaluate(filter, ratingData);
      
      expect(result.metadata?.userId).toBe('user-123');
      expect(result.metadata?.filter).toBe('platform.driving >= 55');
    });

    it('should provide explainable reasons', () => {
      const filter: EligibilityFilterDto = {
        dsl: 'platform.driving >= 75',
      };
      
      const result = evaluator.evaluate(filter, ratingData);
      
      expect(result.reasons[0]).toMatchObject({
        target: 'platform.driving',
        operator: '>=',
        expected: 75,
        actual: 65,
        failed: true,
        message: expect.stringContaining('Expected platform.driving >= 75, but got 65'),
      });
    });

    it('should handle all operators correctly', () => {
      const testCases = [
        { dsl: 'platform.driving >= 60', expected: true },
        { dsl: 'platform.driving > 65', expected: false },
        { dsl: 'platform.driving <= 70', expected: true },
        { dsl: 'platform.driving < 65', expected: false },
        { dsl: 'platform.driving = 65', expected: true },
        { dsl: 'platform.driving != 65', expected: false },
      ];
      
      testCases.forEach(({ dsl, expected }) => {
        const result = evaluator.evaluate({ dsl }, ratingData);
        expect(result.eligible).toBe(expected);
      });
    });

    it('should handle decimal values', () => {
      const filter: EligibilityFilterDto = {
        dsl: 'external.iracing.safetyRating >= 4.0',
      };
      
      const result = evaluator.evaluate(filter, ratingData);
      
      expect(result.eligible).toBe(true);
      expect(result.reasons[0].actual).toBe(4.5);
    });
  });

  describe('Summary Generation', () => {
    const ratingData: RatingData = {
      platform: { driving: 65 },
      external: { iracing: { iRating: 2200 } },
    };

    it('should generate summary for AND all pass', () => {
      const result = evaluator.evaluate(
        { dsl: 'platform.driving >= 55 AND external.iracing.iRating >= 2000' },
        ratingData
      );
      
      expect(result.summary).toBe('Eligible: All conditions met (2/2)');
    });

    it('should generate summary for OR at least one pass', () => {
      const result = evaluator.evaluate(
        { dsl: 'platform.driving >= 75 OR external.iracing.iRating >= 2000' },
        ratingData
      );
      
      expect(result.summary).toContain('Eligible: At least one condition met');
    });

    it('should generate summary for AND with failures', () => {
      const result = evaluator.evaluate(
        { dsl: 'platform.driving >= 55 AND external.iracing.iRating >= 3000' },
        ratingData
      );
      
      expect(result.summary).toContain('Not eligible: 1 condition(s) failed');
    });

    it('should generate summary for OR all fail', () => {
      const result = evaluator.evaluate(
        { dsl: 'platform.driving >= 75 OR external.iracing.iRating >= 3000' },
        ratingData
      );
      
      expect(result.summary).toContain('Not eligible: All conditions failed');
    });
  });

  describe('Error Handling', () => {
    it('should handle parsing errors gracefully', () => {
      const result = evaluator.evaluate(
        { dsl: 'invalid syntax here' },
        { platform: {}, external: {} }
      );
      
      expect(result.eligible).toBe(false);
      expect(result.reasons).toHaveLength(0);
      expect(result.summary).toContain('Failed to evaluate filter');
      expect(result.metadata?.error).toBeDefined();
    });

    it('should handle empty DSL', () => {
      const result = evaluator.evaluate(
        { dsl: '' },
        { platform: {}, external: {} }
      );
      
      expect(result.eligible).toBe(false);
    });
  });
});
