import { TestOptionsModel } from '../../model/studio/command-options/test-options-model.js';
import { executeDeployment, prepareGatewayJson } from '../../deployers/project/projects-deployer.js';
import { showError, showWarning, showInfo } from '../../helpers/common/message-helper.js';
import { buildAssets } from './build-action-helper.js';
import { processEndpointFromResponse,
	addEndpointToZip,
	testAssets,
	testProjects,
	combineTestAsset,
	createJSONBuffer,
	findProjectForApi,
	buildAndDeployAssets,
	updateEndpointZip,
	formattedEndpoints,
	APIEndpoints,
} from '../../helpers/apim/test-helper.js';
import { MULTIPLE_PROJECTS_NOT_ALLOWED,
	IGNORE_PROJECT_ARG,
	IGNORE_NAMES_OPT,
	ENDPOINT_TEST_SUCCESS,
	CREATED_TEST_ZIP,
	API_DETAILS_MISSING,
	RETRY_TEST_COMMAND,
	DEPLOYMENT_DETAILS_NOT_IDENTIFIED_LOCALLY,
	DEPLOYMENT_DOESNOT_OVERWRITE,
	ERROR_PROCESSING_ENDPOINT,
	ENDPOINT_ARGUMENT_NOT_AVAILABLE,
	NO_VALID_ENDPOINT_FOUND,
	MISMATCH_IN_API_AND_ENDPOINT,
	CREATED_BUILD_ZIP
 } from '../../constants/message-constants.js';
import { BUILD, COMMA, TEST } from '../../constants/app-constants.js';
import { GatewaysJson } from '@apic/studio-shared';
import { getGatewayEndpoints } from '../../configure/endpoints/config.js';
import { createBuildZip } from '../../helpers/common/fs-helper.js';
import {getOutputPath} from './../helpers/build-action-helper.js';
import validateEndpoint from '../../validators/endpoint-validator.js'
import { TestOutputBuffers } from '../../model/studio/test-response-model.js';

export const getGatewayJson = async (options: TestOptionsModel, gatewayPassword: string): Promise<GatewaysJson> => {
	const overwriteFlag = options.deploy;
    let is_mcsp_enabled=false;
    if(options.authToken)
    {
        is_mcsp_enabled=true;
    }
	return prepareGatewayJson(options.target, options.username, gatewayPassword, overwriteFlag,is_mcsp_enabled);
};

export const handleTestWarnings = (projects: string, options: TestOptionsModel) => {
	if (projects && options.all) {
		showWarning(IGNORE_PROJECT_ARG);
	}
	if (options.names && options.all) {
		showWarning(IGNORE_NAMES_OPT);
	}
};

const handleMultipleProjectsError = (projects: string): boolean => {
	const projectList = projects.split(COMMA);
	if (projectList.length > 1) {
		showError(MULTIPLE_PROJECTS_NOT_ALLOWED);
		return true;
	}
	return false;
};

const displayGatewayError = () => {
	showError(API_DETAILS_MISSING);
	showError(RETRY_TEST_COMMAND);
};

const displayDeploymentWarning = () => {
	showWarning(DEPLOYMENT_DETAILS_NOT_IDENTIFIED_LOCALLY);
	showWarning(DEPLOYMENT_DOESNOT_OVERWRITE);
};

const handleNotFoundApisForProjects = async (
	notFoundApis: string,
	apiReference: Record<string, string>,
	localDir: string,
	gatewayJson: GatewaysJson,
	finalZipBuffer: Buffer
): Promise<TestOutputBuffers>=> {
	const notFoundApisList = notFoundApis.split(',').map(api => api.trim());
	const projectApisMap = findProjectForApi(apiReference, notFoundApisList);
	const {buildBuffer, deploymentResult} = await buildAndDeployAssets(localDir, projectApisMap, gatewayJson);
	const newEndpointFile = processEndpointFromResponse(deploymentResult);
	const testBuffer = await updateEndpointZip(finalZipBuffer, newEndpointFile);
	return { testZipBuffer: testBuffer, buildZipBuffer: buildBuffer };

};

const handleNotFoundApisForAssets = async (
	notFoundApis: string,
	localDir: string,
	projects: string,
	gatewayJson: GatewaysJson,
	finalZipBuffer: Buffer
): Promise<TestOutputBuffers>=> {
	const zipBufferToBuild = await buildAssets(notFoundApis, localDir, projects);
	const deployResponses = await executeDeployment(gatewayJson, zipBufferToBuild);
	const newEndpointFile = processEndpointFromResponse(deployResponses);
	const testBuffer = await updateEndpointZip(finalZipBuffer, newEndpointFile);
	return { testZipBuffer: testBuffer, buildZipBuffer: zipBufferToBuild };

};

const deployAndAddEndpointForProjects = async (
	gatewayJson: GatewaysJson,
	localDir: string,
	finalZipBuffer: Buffer,
	ApiReference: Record<string, string>
): Promise<TestOutputBuffers>=> {
	const {buildBuffer, deploymentResult} = await buildAndDeployAssets(localDir,ApiReference,gatewayJson);
	const endpointFile = processEndpointFromResponse(deploymentResult);
	const testBuffer= addEndpointToZip(finalZipBuffer, endpointFile);

	return { testZipBuffer: testBuffer, buildZipBuffer: buildBuffer };

};

const deployAndAddEndpointForAssets = async (
	gatewayJson: GatewaysJson,
	localDir: string,
	apiReference: string,
	projects: string,
	testZipBuffer: Buffer
): Promise<TestOutputBuffers>=> {
	const zipBufferToBuild = await buildAssets(apiReference, localDir, projects);
	const deployResponses = await executeDeployment(gatewayJson, zipBufferToBuild);
	const endpointFile = processEndpointFromResponse(deployResponses);
	const testBuffer= addEndpointToZip(testZipBuffer, endpointFile);
	return { testZipBuffer: testBuffer, buildZipBuffer: zipBufferToBuild };

};

const handleFoundEndpoints = async (
	foundEndpoints: Record<string, any>,
	finalZipBuffer: Buffer
): Promise<TestOutputBuffers> => {
    const endpointFile = createJSONBuffer(foundEndpoints);
    const testBuffer = addEndpointToZip(finalZipBuffer, endpointFile);
	return { testZipBuffer: testBuffer, buildZipBuffer: undefined};
};

const hasGatewayDetails = (options: TestOptionsModel): boolean => {
    return Boolean(options.target && options.username && options.password);
};

export const handleDeploymentForProjects = async (
    options: TestOptionsModel,
    gatewayJson: GatewaysJson,
    localDir: string,
    finalZipBuffer: Buffer,
	ApiReference: Record<string, string>,
): Promise<TestOutputBuffers> => {

    if (!hasGatewayDetails(options)) {
        displayGatewayError();
		return { testZipBuffer: undefined, buildZipBuffer: undefined };
    }
    return deployAndAddEndpointForProjects(gatewayJson, localDir,finalZipBuffer,ApiReference);
};

export const handleDeploymentWarning = (options: TestOptionsModel): TestOutputBuffers | null => {
    displayDeploymentWarning();
    if (!hasGatewayDetails(options)) {
        displayGatewayError();
        return { testZipBuffer: undefined, buildZipBuffer: undefined };
    }
    return null;
};

const handleMissingEndpoints = async (
    options: TestOptionsModel,
    gatewayJson: GatewaysJson,
    localDir: string,
    outputBuffers: TestOutputBuffers,
    apiReference: any,
    projects?: string
): Promise<TestOutputBuffers> => {
    const warningResult = handleDeploymentWarning(options);
    if (warningResult)
        {
            return warningResult;
        }

    if (outputBuffers.testZipBuffer) {
        if (projects) {
            outputBuffers = await deployAndAddEndpointForAssets(gatewayJson, localDir, apiReference, projects, outputBuffers.testZipBuffer);
        } else {
            outputBuffers = await deployAndAddEndpointForProjects(gatewayJson, localDir, outputBuffers.testZipBuffer, apiReference);
        }
    }

    return outputBuffers;
};

async function processNotFoundApisForProjects(
    notFoundApis: string,
    apiReferencesString: string,
    gatewayJson: GatewaysJson,
    localDir: string,
    testZipBuffer: Buffer,
    apiReference: any
): Promise<TestOutputBuffers> {
    return (notFoundApis === apiReferencesString)
        ?  deployAndAddEndpointForProjects(gatewayJson, localDir, testZipBuffer, apiReference)
        :  handleNotFoundApisForProjects(notFoundApis, apiReference, localDir, gatewayJson, testZipBuffer);
}

export const handleNonDeploymentForProjects = async (
    apiReferencesString: string,
    options: TestOptionsModel,
    gatewayJson: GatewaysJson,
    localDir: string,
    finalZipBuffer: Buffer,
    apiReference: any,
): Promise<TestOutputBuffers> => {
    const result = await getGatewayEndpoints(apiReferencesString);
    let outputBuffers: TestOutputBuffers = { testZipBuffer: finalZipBuffer, buildZipBuffer: undefined };

    if (result) {
        const { foundEndpoints, notFoundApis } = result;

        if (Object.keys(foundEndpoints).length > 0 && outputBuffers.testZipBuffer) {
            formattedEndpoints(foundEndpoints);
            outputBuffers = await handleFoundEndpoints(foundEndpoints, outputBuffers.testZipBuffer);
        }

        if (notFoundApis.length > 0) {
            const warningResult = handleDeploymentWarning(options);
            if (warningResult)
                {
                    return warningResult;
                }

            if (outputBuffers.testZipBuffer) {
                outputBuffers = await processNotFoundApisForProjects(
                    notFoundApis,
                    apiReferencesString,
                    gatewayJson,
                    localDir,
                    outputBuffers.testZipBuffer,
                    apiReference
                );
            }
        }
    } else {
        outputBuffers = await handleMissingEndpoints(options, gatewayJson, localDir, outputBuffers, apiReference);
    }

    return outputBuffers;
};

async function processNotFoundApisForAssets(
    notFoundApis: string,
    apiReference: string,
    gatewayJson: GatewaysJson,
    localDir: string,
    projects: string,
    testZipBuffer: Buffer
): Promise<TestOutputBuffers> {
    return (notFoundApis === apiReference)
        ?  deployAndAddEndpointForAssets(gatewayJson, localDir, apiReference, projects, testZipBuffer)
        :  handleNotFoundApisForAssets(notFoundApis, localDir, projects, gatewayJson, testZipBuffer);
}

export const handleNonDeploymentForAssets = async (
    apiReference: string,
    options: TestOptionsModel,
    gatewayJson: GatewaysJson,
    localDir: string,
    projects: string,
    testZipBuffer: Buffer
): Promise<TestOutputBuffers> => {
    const result = await getGatewayEndpoints(apiReference);
    let outputBuffers: TestOutputBuffers = { testZipBuffer, buildZipBuffer: undefined };

    if (result) {
        const { foundEndpoints, notFoundApis } = result;

        if (Object.keys(foundEndpoints).length > 0 && outputBuffers.testZipBuffer) {
            formattedEndpoints(foundEndpoints);
            outputBuffers = await handleFoundEndpoints(foundEndpoints, testZipBuffer);
        }

        if (notFoundApis.length > 0) {
            const warningResult = handleDeploymentWarning(options);
            if (warningResult)
                {
                    return warningResult;
                }

            if (outputBuffers.testZipBuffer) {
                outputBuffers = await processNotFoundApisForAssets(
                    notFoundApis,
                    apiReference,
                    gatewayJson,
                    localDir,
                    projects,
                    outputBuffers.testZipBuffer
                );
            }
        }
    } else {
        outputBuffers = await handleMissingEndpoints(options, gatewayJson, localDir, outputBuffers, apiReference, projects);

    }

    return outputBuffers;
};


export const handleDeploymentForAssets = async (
    options: TestOptionsModel,
    gatewayJson: GatewaysJson,
    localDir: string,
    apiReference: any,
    projects: string,
    testZipBuffer: Buffer
): Promise<TestOutputBuffers> => {
    if (!hasGatewayDetails(options)) {
        displayGatewayError();
		return { testZipBuffer: undefined, buildZipBuffer: undefined };
    }
    return deployAndAddEndpointForAssets(gatewayJson, localDir, apiReference, projects, testZipBuffer);
};


export const handleTestAssets = async (options: TestOptionsModel, projects: string, localDir: string, gatewayJson: GatewaysJson): Promise<TestOutputBuffers>=> {
	if (handleMultipleProjectsError(projects)) {
		return { testZipBuffer: undefined, buildZipBuffer: undefined };
	}

	const { zipBuffer, apiReference } = await testAssets(localDir, projects, options.names);
	const testZipBuffer = zipBuffer;

	if (options.deploy) {
		return handleDeploymentForAssets(options, gatewayJson, localDir, apiReference, projects, testZipBuffer);
	} else {
		return handleNonDeploymentForAssets(apiReference, options, gatewayJson, localDir, projects, testZipBuffer);

	}
};

export const handleTestProjects = async (
    options: TestOptionsModel,
    projects: string,
    localDir: string,
    gatewayJson: GatewaysJson
): Promise<TestOutputBuffers> => {

    const testProject = await testProjects(options.all, localDir, projects);

    const { zipBuffer: testZipBuffer, apiReference } = await combineTestAsset(localDir, testProject);

    const apiReferencesString = Object.values(apiReference).join(',');

    const finalZipBuffer = testZipBuffer;

    if (options.deploy) {
        const { testZipBuffer: finalTestZipBuffer, buildZipBuffer: finalBuildZipBuffer } =
            await handleDeploymentForProjects(options, gatewayJson, localDir, finalZipBuffer, apiReference);
        return { testZipBuffer: finalTestZipBuffer, buildZipBuffer: finalBuildZipBuffer };
    } else {
        const { testZipBuffer: finalTestZipBuffer, buildZipBuffer: finalBuildZipBuffer } =
            await handleNonDeploymentForProjects(apiReferencesString, options, gatewayJson, localDir, finalZipBuffer, apiReference);
        return { testZipBuffer: finalTestZipBuffer, buildZipBuffer: finalBuildZipBuffer };
    }
};


const mapEndpoints = (apiReference: string, endpoints: string[]): APIEndpoints => {
    const apiReferences = apiReference.split(COMMA).map(ref => ref.trim());
    const mapped: APIEndpoints = {};

    // 1-1 mapping
    if (apiReferences.length === endpoints.length) {
        apiReferences.forEach((reference, index) => {
            mapped[reference] = [endpoints[index]];
        });
    }
    // n:1 mapping
    else if (endpoints.length === 1) {
        apiReferences.forEach(reference => {
            mapped[reference] = [endpoints[0]];
        });
    }
    // case n:m mapping - throw an error for mismatch
    else {
        throw new Error(MISMATCH_IN_API_AND_ENDPOINT);
    }

    return mapped;
};



export const validateEndpointOptions = (options: TestOptionsModel) => {
    if (options.endpoints && !options.names) {
        showError(ENDPOINT_ARGUMENT_NOT_AVAILABLE);
        return false;
    }
    return true;
};


export const testAssetsForEndpoint = async ( options: TestOptionsModel,project: string,localDir: string): Promise<TestOutputBuffers> => {
    if (handleMultipleProjectsError(project)) {
		return { testZipBuffer: undefined, buildZipBuffer: undefined };
    }

    const { zipBuffer, apiReference } = await testAssets(localDir, project, options.names);
    const testZipBuffer = zipBuffer;

    try {
        const processedEndpoints = await validateEndpoint(options.endpoints);
        if (processedEndpoints === false) {
            throw new Error(NO_VALID_ENDPOINT_FOUND);
        }
        const mappedEndpoints = mapEndpoints(apiReference, processedEndpoints);

        if (Object.keys(mappedEndpoints).length === 0) {
			return { testZipBuffer: undefined, buildZipBuffer: undefined };
        }

        const endpointBuffer = createJSONBuffer(mappedEndpoints);
        if (!endpointBuffer || endpointBuffer.length === 0) {
			return { testZipBuffer: undefined, buildZipBuffer: undefined };
        }

        const testBuffer =  addEndpointToZip(testZipBuffer, endpointBuffer);

		return { testZipBuffer: testBuffer, buildZipBuffer: undefined };

	} catch (error) {
		showError(` ${ERROR_PROCESSING_ENDPOINT} ${(error as Error).message}`);
		return { testZipBuffer: undefined, buildZipBuffer: undefined };
	}
};


export const writeArchive = async (projects: string, all: boolean, names: string, testZipBuffer: Buffer, buildZipBuffer: Buffer| undefined) => {
    showInfo(ENDPOINT_TEST_SUCCESS);
	const buildZipFileName = await getOutputPath(projects, all, names, BUILD);
    const testZipFileName = await getOutputPath(projects, all, names, TEST);
    createBuildZip(testZipBuffer, testZipFileName);
		showInfo(CREATED_TEST_ZIP + testZipFileName);
		if(buildZipBuffer){
	createBuildZip(buildZipBuffer, buildZipFileName);
		showInfo(CREATED_BUILD_ZIP + buildZipFileName);
		}
};

