/**
 * Copyright IBM Corp. 2024, 2025
 */
import path from 'path';
import fs from 'fs';
import JSZip from 'jszip';

jest.mock('@apic/studio-shared', () => ({
  Logger: {
    debug: jest.fn(),
    info: jest.fn(),
    warn: jest.fn(),
    error: jest.fn(),
    log: jest.fn(),
    createChildLogger: jest.fn().mockReturnThis(),
  },
  LogComponent: () => (target: any) => target,
  Component: {
    Build: 'Build',
    All: 'Studio',
  },
  ErrorResponse: jest.fn(),
  Metadata_Ref: jest.fn(),
  SpecObject: jest.fn(),
  YamlContent: jest.fn(),
  UpperCaseKinds: jest.fn(),
}));

import { BuildProjectAssets } from '../../src/build-project-assets.js';
import { ProjectAssetValidator } from '../../src/validator/asset-validator.js';
import { normalizeZipPaths, resolveRelativePaths } from '../../src/index.js';

jest.mock('@apic/studio-client-model', () => ({
  AssetModelKindConstants: {
    API: 'API',
  },
}));

describe('Build Asset Project Modules', () => {
  it('should validate that given entry and return true if it is yaml  ', async () => {
    const zipFilePath = path.resolve(__dirname, '../assets/gateway-multi-project-asset.zip');
    const Buffer = fs.readFileSync(zipFilePath);
    const obj = new BuildProjectAssets();
    const zip2 = await JSZip.loadAsync(Buffer);
    const normalizedBuffer = await normalizeZipPaths(zip2);
    const buffer2 = await normalizedBuffer.generateAsync({
      type: 'nodebuffer',
    });
    const result = await obj['loadZipFromBuffer'](buffer2);

    const entry = result.files[path.normalize('demo/api.yml')];
    const obj2 = new ProjectAssetValidator();
    const bool = obj2['isYamlFileForFolder'](entry, 'demo');
    expect(bool).toBe(true);
  });
  it('should load zip from buffer ', async () => {
    const zipFilePath = path.resolve(__dirname, '../assets/gateway-multi-project-asset.zip');
    const Buffer = fs.readFileSync(zipFilePath);
    const obj = new ProjectAssetValidator();
    const result = await obj['loadZipFromBuffer'](Buffer);
    expect(result).not.toBe(undefined);
    expect(result).not.toBe(null);
    expect(result).not.toBe(false);
  });
  it('should create asset reference map for the project', async () => {
    const zipFilePath = path.resolve(__dirname, '../assets/gateway-multi-project-asset.zip');
    const Buffer = fs.readFileSync(zipFilePath);
    const obj = new ProjectAssetValidator();
    const allFolderNames = new Set<string>();
    allFolderNames.add('project1');
    allFolderNames.add('project2');
    const result = await obj['createProjectAssetReferenceMap'](Buffer, 'project1', allFolderNames);
    for (const value of result.values()) {
      expect(value).toBe(true);
    }
  });
  it('should execute catch if createProjectAssetReferenceMap throws an error', async () => {
    const spy = jest
      .spyOn(BuildProjectAssets.prototype, 'createVersionProcessingMap')
      .mockImplementation(() => {
        throw new Error('Error processing zip');
      });

    const buffer = fs.readFileSync(
      path.resolve(__dirname, '../assets/gateway-multi-project-asset.zip')
    );
    const obj = new ProjectAssetValidator();
    const allFolderNames = new Set(['project1', 'project2']);
    const result = await obj['createProjectAssetReferenceMap'](buffer, 'project1', allFolderNames);

    expect(result).toBeInstanceOf(Map);
    spy.mockRestore();
  });
  it('should process other folders if unresolved refs exist', async () => {
    const buffer = fs.readFileSync(
      path.resolve(__dirname, '../assets/gateway-multi-project-asset.zip')
    );
    const obj = new ProjectAssetValidator();
    const allFolderNames = new Set(['project1', 'project2']);

    const processedFolders: string[] = [];

    const spy = jest
      .spyOn(ProjectAssetValidator.prototype as any, 'processYamlFiles')
      .mockImplementation(async function (
        zipContent: unknown,
        folder: unknown,
        refMap: unknown
        // versionMap: unknown,
      ) {
        const typedRefMap = refMap as Map<string, boolean>;
        processedFolders.push(folder as string);

        if (folder === 'project1') {
          typedRefMap.set('ref1', false);
        }
      });

    const result = await obj['createProjectAssetReferenceMap'](buffer, 'project1', allFolderNames);

    expect(result).toBeInstanceOf(Map);
    expect(result.get('ref1')).toBe(false);

    expect(processedFolders).toContain('project2');
    spy.mockRestore();
  });

  it('should create asset path reference map for the project', async () => {
    const zipFilePath = path.resolve(__dirname, '../assets/gateway-multi-project-asset.zip');
    const Buffer = fs.readFileSync(zipFilePath);
    const obj = new ProjectAssetValidator();
    const result = await obj['createProjectPathReferenceMap'](Buffer, 'project1');
    for (const value of result.values()) {
      expect(value).toBe(false);
    }
  });
  it('should execute catch if createProjectPathReferenceMap throws an error', async () => {
    const spy = jest
      .spyOn(BuildProjectAssets.prototype, 'createVersionProcessingMap')
      .mockImplementation(() => {
        throw new Error('Error processing zip');
      });

    const buffer = fs.readFileSync(
      path.resolve(__dirname, '../assets/gateway-multi-project-asset.zip')
    );
    const obj = new ProjectAssetValidator();
    const result = await obj['createProjectPathReferenceMap'](buffer, 'project1');
    expect(result).toBeInstanceOf(Map);
    spy.mockRestore();
  });
  it('should create asset path reference map for the project and update the map with filepath ', async () => {
    const zipFilePath = path.resolve(__dirname, '../assets/gateway-multi-project-asset.zip');
    const Buffer = fs.readFileSync(zipFilePath);
    const obj = new ProjectAssetValidator();
    const zip2 = await JSZip.loadAsync(Buffer);
    const normalizedBuffer = await normalizeZipPaths(zip2);
    const zip3 = await resolveRelativePaths(
      await normalizedBuffer.generateAsync({ type: 'nodebuffer' })
    );
    const buffer2 = await zip3.generateAsync({ type: 'nodebuffer' });
    const obj2 = new BuildProjectAssets();
    const folderNames = new Set<string>();
    const filePathsInFolder = new Set<string>();
    await obj2['extractFolderNamesAndPaths'](buffer2, folderNames, filePathsInFolder);
    const result = await obj['validateProjectPathReference'](
      buffer2,
      'project1',
      filePathsInFolder
    );
    expect(result).toBe(true);
  });
  it('should throw an error if ValidateProjectPathReference throws an error', async () => {
    const obj = new ProjectAssetValidator();
    const spy = jest
      .spyOn(ProjectAssetValidator.prototype as any, 'createProjectPathReferenceMap')
      .mockImplementation(() => {
        throw new Error('Error validating asset');
      });
    // const refMap = new Map<string, boolean>();
    const buffer = fs.readFileSync(
      path.resolve(__dirname, '../assets/gateway-multi-project-asset.zip')
    );
    const allFolderNames = new Set(['project1', 'project2']);
    const result = await obj['validateProjectPathReference'](buffer, 'project1', allFolderNames);
    expect(result).toBe(false);
    spy.mockRestore();
  });
  it('should throw an error if validateProjectPathReference encounters invalid references', async () => {
    const obj = new ProjectAssetValidator();
    // const spy = jest.spyOn(ProjectAssetValidator.prototype as any, 'createProjectPathReferenceMap').mockResolvedValue(new Map([
    // 	['ref1', false],
    // 	['ref2', true],
    // ]))

    const Buffer = fs.readFileSync(
      path.resolve(__dirname, '../assets/gateway-multi-project-asset.zip')
    );
    const filePathsInFolder = new Set<string>();
    const result = await obj['validateProjectPathReference'](Buffer, 'demo', filePathsInFolder);
    expect(result).toBe(false);
  });
  it('should throw an error if ValidateProjectAssetReference encounters invalid references', async () => {
    const obj = new ProjectAssetValidator();
    const spy = jest
      .spyOn(ProjectAssetValidator.prototype as any, 'createProjectAssetReferenceMap')
      .mockImplementation(() => {
        throw new Error('Error validating asset');
      });
    // const refMap = new Map<string, boolean>();
    const buffer = fs.readFileSync(
      path.resolve(__dirname, '../assets/gateway-multi-project-asset.zip')
    );
    const allFolderNames = new Set(['project1', 'project2']);
    const result = await obj['validateProjectAssetReference'](buffer, 'project1', allFolderNames);
    expect(result.isValid).toBe(false);
    expect(result.refMap).toBeInstanceOf(Map);
    spy.mockRestore();
  });

  it('should create asset reference map for the project and update the map with filepath ', async () => {
    const zipFilePath = path.resolve(__dirname, '../assets/gateway-multi-project-asset.zip');
    const Buffer = fs.readFileSync(zipFilePath);
    const obj = new ProjectAssetValidator();
    const zip2 = await JSZip.loadAsync(Buffer);
    const normalizedBuffer = await normalizeZipPaths(zip2);
    const zip3 = await resolveRelativePaths(
      await normalizedBuffer.generateAsync({ type: 'nodebuffer' })
    );
    const buffer2 = await zip3.generateAsync({ type: 'nodebuffer' });
    const obj2 = new BuildProjectAssets();
    const folderNames = new Set<string>();
    const filePathsInFolder = new Set<string>();
    await obj2['extractFolderNamesAndPaths'](buffer2, folderNames, filePathsInFolder);
    const result = await obj['validateProjectAssetReference'](
      buffer2,
      'project1',
      filePathsInFolder
    );
    expect(result.isValid).toBe(true);
    expect(result.refMap).not.toBe(null);
    expect(result.refMap).not.toBe(undefined);
  });

  it('should validate the minimum assets required for deployment ', async () => {
    const zipFilePath = path.resolve(__dirname, '../assets/gateway-multi-project-asset.zip');
    const Buffer = fs.readFileSync(zipFilePath);
    const obj = new ProjectAssetValidator();
    const result = await obj['validateProjectHasMinimumAssets'](Buffer);
    expect(result).toBe(true);
  });
  it('should return false the minimum assets required for deployment is not found ', async () => {
    const zipFilePath = path.resolve(
      __dirname,
      '../assets/gateway-project-with-no-valid-assets.zip'
    );
    const Buffer = fs.readFileSync(zipFilePath);
    const obj = new ProjectAssetValidator();
    const result = await obj['validateProjectHasMinimumAssets'](Buffer);
    expect(result).toBe(false);
  });
  it('should validate the api spec for kind api files  ', async () => {
    const zipFilePath = path.resolve(__dirname, '../assets/gateway-multi-project-asset.zip');
    const Buffer = fs.readFileSync(zipFilePath);
    const obj = new ProjectAssetValidator();
    const result = await obj['validateProjectApiSpecVariable'](Buffer, 'project1');
    expect(result).toBe(true);
  });
});
