/**
 * Unit tests for HAZOP AI Service
 * These tests validate the core logic without requiring API calls
 */

const { calculateRiskScore } = require('./hazop-ai-service');

describe('HAZOP AI Service - Unit Tests', () => {
  
  describe('calculateRiskScore', () => {
    
    test('should calculate correct risk score and level for Low risk (score < 6)', () => {
      const result1 = calculateRiskScore(1, 1);
      expect(result1.score).toBe(1);
      expect(result1.level).toBe('Low');
      
      const result2 = calculateRiskScore(2, 2);
      expect(result2.score).toBe(4);
      expect(result2.level).toBe('Low');
      
      const result3 = calculateRiskScore(1, 5);
      expect(result3.score).toBe(5);
      expect(result3.level).toBe('Low');
    });
    
    test('should calculate correct risk score and level for Medium risk (6 <= score <= 11)', () => {
      const result1 = calculateRiskScore(2, 3);
      expect(result1.score).toBe(6);
      expect(result1.level).toBe('Medium');
      
      const result2 = calculateRiskScore(3, 3);
      expect(result2.score).toBe(9);
      expect(result2.level).toBe('Medium');
      
      const result3 = calculateRiskScore(2, 5);
      expect(result3.score).toBe(10);
      expect(result3.level).toBe('Medium');
    });
    
    test('should calculate correct risk score and level for High risk (12 <= score <= 14)', () => {
      const result1 = calculateRiskScore(3, 4);
      expect(result1.score).toBe(12);
      expect(result1.level).toBe('High');
      
      const result2 = calculateRiskScore(4, 3);
      expect(result2.score).toBe(12);
      expect(result2.level).toBe('High');
      
      const result3 = calculateRiskScore(2, 7);
      expect(result3.score).toBe(14);
      expect(result3.level).toBe('High');
    });
    
    test('should calculate correct risk score and level for Critical risk (score >= 15)', () => {
      const result1 = calculateRiskScore(3, 5);
      expect(result1.score).toBe(15);
      expect(result1.level).toBe('Critical');
      
      const result2 = calculateRiskScore(4, 4);
      expect(result2.score).toBe(16);
      expect(result2.level).toBe('Critical');
      
      const result3 = calculateRiskScore(5, 5);
      expect(result3.score).toBe(25);
      expect(result3.level).toBe('Critical');
    });
    
    test('should handle boundary values correctly', () => {
      // Boundary between Low and Medium (5 vs 6)
      const low = calculateRiskScore(1, 5);
      expect(low.score).toBe(5);
      expect(low.level).toBe('Low');
      
      const medium = calculateRiskScore(2, 3);
      expect(medium.score).toBe(6);
      expect(medium.level).toBe('Medium');
      
      // Boundary between Medium and High (11 vs 12)
      const mediumHigh = calculateRiskScore(2, 5);
      expect(mediumHigh.score).toBe(10);
      expect(mediumHigh.level).toBe('Medium');
      
      const high = calculateRiskScore(3, 4);
      expect(high.score).toBe(12);
      expect(high.level).toBe('High');
      
      // Boundary between High and Critical (14 vs 15)
      const highCrit = calculateRiskScore(2, 7);
      expect(highCrit.score).toBe(14);
      expect(highCrit.level).toBe('High');
      
      const critical = calculateRiskScore(3, 5);
      expect(critical.score).toBe(15);
      expect(critical.level).toBe('Critical');
    });
    
    test('should validate Property 7: Risk Score Calculation Correctness', () => {
      // Property 7: For any likelihood value (1-5) and severity value (1-5),
      // the calculated risk score should equal likelihood × severity,
      // and the risk level should be correctly assigned
      
      const testCases = [
        { l: 1, s: 1, expectedScore: 1, expectedLevel: 'Low' },
        { l: 1, s: 2, expectedScore: 2, expectedLevel: 'Low' },
        { l: 1, s: 3, expectedScore: 3, expectedLevel: 'Low' },
        { l: 1, s: 4, expectedScore: 4, expectedLevel: 'Low' },
        { l: 1, s: 5, expectedScore: 5, expectedLevel: 'Low' },
        { l: 2, s: 1, expectedScore: 2, expectedLevel: 'Low' },
        { l: 2, s: 2, expectedScore: 4, expectedLevel: 'Low' },
        { l: 2, s: 3, expectedScore: 6, expectedLevel: 'Medium' },
        { l: 2, s: 4, expectedScore: 8, expectedLevel: 'Medium' },
        { l: 2, s: 5, expectedScore: 10, expectedLevel: 'Medium' },
        { l: 3, s: 1, expectedScore: 3, expectedLevel: 'Low' },
        { l: 3, s: 2, expectedScore: 6, expectedLevel: 'Medium' },
        { l: 3, s: 3, expectedScore: 9, expectedLevel: 'Medium' },
        { l: 3, s: 4, expectedScore: 12, expectedLevel: 'High' },
        { l: 3, s: 5, expectedScore: 15, expectedLevel: 'Critical' },
        { l: 4, s: 1, expectedScore: 4, expectedLevel: 'Low' },
        { l: 4, s: 2, expectedScore: 8, expectedLevel: 'Medium' },
        { l: 4, s: 3, expectedScore: 12, expectedLevel: 'High' },
        { l: 4, s: 4, expectedScore: 16, expectedLevel: 'Critical' },
        { l: 4, s: 5, expectedScore: 20, expectedLevel: 'Critical' },
        { l: 5, s: 1, expectedScore: 5, expectedLevel: 'Low' },
        { l: 5, s: 2, expectedScore: 10, expectedLevel: 'Medium' },
        { l: 5, s: 3, expectedScore: 15, expectedLevel: 'Critical' },
        { l: 5, s: 4, expectedScore: 20, expectedLevel: 'Critical' },
        { l: 5, s: 5, expectedScore: 25, expectedLevel: 'Critical' }
      ];
      
      testCases.forEach(({ l, s, expectedScore, expectedLevel }) => {
        const result = calculateRiskScore(l, s);
        expect(result.score).toBe(expectedScore);
        expect(result.level).toBe(expectedLevel);
      });
    });
  });
  
  describe('analyzeNode - Structure Validation', () => {
    
    test('should validate finding structure requirements (Property 6)', () => {
      // Property 6: For any generated HAZOP finding, it should contain all required fields
      const mockFinding = {
        findingRef: 'N-01-F-01',
        parameter: 'Flow',
        guideword: 'No',
        deviation: 'No Flow',
        causes: ['Valve closed', 'Pump failure'],
        consequences: ['Process shutdown', 'Product loss'],
        safeguards: ['Flow indicator', 'Low flow alarm'],
        recommendations: ['Install redundant pump', 'Add automatic shutdown'],
        likelihood: 3,
        severity: 4,
        riskScore: 12,
        riskLevel: 'High',
        accepted: false
      };
      
      // Validate all required fields are present
      expect(mockFinding).toHaveProperty('findingRef');
      expect(mockFinding).toHaveProperty('parameter');
      expect(mockFinding).toHaveProperty('guideword');
      expect(mockFinding).toHaveProperty('deviation');
      expect(mockFinding).toHaveProperty('causes');
      expect(mockFinding).toHaveProperty('consequences');
      expect(mockFinding).toHaveProperty('safeguards');
      expect(mockFinding).toHaveProperty('recommendations');
      expect(mockFinding).toHaveProperty('likelihood');
      expect(mockFinding).toHaveProperty('severity');
      expect(mockFinding).toHaveProperty('riskScore');
      expect(mockFinding).toHaveProperty('riskLevel');
      
      // Validate field types
      expect(typeof mockFinding.findingRef).toBe('string');
      expect(typeof mockFinding.parameter).toBe('string');
      expect(typeof mockFinding.guideword).toBe('string');
      expect(typeof mockFinding.deviation).toBe('string');
      expect(Array.isArray(mockFinding.causes)).toBe(true);
      expect(Array.isArray(mockFinding.consequences)).toBe(true);
      expect(Array.isArray(mockFinding.safeguards)).toBe(true);
      expect(Array.isArray(mockFinding.recommendations)).toBe(true);
      expect(typeof mockFinding.likelihood).toBe('number');
      expect(typeof mockFinding.severity).toBe('number');
      expect(typeof mockFinding.riskScore).toBe('number');
      expect(typeof mockFinding.riskLevel).toBe('string');
      
      // Validate value ranges
      expect(mockFinding.likelihood).toBeGreaterThanOrEqual(1);
      expect(mockFinding.likelihood).toBeLessThanOrEqual(5);
      expect(mockFinding.severity).toBeGreaterThanOrEqual(1);
      expect(mockFinding.severity).toBeLessThanOrEqual(5);
      expect(['Low', 'Medium', 'High', 'Critical']).toContain(mockFinding.riskLevel);
    });
    
    test('should validate guideword coverage requirements (Property 5)', () => {
      // Property 5: For any HAZOP node analysis, the generated findings should cover
      // all 6 IEC 61882 guidewords
      const requiredGuidewords = ['No', 'More', 'Less', 'As Well As', 'Reverse', 'Other Than'];
      
      const mockFindings = [
        { guideword: 'No', parameter: 'Flow' },
        { guideword: 'More', parameter: 'Pressure' },
        { guideword: 'Less', parameter: 'Temperature' },
        { guideword: 'As Well As', parameter: 'Flow' },
        { guideword: 'Reverse', parameter: 'Flow' },
        { guideword: 'Other Than', parameter: 'Composition' }
      ];
      
      const guidewordsFound = mockFindings.map(f => f.guideword);
      const uniqueGuidewords = [...new Set(guidewordsFound)];
      
      // Validate all required guidewords are present
      requiredGuidewords.forEach(gw => {
        expect(uniqueGuidewords).toContain(gw);
      });
      
      expect(uniqueGuidewords.length).toBe(6);
    });
    
    test('should generate correct finding reference format', () => {
      // Finding references should follow the format: {nodeNumber}-F-{index}
      const nodeNumber = 'N-01';
      const findingIndex = 0;
      const expectedRef = `${nodeNumber}-F-${(findingIndex + 1).toString().padStart(2, '0')}`;
      
      expect(expectedRef).toBe('N-01-F-01');
      
      // Test multiple indices
      expect(`N-02-F-${(5).toString().padStart(2, '0')}`).toBe('N-02-F-05');
      expect(`N-03-F-${(12).toString().padStart(2, '0')}`).toBe('N-03-F-12');
    });
  });
  
  describe('generateHazards - Structure Validation', () => {
    
    test('should validate hazard structure requirements (Property 10)', () => {
      // Property 10: For any generated HAZID hazard, it should contain all required fields
      const mockHazard = {
        hazardRef: 'HZD-01',
        category: 'Process',
        description: 'Fire or explosion due to flammable materials',
        cause: 'Ignition source in presence of flammable vapors',
        consequence: 'Multiple fatalities, facility destruction',
        affectedParties: ['Workers', 'Public', 'Environment'],
        safeguards: ['Gas detection system', 'Fire suppression system'],
        recommendations: ['Install redundant gas detectors', 'Conduct fire drills'],
        likelihood: 3,
        severity: 5,
        riskScore: 15,
        riskLevel: 'Critical',
        priority: 'Immediate',
        accepted: false
      };
      
      // Validate all required fields are present
      expect(mockHazard).toHaveProperty('hazardRef');
      expect(mockHazard).toHaveProperty('category');
      expect(mockHazard).toHaveProperty('description');
      expect(mockHazard).toHaveProperty('cause');
      expect(mockHazard).toHaveProperty('consequence');
      expect(mockHazard).toHaveProperty('affectedParties');
      expect(mockHazard).toHaveProperty('safeguards');
      expect(mockHazard).toHaveProperty('recommendations');
      expect(mockHazard).toHaveProperty('likelihood');
      expect(mockHazard).toHaveProperty('severity');
      expect(mockHazard).toHaveProperty('riskScore');
      expect(mockHazard).toHaveProperty('riskLevel');
      expect(mockHazard).toHaveProperty('priority');
      expect(mockHazard).toHaveProperty('accepted');
      
      // Validate field types
      expect(typeof mockHazard.hazardRef).toBe('string');
      expect(typeof mockHazard.category).toBe('string');
      expect(typeof mockHazard.description).toBe('string');
      expect(typeof mockHazard.cause).toBe('string');
      expect(typeof mockHazard.consequence).toBe('string');
      expect(Array.isArray(mockHazard.affectedParties)).toBe(true);
      expect(Array.isArray(mockHazard.safeguards)).toBe(true);
      expect(Array.isArray(mockHazard.recommendations)).toBe(true);
      expect(typeof mockHazard.likelihood).toBe('number');
      expect(typeof mockHazard.severity).toBe('number');
      expect(typeof mockHazard.riskScore).toBe('number');
      expect(typeof mockHazard.riskLevel).toBe('string');
      expect(typeof mockHazard.priority).toBe('string');
      expect(typeof mockHazard.accepted).toBe('boolean');
      
      // Validate value ranges and enums
      expect(mockHazard.likelihood).toBeGreaterThanOrEqual(1);
      expect(mockHazard.likelihood).toBeLessThanOrEqual(5);
      expect(mockHazard.severity).toBeGreaterThanOrEqual(1);
      expect(mockHazard.severity).toBeLessThanOrEqual(5);
      expect(['Low', 'Medium', 'High', 'Critical']).toContain(mockHazard.riskLevel);
      expect(['Process', 'Structural', 'Human', 'Environment', 'External']).toContain(mockHazard.category);
      expect(['Immediate', 'Short-term', 'Long-term']).toContain(mockHazard.priority);
    });
    
    test('should validate priority value validity (Property 11)', () => {
      // Property 11: For any generated HAZID hazard, the priority field should be
      // one of the three valid values: "Immediate", "Short-term", or "Long-term"
      const validPriorities = ['Immediate', 'Short-term', 'Long-term'];
      
      const mockHazards = [
        { priority: 'Immediate' },
        { priority: 'Short-term' },
        { priority: 'Long-term' }
      ];
      
      mockHazards.forEach(hazard => {
        expect(validPriorities).toContain(hazard.priority);
      });
      
      // Test invalid priorities
      const invalidPriorities = ['High', 'Low', 'Medium', 'Urgent', 'Later'];
      invalidPriorities.forEach(priority => {
        expect(validPriorities).not.toContain(priority);
      });
    });
    
    test('should validate HAZID category coverage (Property 8)', () => {
      // Property 8: For any HAZID hazard generation, the generated hazards should
      // include at least one hazard from each of the 5 categories
      const requiredCategories = ['Process', 'Structural', 'Human', 'Environment', 'External'];
      
      const mockHazards = [
        { category: 'Process', description: 'Fire hazard' },
        { category: 'Structural', description: 'Building collapse' },
        { category: 'Human', description: 'Fatigue' },
        { category: 'Environment', description: 'Spill' },
        { category: 'External', description: 'Earthquake' }
      ];
      
      const categoriesFound = mockHazards.map(h => h.category);
      const uniqueCategories = [...new Set(categoriesFound)];
      
      // Validate all required categories are present
      requiredCategories.forEach(cat => {
        expect(uniqueCategories).toContain(cat);
      });
      
      expect(uniqueCategories.length).toBe(5);
    });
    
    test('should validate HAZID minimum hazard count (Property 9)', () => {
      // Property 9: For any valid facility description provided to the hazard
      // generation service, the number of generated hazards should be at least 5
      const mockHazards = [
        { hazardRef: 'HZD-01', category: 'Process' },
        { hazardRef: 'HZD-02', category: 'Structural' },
        { hazardRef: 'HZD-03', category: 'Human' },
        { hazardRef: 'HZD-04', category: 'Environment' },
        { hazardRef: 'HZD-05', category: 'External' }
      ];
      
      expect(mockHazards.length).toBeGreaterThanOrEqual(5);
    });
    
    test('should generate correct hazard reference format', () => {
      // Hazard references should follow the format: HZD-{index}
      const hazardIndex = 0;
      const expectedRef = `HZD-${(hazardIndex + 1).toString().padStart(2, '0')}`;
      
      expect(expectedRef).toBe('HZD-01');
      
      // Test multiple indices
      expect(`HZD-${(5).toString().padStart(2, '0')}`).toBe('HZD-05');
      expect(`HZD-${(12).toString().padStart(2, '0')}`).toBe('HZD-12');
    });
  });
});
