/**
* Copyright Super iPaaS Integration LLC, an IBM Company 2024
*/
import yaml from 'js-yaml';
import JSZip from 'jszip';
import { ErrorResponse, Metadata_Ref, SpecObject, YamlContent, UpperCaseKinds } from './model/interface.js';
import { AssetValidator } from './service/validation-service.js';
import { AppConstants } from './constants/app.constants.js';
import path from 'path';

export const validateYamlFiles = async (buffer: Buffer): Promise<boolean> => {
	const zip = new JSZip();
	let allValid = true;
	try {
		const zipContent = await zip.loadAsync(buffer);
		//logDebug('0308', `${Object.keys(zipContent.files).length}`);

		for (const fileName in zipContent.files) {
			const entry = zipContent.files[fileName];

			if (!entry.dir && (fileName.endsWith('.yaml') || fileName.endsWith('.yml'))) {
				const content = await entry.async('string');
				//logInfo('0353', fileName);

				try {
					const yamlContents = yaml.loadAll(content) as YamlContent[];
					if (!validatYamlContent(yamlContents, fileName)) {
						allValid = false;
					}
				} catch (err) {
					//logError('0013', 'while parsing YAML', `${err}`);
					addErrorToResponse(AppConstants.VALIDATION_ERROR_CODE, AppConstants.YAML_FILE, `Invalid YAML in file ${fileName}: ${err}`);
					allValid = false;
				}
			}
		}
	} catch (err) {
		//logError('0013', 'while loading ZIP', `${err}`);
		addErrorToResponse(AppConstants.VALIDATION_ERROR_CODE, 'ZIP_FILE', `Error loading ZIP: ${err}`);
		allValid = false;
	}

	return allValid;
};

export const createAssetReferenceMap = async (buffer: Buffer): Promise<Map<string, boolean>> => {
	const zip = new JSZip();
	const refMap = new Map<string, boolean>();

	try {
		const zipContent = await zip.loadAsync(buffer);
		//logDebug('0308', `${Object.keys(zipContent.files).length}`);

		for (const fileName in zipContent.files) {
			const entry = zipContent.files[fileName];
			if (!entry.dir && (fileName.endsWith('.yaml') || fileName.endsWith('.yml')) && !fileName.includes('resources')) {
				const content = await entry.async('string');
				//logInfo('0353', fileName);

				try {
					const yamlContents = yaml.loadAll(content) as YamlContent[];
					for (const yamlContent of yamlContents) {
						extractRefs(yamlContent, refMap);
						updateMapWithMetadata(yamlContent, refMap);
					}
				} catch (err) {
					//logError('0013', 'parsing YAML', `${err}`);
				}
			}
		}
	} catch (err) {
		//logError('0013', 'loading ZIP', `${err}`);
	}

	return refMap;
};

export const extractRefs = (yamlContent: YamlContent, refMap: Map<string, boolean>): void => {
	const extractRef = (obj: SpecObject) => {
		for (const key in obj) {
			const value = obj[key];
			if (key === '$ref' && typeof value === 'string') {
				if (!refMap.has(value)) {
					refMap.set(value, false);
					//logDebug('0309', value);
				}
			} else if (typeof value === 'object' && value !== null) {
				extractRef(value);
			}
		}
	};
	const specOb = JSON.stringify(yamlContent.spec);
	extractRef(yaml.load(specOb) as SpecObject);
};

export const updateMapWithMetadata = (yamlContent: YamlContent, refMap: Map<string, boolean>): void => {
	const metadata = yamlContent['metadata'] as Metadata_Ref;
	const keyParts = [];
	if (metadata.namespace) {
		keyParts.push(metadata.namespace);
	}
	if (metadata.name) {
		keyParts.push(metadata.name);
	}
	const version = convertNumberToString(metadata.version);
	if (metadata.version) {
		keyParts.push(version);
	}
	const key = keyParts.join(':');
	refMap.set(key, true);
	//logDebug('0310', 'Metadata', key);
};

export const convertNumberToString = (data: number | string): string => {
	if (typeof data === 'string') {
		return data.trim();
	} else if (Math.abs(data - Math.floor(data)) < 1e-7) {
		return data.toFixed(1);
	} else {
		return data.toString();
	}
};

export const isValidAsset = (yamlContent: YamlContent): boolean => {
	return !!(
		yamlContent?.kind &&
		yamlContent.kind.toLowerCase() !== AppConstants.TEST &&
		yamlContent.kind.toLowerCase() !== AppConstants.ASSERTION &&
		yamlContent.kind.toLowerCase() !== AppConstants.ENVIRONMENT &&
		yamlContent?.metadata?.name &&
		yamlContent?.metadata?.version &&
		yamlContent?.spec &&
		UpperCaseKinds.includes(yamlContent.kind.toUpperCase())
	);
};


const errorsArray: ErrorResponse[] = [];
export const addErrorToResponse = (errorCode: string, field: string, description: string) => {
	errorsArray.push({
		code: errorCode, field: field, description: description
	});
	//logDebug('0363', description);
};

export const constructErrorResponse = () => {
	const tempErrorsArray = [...errorsArray];
	errorsArray.length = 0;
	return {
		respCode: 400, message: 'Invalid Assets or Reference in the Zip', Endpoints: [], errors: tempErrorsArray
	};
};

export const createPathReferenceMap = async (buffer: Buffer): Promise<Map<string, boolean>> => {
	const zip = new JSZip();
	const refMap = new Map<string, boolean>();

	try {
		const zipContent = await zip.loadAsync(buffer);
		//logDebug('0308', `${Object.keys(zipContent.files).length}`);

		for (const fileName in zipContent.files) {
			const entry = zipContent.files[fileName];
			if (!entry.dir && (fileName.endsWith('.yaml') || fileName.endsWith('.yml')) && !fileName.includes('resources')) {
				const content = await entry.async('string');
				//logInfo('0353', fileName);

				try {
					const yamlContents = yaml.loadAll(content) as YamlContent[];
					for (const yamlContent of yamlContents) {
						extractPath(yamlContent, refMap);
					}
				} catch (err) {
					//logError('0013', 'parsing YAML', `${err}`);
				}
			}
		}
	} catch (err) {
		//logError('0013', 'loading ZIP', `${err}`);
	}

	return refMap;
};

export const extractPath = (yamlContent: YamlContent, refMap: Map<string, boolean>): void => {
	const buildPathRefMap = (obj: SpecObject) => {
		for (const key in obj) {
			const value = obj[key];
			if (key === '$path' && typeof value === 'string') {
				if (!refMap.has(value)) {
					refMap.set(path.normalize(value), false);
					//logDebug('0309', value);
				}
			} else if (typeof value === 'object' && value !== null) {
				buildPathRefMap(value);
			}
		}
	};
	const specOb = JSON.stringify(yamlContent.spec);
	buildPathRefMap(yaml.load(specOb) as SpecObject);
};

export const updatePathRefMap = async (buffer: Buffer, refMap: Map<string, boolean>) => {
	const zip = await JSZip.loadAsync(buffer);
	//logDebug('0003', 'Updating path references from resources directory.');

	zip.forEach((relativePath) => {
		if (relativePath.startsWith('resources/')) {
			const modifiedFileName = relativePath.replace('resources/', '');
			if (refMap.has(modifiedFileName)) {
				refMap.set(modifiedFileName, true);
				//logDebug('0310', 'Path', modifiedFileName);
			}
		}
	});
};

export const validateMinAssets = async (buffer: Buffer): Promise<boolean> => {
	const zip = new JSZip();
	let valid = false;
	try {
		const zipContent = await zip.loadAsync(buffer);
		//logDebug('0308', `${Object.keys(zipContent.files).length}`);

		for (const fileName in zipContent.files) {
			if (fileName.endsWith('.yaml') || fileName.endsWith('.yml')) {
				valid = true;
				break;
			}
		}

	} catch (err) {
		//logError('0013', 'loading zip with minimum assets', `${err}`);
		addErrorToResponse(AppConstants.VALIDATION_ERROR_CODE, 'ZIP_FILE', `Error loading zip with minimum assets: ${err}`);
		valid = false;
	}
	return valid;
};

const validatYamlContent = (yamlContents: YamlContent[], fileName: string): boolean => {
	let allValid = true;
	const assetValidator = new AssetValidator();
	for (const yamlContent of yamlContents) {
		if (yamlContent?.kind != null && !assetValidator.validateAssets(yamlContent)) {
			//logError('0361', fileName);
			addErrorToResponse(AppConstants.VALIDATION_ERROR_CODE, AppConstants.YAML_FILE, `Invalid YAML in file ${fileName}`);
			allValid = false;
		}
	}
	return allValid;
};

export const updateRefs = (yamlContent: YamlContent, versionMap: Map<string, boolean>): YamlContent => {
	const updateRef = (obj: SpecObject, verionMap: Map<string, boolean>) => {
		for (const key in obj) {
			const value = obj[key];
			if (key === '$ref' && typeof value === 'string') {
				if (!verionMap.get(value)) {
					obj[key] = processRef(value);
				}
				else {
					obj[key] = value;

				}
			} else if (typeof value === 'object' && value !== null) {
				updateRef(value, verionMap);
			}
		}
	};
	//@ts-expect-error: This error is expected because of proto object is of type any or undefined
	updateRef(yamlContent.spec, versionMap);

	return yamlContent;
};

export const processRef = (value: string) => {
	const parts = value.split(':');
	const numberValue = parseFloat(parts[parts.length - 1]);

	if (!isNaN(numberValue)) {
		parts[parts.length - 1] = convertNumberToString(numberValue);
	}
	return parts.join(':');
};

export function checkFileExtension(name: string): boolean {
	return name.endsWith('.yml') || name.endsWith('.yaml');
}

export function isRelativePath(file: string): boolean {
	return file.startsWith('./') || file.startsWith('../');
}