/**
 * Copyright IBM Corp. 2024, 2025
 */
import { AssetSchemaValidator } from '../../src/validator/schema-validator.impl.js';
import { GatewayLabels } from '@apic/studio-client-model';
import { SchemaHandler, YamlContent } from '@apic/studio-shared';
import { AssetValidator } from '../../src/service/validation-service.js';

jest.mock('@apic/studio-shared', () => ({
  SchemaHandler: jest.fn().mockImplementation(() => ({
    getSchema: jest.fn().mockReturnValue(JSON.stringify({ type: 'object' })),
  })),
  isValidAsset: jest.fn().mockImplementation((asset: any) => {
    if (asset && asset.kind) {
      return true;
    }
    return false;
  }),
  YamlContent: jest.fn(),
  Logger: {
    createChildLogger: jest.fn().mockReturnValue({
      debug: jest.fn(),
      info: jest.fn(),
      warn: jest.fn(),
      error: jest.fn(),
      log: jest.fn(),
    }),
  },
  Component: {
    Core: 'Core',
  },
}));

jest.mock('@apic/studio-shared', () => ({
  Component: {
    Build: 'Build',
  },

  LogComponent: () => {
    return () => {
      /* noop */
    };
  },

  Logger: {
    error: jest.fn(),
    warn: jest.fn(),
    info: jest.fn(),
  },

  toError: jest.fn((e) => (e instanceof Error ? e : new Error(String(e)))),

  ErrorResponse: jest.fn(),
  Metadata_Ref: jest.fn(),
  SpecObject: jest.fn(),
  UpperCaseKinds: jest.fn(),
  loadYaml: jest.fn((content) => require('js-yaml').load(content)),

  SchemaHandler: jest.fn().mockImplementation(() => ({
    getSchema: jest.fn().mockReturnValue(JSON.stringify({ type: 'object' })),
  })),
  isValidAsset: jest.fn().mockImplementation((asset: any) => {
    if (asset && asset.kind) {
      return true;
    }
    return false;
  }),
  YamlContent: jest.fn(),
}));

jest.mock('../../src/service/validation-service.js', () => ({
  AssetValidator: jest.fn().mockImplementation(() => ({
    validateAssets: jest.fn().mockReturnValue({ valid: true, errors: [] }),
  })),
}));

jest.mock('@apic/studio-logger', () => {
  const mockLogger = {
    logDebug: jest.fn(),
    logInfo: jest.fn(),
    logWarn: jest.fn(),
    logError: jest.fn(),
  };

  return {
    Components: {
      ApimIntegratorComponent: 'ApimIntegratorComponent',
    },
    Logger: jest.fn().mockImplementation(() => mockLogger),
    LoggerBase: jest.fn().mockImplementation(function (this: any, logger: any) {
      this.logger = logger;
      this.logDebug = jest.fn();
      this.logInfo = jest.fn();
      this.logWarn = jest.fn();
      this.logError = jest.fn();
    }),
    LoggerConfig: {
      isLoggerEnabled: jest.fn(),
    },
  };
});

jest.mock('@apic/studio-client-model', () => ({
  GatewayLabels: {
    WMGW: 'webMethods',
    LWGW: 'nano',
    DPGW: 'datapower',
  },
}));
const originalConsoleError = console.error;
const originalConsoleWarn = console.warn;
const originalConsoleLog = console.log;

const mockZipFile = {
  async: jest.fn(),
  dir: false,
  name: 'test.yaml',
};

const mockZipContent = {
  loadAsync: jest.fn().mockResolvedValue({
    files: {
      'test.yaml': { ...mockZipFile },
      'api.yaml': { ...mockZipFile, name: 'api.yaml' },
      'policy.yaml': { ...mockZipFile, name: 'policy.yaml' },
      'directory/': { dir: true, name: 'directory/' },
    },
  }),
};

jest.mock('jszip', () => {
  return jest.fn().mockImplementation(() => mockZipContent);
});

jest.mock('js-yaml', () => ({
  loadAll: jest.fn().mockImplementation((content: string) => {
    if (content.includes('invalid')) {
      throw new Error('Invalid YAML');
    }

    if (content.includes('api')) {
      return [
        {
          kind: 'API',
          metadata: {
            namespace: 'test',
            name: 'api',
            version: '1.0',
          },
          spec: {
            'policy-sequence': [{ $ref: 'test:policy:1.0' }],
          },
        },
      ];
    }

    if (content.includes('stagedpolicy')) {
      return [
        {
          kind: 'StagedPolicySequence',
          metadata: {
            namespace: 'test',
            name: 'policy',
            version: '1.0',
          },
          spec: {
            policies: [{ $ref: 'test:nested:1.0' }],
          },
        },
      ];
    }

    if (content.includes('freeflowpolicy')) {
      return [
        {
          kind: 'FreeFlowPolicySequence',
          metadata: {
            namespace: 'test',
            name: 'policy',
            version: '1.0',
          },
          spec: {
            policies: [{ $ref: 'test:nested:1.0' }],
          },
        },
      ];
    }

    if (content.includes('datapowerassembly')) {
      return [
        {
          kind: 'DataPowerAssembly',
          metadata: {
            namespace: 'test',
            name: 'policy',
            version: '1.0',
          },
          spec: {
            policies: [{ $ref: 'test:nested:1.0' }],
          },
        },
      ];
    }

    if (content.includes('nested')) {
      return [
        {
          kind: 'NestedPolicy',
          metadata: {
            namespace: 'test',
            name: 'nested',
            version: '1.0',
          },
          spec: {},
        },
      ];
    }

    return [
      {
        kind: 'TestKind',
        metadata: {
          namespace: 'test',
          name: 'test',
          version: '1.0',
        },
        spec: {},
      },
    ];
  }),
}));

describe('AssetSchemaValidator', () => {
  let validator: AssetSchemaValidator;

  beforeEach(() => {
    jest.clearAllMocks();
    console.error = jest.fn();
    console.warn = jest.fn();
    console.log = jest.fn();
    validator = new AssetSchemaValidator();
    mockZipFile.async.mockResolvedValue('test content');
  });

  afterEach(() => {
    console.error = originalConsoleError;
    console.warn = originalConsoleWarn;
    console.log = originalConsoleLog;
  });

  describe('validateApiFile', () => {
    it('should validate API file successfully with WebMethods gateway', async () => {
      mockZipFile.async
        .mockResolvedValueOnce('api content')
        .mockResolvedValueOnce('stagedpolicy content');

      const result = await validator.validateApiFile(Buffer.from('test'), [GatewayLabels.WMGW]);

      expect(result.valid).toBe(true);
      expect(result.errors).toHaveLength(0);
    });

    it('should validate API file successfully with DataPower Nano Gateway', async () => {
      mockZipFile.async
        .mockResolvedValueOnce('api content')
        .mockResolvedValueOnce('freeflowpolicy content');

      const result = await validator.validateApiFile(Buffer.from('test'), [GatewayLabels.LWGW]);

      expect(result.valid).toBe(true);
      expect(result.errors).toHaveLength(0);
    });

    it('should return error when WebMethods gateway is selected but no staged policy sequences are referenced', async () => {
      // Setup mocks for no policy sequences
      mockZipFile.async.mockResolvedValue('test content');

      const result = await validator.validateApiFile(Buffer.from('test'), [GatewayLabels.WMGW]);

      expect(result.valid).toBe(false);
      expect(result.errors).toHaveLength(1);
      expect(result.errors[0]).toContain(
        'WebMethods gateway is selected but no staged policy sequences are referenced'
      );
    });

    it('should return error when DataPower Nano Gateway is selected but no free flow policy sequences are referenced', async () => {
      // Setup mocks for no policy sequences
      mockZipFile.async.mockResolvedValue('test content');

      const result = await validator.validateApiFile(Buffer.from('test'), [GatewayLabels.LWGW]);

      expect(result.valid).toBe(false);
      expect(result.errors).toHaveLength(1);
      expect(result.errors[0]).toContain(
        'DataPower Nano gateway is selected but no free flow policy sequences are referenced'
      );
    });

    it('should handle errors during validation', async () => {
      // Force an error
      mockZipContent.loadAsync.mockRejectedValueOnce(new Error('Test error'));

      const result = await validator.validateApiFile(Buffer.from('test'), [GatewayLabels.WMGW]);

      expect(result.valid).toBe(false);
      expect(result.errors).toHaveLength(1);
      expect(result.errors[0]).toContain(
        'WebMethods gateway is selected but no staged policy sequences are referenced'
      );
    });
  });

  describe('readAndConsolidateYamlFiles', () => {
    it('should read and consolidate YAML files', async () => {
      const result = await (validator as any).readAndConsolidateYamlFiles(Buffer.from('test'));

      expect(result).toBeInstanceOf(Array);
      expect(result.length).toBeGreaterThan(0);
    });

    it('should handle invalid YAML files', async () => {
      mockZipFile.async.mockResolvedValue('invalid yaml content');

      const result = await (validator as any).readAndConsolidateYamlFiles(Buffer.from('test'));

      expect(result).toBeInstanceOf(Array);
    });

    it('should handle errors during processing', async () => {
      mockZipContent.loadAsync.mockRejectedValueOnce(new Error('Test error'));

      const result = await (validator as any).readAndConsolidateYamlFiles(Buffer.from('test'));

      expect(result).toEqual([]);
    });
  });

  describe('categorizePolicySequences', () => {
    it('should categorize staged policy sequences', async () => {
      // Setup mocks for staged policy sequence
      mockZipFile.async
        .mockResolvedValueOnce('api content')
        .mockResolvedValueOnce('stagedpolicy content')
        .mockResolvedValueOnce('nested content');

      const result = await validator.categorizePolicySequences(Buffer.from('test'));

      expect(result.wmgwReferences.size).toBe(2);
      expect(result.wmgwReferences.has('test:policy:1.0')).toBe(true);
      expect(result.lwgwReferences.size).toBe(0);
    });

    it('should categorize free flow policy sequences', async () => {
      // Setup mocks for free flow policy sequence
      mockZipFile.async
        .mockResolvedValueOnce('api content')
        .mockResolvedValueOnce('freeflowpolicy content')
        .mockResolvedValueOnce('nested content');

      const result = await validator.categorizePolicySequences(Buffer.from('test'));

      expect(result.lwgwReferences.size).toBe(2);
      expect(result.lwgwReferences.has('test:policy:1.0')).toBe(true);
      expect(result.wmgwReferences.size).toBe(0);
    });

    it('should handle missing policy references', async () => {
      // Setup mocks for API with no matching policy
      mockZipFile.async.mockResolvedValue('api content');

      const result = await validator.categorizePolicySequences(Buffer.from('test'));

      expect(result.wmgwReferences.size).toBe(0);
      expect(result.lwgwReferences.size).toBe(0);
    });
  });

  describe('findAllNestedReferences', () => {
    it('should find all nested references', () => {
      const asset = {
        spec: {
          policies: [{ $ref: 'test:ref1:1.0' }, { $ref: 'test:ref2:1.0' }],
        },
      } as YamlContent;

      const allAssets = [
        {
          metadata: {
            namespace: 'test',
            name: 'ref1',
            version: '1.0',
          },
          spec: {
            nestedRef: { $ref: 'test:ref3:1.0' },
          },
        } as YamlContent,
      ];

      const referenceSet = new Set<string>();

      (validator as any).findAllNestedReferences(asset, allAssets, referenceSet);

      expect(referenceSet.size).toBeGreaterThan(0);
    });

    it('should handle null or undefined assets', () => {
      const referenceSet = new Set<string>();

      (validator as any).findAllNestedReferences(null, [], referenceSet);
      (validator as any).findAllNestedReferences({ metadata: {} }, [], referenceSet);

      expect(referenceSet.size).toBe(0);
    });
  });

  describe('findAllRefsRecursive', () => {
    it('should find all references recursively', () => {
      const data = {
        prop1: { $ref: 'test:ref1:1.0' },
        prop2: [{ $ref: 'test:ref2:1.0' }, { nested: { $ref: 'test:ref3:1.0' } }],
      };

      const referenceSet = new Set<string>();

      (validator as any).findAllRefsRecursive(data, referenceSet);

      expect(referenceSet.size).toBe(3);
      expect(referenceSet.has('test:ref1:1.0')).toBe(true);
      expect(referenceSet.has('test:ref2:1.0')).toBe(true);
      expect(referenceSet.has('test:ref3:1.0')).toBe(true);
    });

    it('should handle null or undefined data', () => {
      const referenceSet = new Set<string>();

      (validator as any).findAllRefsRecursive(null, referenceSet);
      (validator as any).findAllRefsRecursive(undefined, referenceSet);

      expect(referenceSet.size).toBe(0);
    });
  });

  describe('validateSchema', () => {
    it('should validate schema successfully', async () => {
      jest.spyOn(validator, 'validateApiFile').mockResolvedValueOnce({
        valid: true,
        errors: [],
      });
      (AssetValidator as jest.Mock).mockImplementation(() => ({
        validateAssets: jest.fn().mockReturnValue({ valid: true, errors: [] }),
      }));

      const result = await validator.validateSchema(Buffer.from('test'), [GatewayLabels.WMGW]);

      expect(result.valid).toBe(true);
      expect(result.errors).toHaveLength(0);
    });

    it('should return errors from API validation', async () => {
      jest.spyOn(validator, 'validateApiFile').mockResolvedValueOnce({
        valid: false,
        errors: ['API validation error'],
      });

      const result = await validator.validateSchema(Buffer.from('test'), [GatewayLabels.WMGW]);

      expect(result.valid).toBe(false);
      expect(result.errors).toHaveLength(1);
      expect(result.errors[0]).toBe('API validation error');
    });

    it('should validate common assets', async () => {
      jest.spyOn(validator, 'validateApiFile').mockResolvedValueOnce({
        valid: true,
        errors: [],
      });
      jest.spyOn(validator as any, 'readAndConsolidateYamlFiles').mockResolvedValueOnce([
        {
          kind: 'API',
          metadata: { namespace: 'test', name: 'api', version: '1.0' },
          spec: {},
        },
        {
          kind: 'Properties',
          metadata: { namespace: 'test', name: 'props', version: '1.0' },
          spec: {},
        },
      ]);

      const result = await validator.validateSchema(Buffer.from('test'), [GatewayLabels.WMGW]);

      expect(result.valid).toBe(true);
      expect(SchemaHandler).toHaveBeenCalled();
      expect(AssetValidator).toHaveBeenCalled();
    });

    it('should validate WebMethods gateway specific assets', async () => {
      jest.spyOn(validator, 'validateApiFile').mockResolvedValueOnce({
        valid: true,
        errors: [],
      });
      jest.spyOn(validator, 'categorizePolicySequences').mockResolvedValue({
        wmgwReferences: new Set(['test:policy:1.0']),
        lwgwReferences: new Set(),
        dpgwReferences: new Set(),
      });
      jest.spyOn(validator as any, 'readAndConsolidateYamlFiles').mockResolvedValueOnce([
        {
          kind: 'StagedPolicySequence',
          metadata: { namespace: 'test', name: 'policy', version: '1.0' },
          spec: {},
        },
      ]);

      const result = await validator.validateSchema(Buffer.from('test'), [GatewayLabels.WMGW]);

      expect(result.valid).toBe(true);
      expect(SchemaHandler).toHaveBeenCalledWith('webMethods');
    });

    it('should validate DataPower Nano Gateway specific assets', async () => {
      jest.spyOn(validator, 'validateApiFile').mockResolvedValueOnce({
        valid: true,
        errors: [],
      });
      jest.spyOn(validator, 'categorizePolicySequences').mockResolvedValue({
        wmgwReferences: new Set(),
        lwgwReferences: new Set(['test:policy:1.0']),
        dpgwReferences: new Set(),
      });
      jest.spyOn(validator as any, 'readAndConsolidateYamlFiles').mockResolvedValueOnce([
        {
          kind: 'FreeFlowPolicySequence',
          metadata: { namespace: 'test', name: 'policy', version: '1.0' },
          spec: {},
        },
      ]);

      const result = await validator.validateSchema(Buffer.from('test'), [GatewayLabels.LWGW]);

      expect(result.valid).toBe(true);
      expect(SchemaHandler).toHaveBeenCalledWith('nano');
    });

    it('should validate DataPower API Gateway specific assets', async () => {
      jest.spyOn(validator, 'validateApiFile').mockResolvedValueOnce({
        valid: true,
        errors: [],
      });
      jest.spyOn(validator, 'categorizePolicySequences').mockResolvedValue({
        wmgwReferences: new Set(),
        lwgwReferences: new Set(),
        dpgwReferences: new Set(['test:policy:1.0']),
      });
      jest.spyOn(validator as any, 'readAndConsolidateYamlFiles').mockResolvedValueOnce([
        {
          kind: 'DataPowerAssembly',
          metadata: { namespace: 'test', name: 'policy', version: '1.0' },
          spec: {},
        },
      ]);

      const result = await validator.validateSchema(Buffer.from('test'), [GatewayLabels.DPGW]);

      expect(result.valid).toBe(true);
      expect(SchemaHandler).toHaveBeenCalledWith('datapower');
    });

    it('should validate all WebMethods, DataPower Nano Gateway and DataPower API Gateway assets', async () => {
      jest.spyOn(validator, 'validateApiFile').mockResolvedValueOnce({
        valid: true,
        errors: [],
      });
      jest.spyOn(validator as any, 'validateGatewaySpecificAssets').mockResolvedValue(undefined);

      const result = await validator.validateSchema(Buffer.from('test'), [
        GatewayLabels.WMGW,
        GatewayLabels.LWGW,
        GatewayLabels.DPGW,
      ]);

      expect(result.valid).toBe(true);
      expect((validator as any).validateGatewaySpecificAssets).toHaveBeenCalledTimes(3);
    });

    it('should handle errors during validation', async () => {
      jest.spyOn(validator, 'validateApiFile').mockResolvedValueOnce({
        valid: true,
        errors: [],
      });

      jest
        .spyOn(validator as any, 'readAndConsolidateYamlFiles')
        .mockRejectedValueOnce(new Error('Test error'));

      const result = await validator.validateSchema(Buffer.from('test'), [GatewayLabels.WMGW]);

      expect(result.valid).toBe(false);
      expect(result.errors).toHaveLength(1);
      expect(result.errors[0]).toContain('Error validating schema');
    });
  });

  describe('validateGatewaySpecificAssets', () => {
    it('should validate gateway specific assets', async () => {
      jest.spyOn(validator, 'categorizePolicySequences').mockResolvedValueOnce({
        wmgwReferences: new Set(['test:policy:1.0']),
        lwgwReferences: new Set(),
        dpgwReferences: new Set(),
      });

      const assets = [
        {
          kind: 'StagedPolicySequence',
          metadata: { namespace: 'test', name: 'policy', version: '1.0' },
          spec: {},
        },
      ] as YamlContent[];

      const errors: string[] = [];

      await (validator as any).validateGatewaySpecificAssets(
        assets,
        GatewayLabels.WMGW,
        errors,
        Buffer.from('test')
      );

      expect(errors).toHaveLength(0);
      expect(SchemaHandler).toHaveBeenCalledWith('webMethods');
    });

    it('should add errors for invalid assets', async () => {
      jest.spyOn(validator, 'categorizePolicySequences').mockResolvedValueOnce({
        wmgwReferences: new Set(['test:policy:1.0']),
        lwgwReferences: new Set(),
        dpgwReferences: new Set(),
      });

      (AssetValidator as jest.Mock).mockImplementation(() => ({
        validateAssets: jest.fn().mockReturnValue({
          valid: false,
          errors: ['Validation error'],
        }),
      }));

      const assets = [
        {
          kind: 'StagedPolicySequence',
          metadata: { namespace: 'test', name: 'policy', version: '1.0' },
          spec: {},
        },
      ] as YamlContent[];

      const errors: string[] = [];

      await (validator as any).validateGatewaySpecificAssets(
        assets,
        GatewayLabels.WMGW,
        errors,
        Buffer.from('test')
      );

      expect(errors).toHaveLength(1);
      expect(errors[0]).toContain('Validation error');
    });

    it('should skip assets without kind or metadata', async () => {
      jest.spyOn(validator, 'categorizePolicySequences').mockResolvedValueOnce({
        wmgwReferences: new Set(['test:policy:1.0']),
        lwgwReferences: new Set(),
        dpgwReferences: new Set(),
      });

      const assets = [{ spec: {} }, { kind: 'Test', spec: {} }] as YamlContent[];

      const errors: string[] = [];

      await (validator as any).validateGatewaySpecificAssets(
        assets,
        GatewayLabels.WMGW,
        errors,
        Buffer.from('test')
      );

      expect(errors).toHaveLength(0);
    });

    it('should handle unsupported gateway types', async () => {
      const assets = [] as YamlContent[];
      const errors: string[] = [];

      await (validator as any).validateGatewaySpecificAssets(
        assets,
        'unsupported' as GatewayLabels,
        errors,
        Buffer.from('test')
      );

      expect(errors).toHaveLength(0);
    });
  });
});
