import path from 'path';
import AdmZip from 'adm-zip';
import {showError, showInfo} from '../common/message-helper.js';
import { readMultiYaml } from '../common/yaml-helper.js';
import { readFile } from '../common/fs-helper.js';
import {searchAssetByKind} from './asset-searchByKind-helper.js';
import { getAllProjectNames } from './root-dir-helper.js';
import Table from 'cli-table3';
import { GatewayResponseAPI } from '../../model/studio/deploy-response-model.js';
import {
	INVALID_OR_EMPTY_DEPLOYMENT_RESPONSES,
	NO_VALID_API_ASSET_FOUND,
	ADDING_ENDPOINT_FILE_FAILED,
	TESTING_LIST_ALL_PROJECTS,
	CHECKING_ALL_PROJECTS,
	IDENTIFIED_PROJECTS,
	FAILED_TO_BUILD_TEST_ASSETS,
	ERROR_IN_COMBINING_TEST_ASSET,
	LINE,
	TEST_STARTED,
	PROCESS_ENDPOINT_RESPONSE,
	PROCESS_GATEWAY_RESPONSE,
	PROCESS_ENDPOINT_RESPONSE_SUCCESS,
	ENDPOINT_TEST,
	COMBINING_TEST_ZIP,
	COMBINING_TEST_ZIP_SUCCESS,
	BUILDING_TEST,
	BUILDING_TEST_SUCCESS,
	JSON_FILE_MISSING,
	UPDATE_ENDPOINT_FAILED,
	ASSETS_BUILD_FAILED,
	APIENDPOINTS,
} from '../../constants/message-constants.js';
import { ENDPOINT_FILE, TEST } from '../../constants/app-constants.js';
import { Test } from '@apic/api-model/test/Test.js';
import {BuildTestAssetsResult,executeBuildTestAssets} from '../../testers/project/projects-asset-testers.js';
import { DebugManager } from '../../debug/debug-manager.js';
import { KindEnums } from '@apic/api-model/common/StudioEnums.js';
import { buildAssets } from '../../actions/helpers/build-action-helper.js';
import { executeDeployment } from '../../deployers/project/projects-deployer.js';
import { GatewaysJson } from '@apic/studio-shared';

export type APIEndpoints = Record<string, string[]>;

/**
 * @param jsonObject - The JSON object to be constructed.
 * @param key - The key to add to the JSON object.
 * @param value - The array of endpoints to add.
 */

export interface  BuildTestProjectsResult {
    zipBuffer: Buffer;
    apiReference: Record<string,string>;
}

const constructJSON = (jsonObject: APIEndpoints,key: string,value: string[]): void => {
	jsonObject[key] = value;
};

export const constructKey = (response: GatewayResponseAPI): string => {
	return `${response.namespace}:${response.assetName}:${response.version}`;
};

export const createJSONBuffer = (apiGatewayEndpoints: APIEndpoints): Buffer => {
	return Buffer.from(JSON.stringify(apiGatewayEndpoints, null, 2), 'utf-8');
};

export const getAPIRefToBuild = (testFilePath: string): string[] | null => {
	const apiReferences : string[] = [];
	const parentDirPath = path.dirname(testFilePath);
	const testFileName = path.basename(testFilePath);
	const yamlContent = readFile(parentDirPath, testFileName);
	const parsedYamls = readMultiYaml<Test>(testFileName, yamlContent);
	parsedYamls.forEach((parsedYaml) => {
		const ref = parsedYaml?.spec?.api?.$ref;
		if (parsedYaml.kind?.toLowerCase() === TEST && ref) {
			apiReferences.push(ref);
		}
	})
	return apiReferences;
};

export const processEndpointFromResponse = (responses: GatewayResponseAPI[]): Buffer => {
	const apiGatewayEndpoints: APIEndpoints = {};
	if(DebugManager.getInstance().isDebugEnabled()){
		showInfo(PROCESS_ENDPOINT_RESPONSE);
	}
	if (!Array.isArray(responses) || responses.length === 0) {
		showError(INVALID_OR_EMPTY_DEPLOYMENT_RESPONSES);
		throw new Error(INVALID_OR_EMPTY_DEPLOYMENT_RESPONSES);
	}

	responses.forEach((response) => {
		if(DebugManager.getInstance().isDebugEnabled()){
			showInfo(`${PROCESS_GATEWAY_RESPONSE}${response.name}`);
		}
		const key = constructKey(response);
		if (apiGatewayEndpoints[key]) {
			apiGatewayEndpoints[key] = [
				...new Set([...apiGatewayEndpoints[key], ...response.gatewayEndpoints]),
			];
		} else {
			constructJSON(apiGatewayEndpoints, key, response.gatewayEndpoints);
		}
	});
	if(DebugManager.getInstance().isDebugEnabled()){
		showInfo(PROCESS_ENDPOINT_RESPONSE_SUCCESS);
	}
	return createJSONBuffer(apiGatewayEndpoints);
};

export const addEndpointToZip = (testZipBuffer: Buffer,endpointFile: Buffer): Buffer => {
	try {
		if(DebugManager.getInstance().isDebugEnabled()){
			showInfo(ENDPOINT_TEST);
		}
		const testZipBufferWithEndpoint = new AdmZip(testZipBuffer);
		testZipBufferWithEndpoint.addFile(ENDPOINT_FILE, endpointFile);
		return testZipBufferWithEndpoint.toBuffer();
	} catch (error) {
		showError(ADDING_ENDPOINT_FILE_FAILED);
		throw new Error(
			`${ADDING_ENDPOINT_FILE_FAILED} ${(error as Error).message}`
		);
	}
};

const addFileToZip = (combinedZip:  AdmZip, entry: AdmZip.IZipEntry) => {
	if (!combinedZip.getEntry(entry.entryName)) {
		combinedZip.addFile(entry.entryName, entry.getData());
	}
};


const displayErrorMessage = (result: BuildTestAssetsResult, project: string) => {
	if (!result || !result.zipBuffer) {
		showError(`${FAILED_TO_BUILD_TEST_ASSETS} ${project}`);
		throw new Error(`${FAILED_TO_BUILD_TEST_ASSETS} ${project}`);
	}
};

export const updateEndpointZip = async (finalZipBuffer: Buffer, newEndpoints: Buffer): Promise<Buffer> => {
	try {
		const zip = new AdmZip(finalZipBuffer);
		const jsonEntry = zip.getEntry(ENDPOINT_FILE);

		if (!jsonEntry) {
			throw new Error(JSON_FILE_MISSING);
		}

		const existingEndpointsBuffer = jsonEntry.getData();
		const existingEndpoints: APIEndpoints = JSON.parse(existingEndpointsBuffer.toString('utf-8'));
		const newEndpointsData: APIEndpoints = JSON.parse(newEndpoints.toString('utf-8'));

		const updatedEndpoints = { ...existingEndpoints, ...newEndpointsData };
		const updatedEndpointsBuffer = createJSONBuffer(updatedEndpoints);
		zip.updateFile(ENDPOINT_FILE, updatedEndpointsBuffer);

		return zip.toBuffer();
	} catch (error) {
		showError(`${UPDATE_ENDPOINT_FAILED} ${(error as Error).message}`);
		throw new Error(`${UPDATE_ENDPOINT_FAILED} ${(error as Error).message}`);
	}
};

export const combineTestAsset = async (rootDir: string, assetsToTest: Record<string, string>): Promise<BuildTestProjectsResult> => {
	try {
		if (DebugManager.getInstance().isDebugEnabled()) {
			showInfo(COMBINING_TEST_ZIP);
		}

		const combinedZip = new AdmZip();
		const apiReferences: Record<string, string> = {};

		for (const project in assetsToTest) {
			if (Object.prototype.hasOwnProperty.call(assetsToTest, project)) {
				if (DebugManager.getInstance().isDebugEnabled()) {
					showInfo(`${BUILDING_TEST} ${project}`);
				}

				const metadata = assetsToTest[project];
				const result = await testAssets(rootDir, project, metadata);
				displayErrorMessage(result, project);

				if (DebugManager.getInstance().isDebugEnabled()) {
					showInfo(`${BUILDING_TEST_SUCCESS} ${project}`);
				}

				const projectZip = new AdmZip(result.zipBuffer);
				projectZip.getEntries().forEach((entry) => {
					addFileToZip(combinedZip, entry);
				});
				apiReferences[project] = result.apiReference;
			}
		}

		if (DebugManager.getInstance().isDebugEnabled()) {
			showInfo(COMBINING_TEST_ZIP_SUCCESS);
		}

		return {
			zipBuffer: combinedZip.toBuffer(),
			apiReference: apiReferences
		};
	} catch (error) {
		showError(`${ERROR_IN_COMBINING_TEST_ASSET} ${(error as Error).message}`);
		throw error;
	}
};

export const findProjectForApi = (apiReference: Record<string, string>, notFoundApisList: string[]): Record<string, string> => {
	const apiReferenceMap: Record<string, string> = {};

	for (const [project, reference] of Object.entries(apiReference)) {
		const apiList = reference.split(',').map(api => api.trim());
		for (const api of notFoundApisList) {
			if (apiList.includes(api)) {
				apiReferenceMap[project] = apiReferenceMap[project]
					? `${apiReferenceMap[project]},${api}`
					: api;
			}
		}
	}

	return apiReferenceMap;
};


export const buildAndDeployAssets = async (
    localDir: string,
    assetsToBuildAndDeploy: Record<string, string>,
    gatewayJson: GatewaysJson
): Promise<{ buildBuffer: Buffer; deploymentResult: GatewayResponseAPI[] }> => {
	const combinedZip = new AdmZip();

	for (const [project, metadata] of Object.entries(assetsToBuildAndDeploy)) {
		try {
			const resultBuffer = await buildAssets(metadata, localDir, project);
			const projectZip = new AdmZip(resultBuffer);
			projectZip.getEntries().forEach((entry) => {
				combinedZip.addFile(entry.entryName, entry.getData());
			});
		} catch (error) {
			showError(`${ASSETS_BUILD_FAILED} ${project}: ${(error as Error).message}`);
		}
	}
	const combinedZipBuffer = combinedZip.toBuffer();
	const deploymentResult = await executeDeployment(gatewayJson, combinedZipBuffer);

    return {
        buildBuffer: combinedZipBuffer,
        deploymentResult
    };
};

export const testProjects = async (all: boolean, rootDir: string,projects: string): Promise<Record<string, string>> => {
	if (all) {
		showInfo(TESTING_LIST_ALL_PROJECTS + rootDir);
		showInfo(CHECKING_ALL_PROJECTS + rootDir);
		projects = getAllProjectNames(rootDir);
		showInfo(IDENTIFIED_PROJECTS + projects);
	}
	return searchAssetByKind(KindEnums.Test,rootDir, projects);
};

export const testAssets = (rootDir: string,projects: string,assets: string): Promise<BuildTestAssetsResult> => {
	showInfo(LINE);
	showInfo(TEST_STARTED);
	showInfo(LINE);
	return executeBuildTestAssets(rootDir, projects, assets);
};


export const formattedEndpoints = (endpoints: Record<string, string[]>): void => {
    const endPointsTable = new Table({
        head: ['APIs', 'Gateway Endpoints'],
        style: {
            head: ['blue'],
            border: ['yellow'],
        },
    });
    Object.entries(endpoints).forEach(([api, urls]) => {
        endPointsTable.push([api, urls.join('\n')]);
    });
	showInfo(APIENDPOINTS);
    console.log(endPointsTable.toString());
};

