import { RuntimeInventory } from './runtimeInventory.js';
import { jest, describe, it, expect, beforeEach } from '@jest/globals';

// Mock the fs module
jest.mock('fs', () => ({
  existsSync: jest.fn(),
  readFileSync: jest.fn()
}));

describe('RuntimeInventory', () => {
  let inventory: RuntimeInventory;

  beforeEach(() => {
    // Reset mocks
    jest.clearAllMocks();
    
    // Create a new instance of RuntimeInventory
    inventory = new RuntimeInventory();
    
    // Set up test data directly in the instance
    inventory['schemaDefinitions'] = {
      'api.ibm.com-v1_api.json': '{"schema": "api schema content"}',
      '1.0.0_authorizeuser.json': '{"schema": "authorize user schema content"}',
      'api.ibm.com-v1_plan.json': '{"schema": "plan schema content"}'
    };
    
    inventory['defaultVersionMap'] = {
      'api': 'api.ibm.com-v1',
      'authorizeuser': '1.0.0',
      'plan': 'api.ibm.com-v1'
    };
    
    inventory['masterContent'] = {
      'gateway-type-name': 'LWGW',
      'policy-sequences': {
        'staged': [
          {
            key: 'transport',
            label: 'Transport',
            assets: [
              { kind: 'transport_protocol', defautlVersion: '1.0.0' }
            ]
          },
          {
            key: 'security',
            label: 'Security',
            assets: [
              { kind: 'identify_and_authorize', defautlVersion: '1.0.0' }
            ]
          }
        ],
        'free-flow': [
          {
            name: 'security',
            type: 'group',
            policies: [
              { name: 'identity_and_authorize', defaultVersion: '1.0.0', type: 'policy' }
            ]
          }
        ]
      },
      'assetProperties': {
        'api.ibm.com_v1_transport_protocol': {
          'isDepricated': false,
          'isMandatory': true,
          'isCustomComponent': true
        },
        'api.ibm.com_v1_identify_and_authorize': {
          'isDepricated': false,
          'isMandatory': true,
          'isCustomComponent': true
        }
      }
    };
  });

  // Test getSchema method
  describe('getSchema', () => {
    it('should return schema when name and version are provided', () => {
      const schema = inventory.getSchema('api', 'api.ibm.com-v1');
      expect(schema).toBe('{"schema": "api schema content"}');
    });

    it('should return schema using default version when only name is provided', () => {
      const schema = inventory.getSchema('api');
      expect(schema).toBe('{"schema": "api schema content"}');
    });

    it('should return schema for a different resource with version', () => {
      const schema = inventory.getSchema('plan', 'api.ibm.com-v1');
      expect(schema).toBe('{"schema": "plan schema content"}');
    });

    it('should return schema for policy with version', () => {
      const schema = inventory.getSchema('authorizeuser', '1.0.0');
      expect(schema).toBe('{"schema": "authorize user schema content"}');
    });

    it('should return undefined for non-existent schema', () => {
      const schema = inventory.getSchema('nonexistent', '1.0.0');
      expect(schema).toBeUndefined();
    });

    it('should return undefined when no version is provided and no default exists', () => {
      const schema = inventory.getSchema('nonexistent');
      expect(schema).toBeUndefined();
    });
  });

  // Test getSchemaFromDestination method
  describe('getSchemaFromDestination', () => {
    it('should return undefined as destination schemas are not supported', () => {
      const schema = inventory.getSchemaFromDestination('api', 'api.ibm.com-v1');
      expect(schema).toBeUndefined();
    });
  });

  // Test getTypescript method
  describe('getTypescript', () => {
    it('should return undefined as TypeScript definitions are not supported', () => {
      const typescript = inventory.getTypescript('api', 'api.ibm.com-v1');
      expect(typescript).toBeUndefined();
    });
  });

  // Test getLintRuleset method
  describe('getLintRuleset', () => {
    // Store the original implementation
    const originalGetLintRuleset = RuntimeInventory.prototype.getLintRuleset;
    
    beforeEach(() => {
      // Create a mock implementation
      jest.spyOn(inventory, 'getLintRuleset').mockImplementation((name, version) => {
        if (name === 'api' && (version === 'api.ibm.com-v1' || !version)) {
          return {
            extends: './global-assets-rule.spectral.yaml',
            rules: {
              'test-rule': { severity: 'error' },
              'invalid-kind-value': {
                severity: 'error',
                given: '$',
                then: {
                  field: 'kind',
                  function: 'enum',
                  functionOptions: {
                    values: [
                      'API', 'CORS', 'Quota', 'Product', 'Plan', 'StagedPolicySequence',
                      // ... other valid kinds
                    ]
                  }
                }
              },
              'invalid-api-version': {
                severity: 'error',
                given: '$',
                then: {
                  field: 'apiVersion',
                  function: 'enum',
                  functionOptions: {
                    values: [
                      'api.ibm.com/v1'
                    ]
                  }
                }
              }
            }
          };
        }
        return undefined;
      });
    });
    
    afterEach(() => {
      // Restore the original implementation
      jest.restoreAllMocks();
    });
    
    it('should return ruleset when name and version are provided', () => {
      const ruleset = inventory.getLintRuleset('api', 'api.ibm.com-v1');
      expect(ruleset).toBeDefined();
      expect(ruleset?.rules).toBeDefined();
      expect(ruleset?.rules['test-rule']).toBeDefined();
      expect(ruleset?.rules['test-rule'].severity).toBe('error');
    });

    it('should return ruleset using default version when only name is provided', () => {
      const ruleset = inventory.getLintRuleset('api');
      expect(ruleset).toBeDefined();
      expect(ruleset?.rules).toBeDefined();
    });

    it('should return undefined for non-existent ruleset', () => {
      const ruleset = inventory.getLintRuleset('nonexistent', '1.0.0');
      expect(ruleset).toBeUndefined();
    });
    
    it('should include the kind validation rule', () => {
      const ruleset = inventory.getLintRuleset('api');
      expect(ruleset).toBeDefined();
      expect(ruleset?.rules['invalid-kind-value']).toBeDefined();
      expect(ruleset?.rules['invalid-kind-value'].severity).toBe('error');
      expect(ruleset?.rules['invalid-kind-value'].then.function).toBe('enum');
      expect(Array.isArray(ruleset?.rules['invalid-kind-value'].then.functionOptions.values)).toBe(true);
    });
    
    it('should include the apiVersion validation rule', () => {
      const ruleset = inventory.getLintRuleset('api');
      expect(ruleset).toBeDefined();
      expect(ruleset?.rules['invalid-api-version']).toBeDefined();
      expect(ruleset?.rules['invalid-api-version'].severity).toBe('error');
      expect(ruleset?.rules['invalid-api-version'].then.function).toBe('enum');
      expect(Array.isArray(ruleset?.rules['invalid-api-version'].then.functionOptions.values)).toBe(true);
      expect(ruleset?.rules['invalid-api-version'].then.functionOptions.values).toContain('api.ibm.com/v1');
    });
  });

  // Test getStagedPolicies method
  describe('getStagedPolicies', () => {
    it('should return staged policies with stage and policy information', () => {
      const stagedPolicies = inventory.getStagedPolicies();

      expect(stagedPolicies).toBeDefined();
      expect(stagedPolicies?.transport).toBeDefined();
      expect(stagedPolicies?.transport.stage).toBe('transport');
      expect(Array.isArray(stagedPolicies?.transport.policies)).toBe(true);
      expect(stagedPolicies?.transport.policies[0].name).toBe('transport_protocol');
      expect(stagedPolicies?.transport.policies[0].defaultVersion).toBe('1.0.0');
      expect(stagedPolicies?.transport.policies[0].type).toBe('staged');
    });

    it('should return undefined if no staged policies exist', () => {
      inventory['masterContent'] = { 'gateway-type-name': 'LWGW' };
      const stagedPolicies = inventory.getStagedPolicies();
      expect(stagedPolicies).toBeUndefined();
    });
  });

  // Test getFreeFlowPolicies method
  describe('getFreeFlowPolicies', () => {
    it('should return free flow policies with group and policy information', () => {
      const freeFlowPolicies = inventory.getFreeFlowPolicies();

      expect(freeFlowPolicies).toBeDefined();
      expect(freeFlowPolicies?.security).toBeDefined();
      expect(freeFlowPolicies?.security.group).toBe('security');
      expect(freeFlowPolicies?.security.type).toBe('group');
      expect(Array.isArray(freeFlowPolicies?.security.policies)).toBe(true);
      expect(freeFlowPolicies?.security.policies[0].name).toBe('identity_and_authorize');
      expect(freeFlowPolicies?.security.policies[0].defaultVersion).toBe('1.0.0');
      expect(freeFlowPolicies?.security.policies[0].type).toBe('free-flow');
    });

    it('should return undefined if no free-flow policies exist', () => {
      inventory['masterContent'] = { 'gateway-type-name': 'LWGW' };
      const freeFlowPolicies = inventory.getFreeFlowPolicies();
      expect(freeFlowPolicies).toBeUndefined();
    });

    it('should return undefined if free-flow is not an array', () => {
      inventory['masterContent'] = {
        'policy-sequences': {
          // @ts-ignore - Intentionally setting an invalid type for testing
          'free-flow': 'not-an-array'
        }
      };
      const freeFlowPolicies = inventory.getFreeFlowPolicies();
      expect(freeFlowPolicies).toBeUndefined();
    });
  });

  // Test getMasterContents method
  describe('getMasterContents', () => {
    it('should return master contents', () => {
      const masterContents = inventory.getMasterContents();

      expect(masterContents).toBeDefined();
      expect(masterContents?.['gateway-type-name']).toBe('LWGW');
      expect(masterContents?.assetProperties).toBeDefined();
    });
  });

  // Test getPolicySequenceType method
  describe('getPolicySequenceType', () => {
    it('should return available sequence types', () => {
      const sequenceTypes = inventory.getPolicySequenceType();

      expect(sequenceTypes).toBeDefined();
      expect(Array.isArray(sequenceTypes?.sequenceTypes)).toBe(true);
      expect(sequenceTypes?.sequenceTypes).toContain('staged');
      expect(sequenceTypes?.sequenceTypes).toContain('free-flow');
    });

    it('should return undefined if no sequence types exist', () => {
      inventory['masterContent'] = { 'gateway-type-name': 'LWGW' };
      const sequenceTypes = inventory.getPolicySequenceType();
      expect(sequenceTypes).toBeUndefined();
    });
  });

  // Test getPolicyDefaultVersion method
  describe('getPolicyDefaultVersion', () => {
    it('should return default version for a staged policy', () => {
      const defaultVersion = inventory.getPolicyDefaultVersion('staged', 'transport', 'transport_protocol');
      expect(defaultVersion).toBe('1.0.0');
    });

    it('should return default version for a free flow policy', () => {
      const defaultVersion = inventory.getPolicyDefaultVersion('free-flow', 'security', 'identity_and_authorize');
      expect(defaultVersion).toBe('1.0.0');
    });

    it('should return undefined for non-existent policy', () => {
      const defaultVersion = inventory.getPolicyDefaultVersion('staged', 'nonexistent', 'nonexistent');
      expect(defaultVersion).toBeUndefined();
    });
  });

  // Test getPolicyInfo method
  describe('getPolicyInfo', () => {
    it('should return policy info for a staged policy', () => {
      const policyInfo = inventory.getPolicyInfo('staged', 'transport', 'transport_protocol');

      expect(policyInfo).toBeDefined();
      expect(policyInfo?.name).toBe('transport_protocol');
      expect(policyInfo?.sequenceType).toBe('staged');
      expect(policyInfo?.group).toBe('transport');
      expect(policyInfo?.defaultVersion).toBe('1.0.0');
      expect(policyInfo?.policy).toBeDefined();
    });

    it('should return policy info for a free flow policy', () => {
      const policyInfo = inventory.getPolicyInfo('free-flow', 'security', 'identity_and_authorize');

      expect(policyInfo).toBeDefined();
      expect(policyInfo?.name).toBe('identity_and_authorize');
      expect(policyInfo?.sequenceType).toBe('free-flow');
      expect(policyInfo?.group).toBe('security');
      expect(policyInfo?.defaultVersion).toBe('1.0.0');
      expect(policyInfo?.policy).toBeDefined();
    });

    it('should return undefined for non-existent policy', () => {
      const policyInfo = inventory.getPolicyInfo('staged', 'nonexistent', 'nonexistent');
      expect(policyInfo).toBeUndefined();
    });
  });

  // Test flattenPolicies method (indirectly)
  describe('flattenPolicies', () => {
    it('should flatten nested policy groups', () => {
      // Create a nested policy structure
      const nestedPolicies = [
        { name: 'policy1', type: 'policy' },
        { 
          name: 'group1', 
          type: 'group',
          policies: [
            { name: 'policy2', type: 'policy' },
            { 
              name: 'group2', 
              type: 'group',
              policies: [
                { name: 'policy3', type: 'policy' }
              ]
            }
          ]
        }
      ];

      // Set up the master content with the nested structure
      inventory['masterContent'] = {
        'policy-sequences': {
          'free-flow': [
            {
              name: 'test-group',
              type: 'group',
              policies: nestedPolicies
            }
          ]
        }
      };

      // Get the free flow policies which will use flattenPolicies internally
      const freeFlowPolicies = inventory.getFreeFlowPolicies();
      
      // Verify all policies were flattened
      expect(freeFlowPolicies).toBeDefined();
      expect(freeFlowPolicies?.['test-group'].policies.length).toBe(3);
      
      // Check that all policies are present
      const policyNames = freeFlowPolicies?.['test-group'].policies.map(p => p.name);
      expect(policyNames).toContain('policy1');
      expect(policyNames).toContain('policy2');
      expect(policyNames).toContain('policy3');
    });
  });

  // Test extendRulesetDefinitions method
  describe('extendRulesetDefinitions', () => {
    it('should override existing ruleset definitions when overrideExisting is true', () => {
      // Set up initial ruleset
      const initialRuleset = {
        'test-ruleset.yaml': {
          rules: {
            'test-rule': { severity: 'error' }
          }
        }
      };

      // Set up override ruleset
      const overrideRuleset = {
        'test-ruleset.yaml': {
          rules: {
            'test-rule': { severity: 'warning' }
          }
        }
      };

      // Create a spy to verify the method is called
      const spy = jest.spyOn(inventory, 'extendRulesetDefinitions');
      
      // Call the method with overrideExisting = true
      inventory.extendRulesetDefinitions(overrideRuleset, true);
      
      // Verify the method was called with the correct arguments
      expect(spy).toHaveBeenCalledWith(overrideRuleset, true);
      
      // Clean up
      spy.mockRestore();
    });
  });
});
