/**
* Copyright Super iPaaS Integration LLC, an IBM Company 2024
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
import AdmZip from 'adm-zip';
import { loadCacheWithProject } from './asset-cache-helper.js';
import { isYamlFile } from '../common/fs-helper.js';
import { readMultiYaml } from '../common/yaml-helper.js';
import { isValidAsset } from './asset-helper.js';
import { AssetCache } from '../../cache/asset-cache.js';
import { getRefsFromAsset } from '../../handlers/asset-handler.js';
import { BaseAsset } from '../../model/assets-model.js';


jest.mock('../common/fs-helper.js', () => ({
	isYamlFile: jest.fn(),
}));

jest.mock('../common/yaml-helper.js', () => ({
	readMultiYaml: jest.fn(),
}));

jest.mock('./asset-helper.js', () => ({
	isValidAsset: jest.fn(),
}));

jest.mock('../common/message-helper.js', () => ({
	showWarning: jest.fn(),
	showInfo: jest.fn()
}));

jest.mock('../../cache/asset-cache.js', () => {
	return {
		AssetCache: {
			getInstance: jest.fn().mockReturnValue({
				markAsProcessed: jest.fn(),
				checkAndMarkAsUnProcessed: jest.fn(),
			}),
		},
	};
});

jest.mock('../../handlers/asset-handler.js', () => ({
	getRefsFromAsset: jest.fn(),
}));
describe('Asset cache helper function test suite', () => {

	afterEach(() => {
		jest.clearAllMocks();
	});
	it('should handle empty zip', () => {
		const zip = new AdmZip();
		jest.spyOn(zip, 'getEntries').mockReturnValue([]);

		loadCacheWithProject(zip);

		expect(readMultiYaml).not.toHaveBeenCalled();
		expect(isValidAsset).not.toHaveBeenCalled();
		expect(AssetCache.getInstance().markAsProcessed).not.toHaveBeenCalled();
		expect(getRefsFromAsset).not.toHaveBeenCalled();
	});
	it('should handle zip with invalid asset', () => {
		const zip = new AdmZip();
		const mockAsset = { kind: 'invalidKind', metadata: { name: 'invalidName' } } as BaseAsset;
		jest.spyOn(zip, 'getEntries').mockReturnValue([
			{ entryName: 'asset.yml', isDirectory: false, getData: jest.fn().mockReturnValue(Buffer.from('invalid yaml')) } as any,
		]);

		(isYamlFile as jest.Mock).mockReturnValue(true);
		(readMultiYaml as jest.Mock).mockReturnValue([mockAsset]);
		(isValidAsset as jest.Mock).mockReturnValue(false);

		loadCacheWithProject(zip);

		expect(readMultiYaml).toHaveBeenCalledWith('asset.yml', 'invalid yaml');
		expect(isValidAsset).toHaveBeenCalledWith(mockAsset);
		expect(AssetCache.getInstance().markAsProcessed).not.toHaveBeenCalled();
		expect(getRefsFromAsset).not.toHaveBeenCalled();
	});
	it('should handle zip with non-yaml files', () => {
		const zip = new AdmZip();
		jest.spyOn(zip, 'getEntries').mockReturnValue([
			{ entryName: 'file.txt', isDirectory: false, getData: jest.fn() } as any,
			{ entryName: 'image.png', isDirectory: false, getData: jest.fn() } as any,
		]);

		(isYamlFile as jest.Mock).mockReturnValue(false);

		loadCacheWithProject(zip);

		expect(readMultiYaml).not.toHaveBeenCalled();
		expect(isValidAsset).not.toHaveBeenCalled();
		expect(AssetCache.getInstance().markAsProcessed).not.toHaveBeenCalled();
		expect(getRefsFromAsset).not.toHaveBeenCalled();
	});

	it('should handle zip with one valid asset', () => {
		const zip = new AdmZip();
		const mockAsset = { kind: 'validKind', metadata: { name: 'validName' } } as BaseAsset;
		const mockRefs = [{ kind: 'refKind', metadata: { name: 'refName' } }] as BaseAsset[];

		jest.spyOn(zip, 'getEntries').mockReturnValue([
			{ entryName: 'asset.yml', isDirectory: false, getData: jest.fn().mockReturnValue(Buffer.from('valid yaml')) } as any,
		]);

		(isYamlFile as jest.Mock).mockReturnValue(true);
		(readMultiYaml as jest.Mock).mockReturnValue([mockAsset]);
		(isValidAsset as jest.Mock).mockReturnValue(true);
		(getRefsFromAsset as jest.Mock).mockReturnValue(mockRefs);

		loadCacheWithProject(zip);

		expect(readMultiYaml).toHaveBeenCalledWith('asset.yml', 'valid yaml');
		expect(isValidAsset).toHaveBeenCalledWith(mockAsset);
		expect(AssetCache.getInstance().markAsProcessed).toHaveBeenCalledWith(mockAsset);
		expect(getRefsFromAsset).toHaveBeenCalledWith(mockAsset);
		expect(AssetCache.getInstance().checkAndMarkAsUnProcessed).toHaveBeenCalledWith(mockRefs[0]);
	});


	it('should handle dependency assets correctly', () => {
		const zip = new AdmZip();
		const mockAsset = { kind: 'validKind', metadata: { name: 'validName' } } as BaseAsset;
		const mockRefAsset = { kind: 'refKind', metadata: { name: 'refName' } } as BaseAsset;
		const mockRefs = [mockRefAsset];

		jest.spyOn(zip, 'getEntries').mockReturnValue([
			{ entryName: 'asset.yml', isDirectory: false, getData: jest.fn().mockReturnValue(Buffer.from('valid yaml')) } as any,
		]);

		(isYamlFile as jest.Mock).mockReturnValue(true);
		(readMultiYaml as jest.Mock).mockReturnValue([mockAsset]);
		(isValidAsset as jest.Mock).mockReturnValue(true);
		(getRefsFromAsset as jest.Mock).mockReturnValue(mockRefs);

		loadCacheWithProject(zip);

		expect(readMultiYaml).toHaveBeenCalledWith('asset.yml', 'valid yaml');
		expect(isValidAsset).toHaveBeenCalledWith(mockAsset);
		expect(AssetCache.getInstance().markAsProcessed).toHaveBeenCalledWith(mockAsset);
		expect(getRefsFromAsset).toHaveBeenCalledWith(mockAsset);
		expect(AssetCache.getInstance().checkAndMarkAsUnProcessed).toHaveBeenCalledWith(mockRefAsset);
	});

	it('should process multiple valid assets in one YAML file', () => {
		const zip = new AdmZip();
		const mockAssets = [
			{ kind: 'validKind1', metadata: { name: 'validName1' } } as BaseAsset,
			{ kind: 'validKind2', metadata: { name: 'validName2' } } as BaseAsset,
		];

		jest.spyOn(zip, 'getEntries').mockReturnValue([
			{ entryName: 'assets.yml', isDirectory: false, getData: jest.fn().mockReturnValue(Buffer.from('multiple valid yaml')) } as any,
		]);

		(isYamlFile as jest.Mock).mockReturnValue(true);
		(readMultiYaml as jest.Mock).mockReturnValue(mockAssets);
		(isValidAsset as jest.Mock).mockReturnValue(true);

		loadCacheWithProject(zip);

		mockAssets.forEach(asset => {
			expect(isValidAsset).toHaveBeenCalledWith(asset);
			expect(AssetCache.getInstance().markAsProcessed).toHaveBeenCalledWith(asset);
		});
		expect(readMultiYaml).toHaveBeenCalledWith('assets.yml', 'multiple valid yaml');
	});

	it('should process valid assets and skip invalid ones in one YAML file', () => {
		const zip = new AdmZip();
		const validAsset = { kind: 'validKind', metadata: { name: 'validName' } } as BaseAsset;
		const invalidAsset = { kind: 'invalidKind', metadata: { name: 'invalidName' } } as BaseAsset;

		jest.spyOn(zip, 'getEntries').mockReturnValue([
			{ entryName: 'assets.yml', isDirectory: false, getData: jest.fn().mockReturnValue(Buffer.from('valid and invalid yaml')) } as any,
		]);

		(isYamlFile as jest.Mock).mockReturnValue(true);
		(readMultiYaml as jest.Mock).mockReturnValue([validAsset, invalidAsset]);
		(isValidAsset as jest.Mock)
			.mockReturnValueOnce(true) // validAsset
			.mockReturnValueOnce(false); // invalidAsset

		loadCacheWithProject(zip);

		expect(readMultiYaml).toHaveBeenCalledWith('assets.yml', 'valid and invalid yaml');
		expect(isValidAsset).toHaveBeenCalledWith(validAsset);
		expect(isValidAsset).toHaveBeenCalledWith(invalidAsset);
		expect(AssetCache.getInstance().markAsProcessed).toHaveBeenCalledWith(validAsset);
		expect(AssetCache.getInstance().markAsProcessed).not.toHaveBeenCalledWith(invalidAsset);
	});

	it('should extract and pass source project name from entry path', () => {
		const zip = new AdmZip();
		const mockAsset = { kind: 'Product', metadata: { name: 'TestProduct' } } as BaseAsset;
		const mockRef = { kind: 'API', ref: 'dev:PaymentAPI:1.0', isNewlyAdded: true };

		jest.spyOn(zip, 'getEntries').mockReturnValue([
			{ entryName: 'ProjectA/api-assets/product.yml', isDirectory: false, getData: jest.fn().mockReturnValue(Buffer.from('yaml content')) } as any,
		]);

		(isYamlFile as jest.Mock).mockReturnValue(true);
		(readMultiYaml as jest.Mock).mockReturnValue([mockAsset]);
		(isValidAsset as jest.Mock).mockReturnValue(true);
		(getRefsFromAsset as jest.Mock).mockReturnValue([mockRef]);

		loadCacheWithProject(zip);

		// Verify that checkForDependencyAssets was called with the source project
		expect(getRefsFromAsset).toHaveBeenCalledWith(mockAsset);
		expect(AssetCache.getInstance().checkAndMarkAsUnProcessed).toHaveBeenCalledWith(
			expect.objectContaining({
				kind: 'API',
				ref: 'dev:PaymentAPI:1.0',
				isNewlyAdded: true,
				sourceProject: 'ProjectA'
			})
		);
	});

	it('should handle multiple projects with different source projects', () => {
		const zip = new AdmZip();
		const mockAssetA = { kind: 'Product', metadata: { name: 'ProductA' } } as BaseAsset;
		const mockAssetB = { kind: 'Product', metadata: { name: 'ProductB' } } as BaseAsset;
		const mockRefA = { kind: 'API', ref: 'dev:API_A:1.0', isNewlyAdded: true };
		const mockRefB = { kind: 'API', ref: 'dev:API_B:1.0', isNewlyAdded: true };

		jest.spyOn(zip, 'getEntries').mockReturnValue([
			{ entryName: 'ProjectA/api-assets/product.yml', isDirectory: false, getData: jest.fn().mockReturnValue(Buffer.from('yaml A')) } as any,
			{ entryName: 'ProjectC/api-assets/product.yml', isDirectory: false, getData: jest.fn().mockReturnValue(Buffer.from('yaml B')) } as any,
		]);

		(isYamlFile as jest.Mock).mockReturnValue(true);
		(readMultiYaml as jest.Mock)
			.mockReturnValueOnce([mockAssetA])
			.mockReturnValueOnce([mockAssetB]);
		(isValidAsset as jest.Mock).mockReturnValue(true);
		(getRefsFromAsset as jest.Mock)
			.mockReturnValueOnce([mockRefA])
			.mockReturnValueOnce([mockRefB]);

		loadCacheWithProject(zip);

		// Verify each dependency has correct source project
		expect(AssetCache.getInstance().checkAndMarkAsUnProcessed).toHaveBeenCalledWith(
			expect.objectContaining({
				kind: 'API',
				ref: 'dev:API_A:1.0',
				sourceProject: 'ProjectA'
			})
		);
		expect(AssetCache.getInstance().checkAndMarkAsUnProcessed).toHaveBeenCalledWith(
			expect.objectContaining({
				kind: 'API',
				ref: 'dev:API_B:1.0',
				sourceProject: 'ProjectC'
			})
		);
	});

	it('should handle nested folder structure in entry path', () => {
		const zip = new AdmZip();
		const mockAsset = { kind: 'Product', metadata: { name: 'TestProduct' } } as BaseAsset;
		const mockRef = { kind: 'API', ref: 'dev:API:1.0', isNewlyAdded: true };

		jest.spyOn(zip, 'getEntries').mockReturnValue([
			{ entryName: 'ProjectA/nested/folder/product.yml', isDirectory: false, getData: jest.fn().mockReturnValue(Buffer.from('yaml')) } as any,
		]);

		(isYamlFile as jest.Mock).mockReturnValue(true);
		(readMultiYaml as jest.Mock).mockReturnValue([mockAsset]);
		(isValidAsset as jest.Mock).mockReturnValue(true);
		(getRefsFromAsset as jest.Mock).mockReturnValue([mockRef]);

		loadCacheWithProject(zip);

		// Should extract 'ProjectA' as source project even with nested folders
		expect(AssetCache.getInstance().checkAndMarkAsUnProcessed).toHaveBeenCalledWith(
			expect.objectContaining({
				sourceProject: 'ProjectA'
			})
		);
	});

	it('should handle entry path without project folder', () => {
		const zip = new AdmZip();
		const mockAsset = { kind: 'Product', metadata: { name: 'TestProduct' } } as BaseAsset;
		const mockRef = { kind: 'API', ref: 'dev:API:1.0', isNewlyAdded: true };

		jest.spyOn(zip, 'getEntries').mockReturnValue([
			{ entryName: 'product.yml', isDirectory: false, getData: jest.fn().mockReturnValue(Buffer.from('yaml')) } as any,
		]);

		(isYamlFile as jest.Mock).mockReturnValue(true);
		(readMultiYaml as jest.Mock).mockReturnValue([mockAsset]);
		(isValidAsset as jest.Mock).mockReturnValue(true);
		(getRefsFromAsset as jest.Mock).mockReturnValue([mockRef]);

		loadCacheWithProject(zip);

		// Should use the filename as source project when no folder structure
		expect(AssetCache.getInstance().checkAndMarkAsUnProcessed).toHaveBeenCalledWith(
			expect.objectContaining({
				sourceProject: 'product.yml'
			})
		);
	});

});