import { DeleteLayerVersionCommand, LambdaClient, ListLayerVersionsCommand, PublishLayerVersionCommand, UpdateFunctionConfigurationCommand, GetFunctionCommand, ListLayersCommand, } from '@aws-sdk/client-lambda';
import { IAMClient, GetRolePolicyCommand, PutRolePolicyCommand, DeleteRolePolicyCommand, } from '@aws-sdk/client-iam';
import { getVersion } from './version.mjs';
import fs from 'fs/promises';
import * as path from 'path';
import { Configuration } from './configuration.mjs';
import { AwsCredentials } from './awsCredentials.mjs';
import { getModuleDirname } from './getDirname.mjs';
import { Logger } from './logger.mjs';
import * as crypto from 'crypto';
let lambdaClient;
let iamClient;
const inlinePolicyName = 'LambdaLiveDebuggerPolicy';
const layerName = 'LambdaLiveDebugger';
const lldWrapperPath = '/opt/lld-wrapper';
let layerDescription;
/**
 * Policy document to attach to the Lambda role
 */
const policyDocument = {
    Version: '2012-10-17',
    Statement: [
        {
            Effect: 'Allow',
            Action: [
                'iot:DescribeEndpoint',
                'iot:Connect',
                'iot:Publish',
                'iot:Subscribe',
                'iot:Receive',
            ],
            Resource: '*',
        },
    ],
};
/**
 * Get the Lambda client
 * @returns
 */
function getLambdaClient() {
    if (!lambdaClient) {
        lambdaClient = new LambdaClient({
            region: Configuration.config.region,
            credentials: AwsCredentials.getCredentialsProvider({
                region: Configuration.config.region,
                profile: Configuration.config.profile,
                role: Configuration.config.role,
            }),
        });
    }
    return lambdaClient;
}
/**
 * Get the IAM client
 * @returns
 */
function getIAMClient() {
    if (!iamClient) {
        iamClient = new IAMClient({
            region: Configuration.config.region,
            credentials: AwsCredentials.getCredentialsProvider({
                region: Configuration.config.region,
                profile: Configuration.config.profile,
                role: Configuration.config.role,
            }),
        });
    }
    return iamClient;
}
/**
 * Find an existing layer
 * @returns
 */
async function findExistingLayerVersion() {
    let nextMarker;
    const layerDescription = await getLayerDescription();
    do {
        const listLayerVersionsCommand = new ListLayerVersionsCommand({
            LayerName: layerName,
            Marker: nextMarker,
        });
        const response = await getLambdaClient().send(listLayerVersionsCommand);
        if (response.LayerVersions && response.LayerVersions.length > 0) {
            const matchingLayer = response.LayerVersions.find((layer) => layer.Description === layerDescription);
            if (matchingLayer) {
                Logger.verbose(`Matching layer version: ${matchingLayer.Version}, description: ${matchingLayer.Description}`);
                return matchingLayer;
            }
        }
        nextMarker = response.NextMarker;
    } while (nextMarker);
    Logger.verbose(`No matching layer version found with description ${layerDescription}`);
    return undefined;
}
/**
 * Get the description of the Lambda layer that is set to the layer
 * @returns
 */
async function getLayerDescription() {
    if (!layerDescription) {
        layerDescription = `Lambda Live Debugger layer version ${await getVersion()}`;
    }
    if ((await getVersion()) === '0.0.1') {
        // add a random string to the description to make it unique
        layerDescription = `Lambda Live Debugger layer - development ${crypto.randomUUID()}`;
    }
    return layerDescription;
}
/**
 * Deploy the Lambda Layer
 * @returns
 */
async function deployLayer() {
    const layerDescription = await getLayerDescription();
    // check the ZIP
    let layerZipPathFullPath = path.resolve(path.join(getModuleDirname(), './extension/extension.zip'));
    // get the full path to the ZIP file
    try {
        await fs.access(layerZipPathFullPath);
    }
    catch {
        // if I am debugging
        const layerZipPathFullPath2 = path.join(getModuleDirname(), '../dist/extension/extension.zip');
        try {
            await fs.access(layerZipPathFullPath2);
            layerZipPathFullPath = layerZipPathFullPath2;
        }
        catch {
            throw new Error(`File for the layer not found: ${layerZipPathFullPath}.`);
        }
    }
    Logger.verbose(`Layer ZIP path: ${layerZipPathFullPath}`);
    // Read the ZIP file containing your layer code
    const layerContent = await fs.readFile(layerZipPathFullPath);
    Logger.verbose(`Deploying ${layerDescription}`);
    // Create the command for publishing a new layer version
    const publishLayerVersionCommand = new PublishLayerVersionCommand({
        LayerName: layerName,
        Description: layerDescription,
        Content: {
            ZipFile: layerContent,
        },
        CompatibleArchitectures: ['x86_64', 'arm64'],
        CompatibleRuntimes: ['nodejs18.x', 'nodejs20.x'],
    });
    const response = await getLambdaClient().send(publishLayerVersionCommand);
    if (!response.LayerVersionArn) {
        throw new Error('Failed to retrieve the layer version ARN.');
    }
    Logger.verbose(`Deployed ${response.Description} ARN: ${response.LayerVersionArn}`);
    return response.LayerVersionArn;
}
/**
 * Delete the Lambda layer
 */
async function deleteLayer() {
    let nextMarker;
    do {
        const layers = await getLambdaClient().send(new ListLayersCommand({
            Marker: nextMarker,
        }));
        // Filter layers by name
        const targetLayers = layers.Layers?.filter((layer) => layer.LayerName === layerName) || [];
        for (const layer of targetLayers) {
            await deleteAllVersionsOfLayer(layer.LayerArn);
        }
        nextMarker = layers.NextMarker;
    } while (nextMarker);
}
/**
 * Delete all versions of a layer
 * @param layerArn
 */
async function deleteAllVersionsOfLayer(layerArn) {
    let nextMarker;
    do {
        const versions = await getLambdaClient().send(new ListLayerVersionsCommand({
            LayerName: layerArn,
            Marker: nextMarker,
        }));
        for (const version of versions.LayerVersions || []) {
            await deleteLayerVersion(layerArn, version.Version);
        }
        nextMarker = versions.NextMarker;
    } while (nextMarker);
}
/**
 * Delete a specific version of a layer
 * @param layerArn
 * @param versionNumber
 */
async function deleteLayerVersion(layerArn, versionNumber) {
    try {
        Logger.verbose(`Deleting version ${versionNumber} of layer ${layerArn}`);
        await getLambdaClient().send(new DeleteLayerVersionCommand({
            LayerName: layerArn,
            VersionNumber: versionNumber,
        }));
    }
    catch (error) {
        Logger.error(`Error deleting version ${versionNumber} of layer ${layerArn}:`, error);
        throw error;
    }
}
/**
 * Get the role name from a Lambda function
 * @param functionName
 * @returns role name
 */
async function getRoleNameFromFunction(functionName) {
    try {
        Logger.verbose(`[Function ${functionName}] Getting role from function`);
        const getFunctionResponse = await getLambdaClient().send(new GetFunctionCommand({
            FunctionName: functionName,
        }));
        const roleArn = getFunctionResponse.Configuration?.Role;
        if (!roleArn) {
            throw new Error(`Failed to retrieve the role ARN for lambda ${functionName}.`);
        }
        // Extract the role name from the role ARN
        const roleName = roleArn.split('/').pop();
        if (!roleName) {
            throw new Error(`Failed to extract role name from role ARN: ${roleArn} for lambda ${functionName}.`);
        }
        Logger.verbose(`[Function ${functionName}] Found role: ${roleName}`);
        return roleName;
    }
    catch (error) {
        throw new Error(`Failed to get role name from function ${functionName}.`, {
            cause: error,
        });
    }
}
/**
 * Check if policy needs to be removed from the Lambda role
 * @param roleName
 * @returns
 */
async function analyzeRoleRemove(roleName) {
    try {
        Logger.verbose(`[Role ${roleName}] Analyzing policy removal from Lambda role`);
        const existingPolicy = await createPolicyDocument(roleName);
        const needToRemovePolicy = !!existingPolicy;
        Logger.verbose(`[Role ${roleName}] Policy ${needToRemovePolicy ? 'needs to be removed' : 'not found to remove'} from role ${roleName}`);
        return {
            needToRemovePolicy,
            roleName,
        };
    }
    catch (error) {
        throw new Error(`Failed to analyze removal policy from role ${roleName}.`, {
            cause: error,
        });
    }
}
/**
 * Remove the policy from the Lambda role
 * @param roleData
 * @returns
 */
async function removePolicyFromLambdaRole(roleName) {
    Logger.verbose(`[Role ${roleName}] Removing policy from the role`);
    try {
        await getIAMClient().send(new DeleteRolePolicyCommand({
            RoleName: roleName,
            PolicyName: inlinePolicyName,
        }));
    }
    catch (error) {
        throw new Error(`Failed to remove policy from the role ${roleName}.`, {
            cause: error,
        });
    }
}
/**
 * Create policy document needed to attach to the Lambda role needed for the Lambda Live Debugger
 * @param roleName
 * @returns
 */
async function createPolicyDocument(roleName) {
    try {
        Logger.verbose(`[Role ${roleName}] Checking for existing policy document`);
        const policy = await getIAMClient().send(new GetRolePolicyCommand({
            RoleName: roleName,
            PolicyName: inlinePolicyName,
        }));
        if (policy.PolicyDocument) {
            Logger.verbose(`[Role ${roleName}] Found existing policy document`);
            const policyDocument = JSON.parse(decodeURIComponent(policy.PolicyDocument));
            return policyDocument;
        }
        else {
            Logger.verbose(`[Role ${roleName}] No policy document found`);
            return undefined;
        }
    }
    catch (error) {
        if (error.name === 'NoSuchEntityException') {
            Logger.verbose(`[Role ${roleName}] Policy does not exist`);
            return undefined;
        }
        else {
            throw new Error(`Failed to create policy document for role ${roleName}.`, { cause: error });
        }
    }
}
/**
 * Deploy the infrastructure
 */
async function applyAddingInfra(changes) {
    Logger.verbose('Starting infrastructure deployment for adding Lambda Live Debugger');
    let layerVersionArn;
    if (changes.deployLayer) {
        Logger.verbose('Deploying new layer version');
        layerVersionArn = await deployLayer();
    }
    else {
        if (!changes.existingLayerVersionArn) {
            throw new Error('Expected existing layer ARN but none provided.');
        }
        Logger.verbose(`Using existing layer version: ${changes.existingLayerVersionArn}`);
        layerVersionArn = changes.existingLayerVersionArn;
    }
    const promises = [];
    // Add LLD to functions
    for (const lambdaData of changes.lambdasToAdd) {
        promises.push(addLayerToLambda({
            ...lambdaData,
            layers: [
                layerVersionArn,
                // remove LLD layer if exist
                ...lambdaData.layers.filter((arn) => !arn.includes(`:layer:${layerName}:`)),
            ],
        }));
    }
    // Remove LLD from filtered functions
    for (const lambdaData of changes.lambdasToRemove) {
        promises.push(removeLayerFromLambda(lambdaData));
    }
    // Add policies to roles
    for (const roleName of changes.rolesToAdd) {
        promises.push(addPolicyToRole(roleName));
    }
    // Remove policies from roles
    for (const roleName of changes.rolesToRemove) {
        promises.push(removePolicyFromLambdaRole(roleName));
    }
    await Promise.all(promises);
}
/**
 * Get the planned infrastructure changes including removal from filtered functions
 */
async function getInfraChangesForAdding() {
    Logger.verbose('Analyzing infrastructure changes for adding Lambda Live Debugger');
    const existingLayer = await findExistingLayerVersion();
    const configLambdasAll = Configuration.getLambdasAll();
    const configLambdasUpdate = configLambdasAll.filter((l) => !(l.filteredOut === true));
    const configLambdasRemove = configLambdasAll.filter((l) => l.filteredOut === true);
    const lambdasToUpdatePromise = Promise.all(configLambdasUpdate.map(async (func) => {
        const lambdaUpdate = await analyzeLambdaAdd(func.functionName, existingLayer?.LayerVersionArn);
        return lambdaUpdate;
    }));
    const lambdasToRemovePromise = Promise.all(configLambdasRemove.map(async (func) => {
        return analyzeLambdaRemove(func.functionName);
    }));
    // Get all role names for lambdas to update, ensure uniqueness, then analyze
    const roleNamesToAddSet = new Set();
    const roleNamesToAddPromise = Promise.all(configLambdasUpdate.map(async (func) => {
        const roleName = await getRoleNameFromFunction(func.functionName);
        roleNamesToAddSet.add(roleName);
    }));
    // Get all role names for lambdas to remove, ensure uniqueness, then analyze
    const roleNamesToRemoveSet = new Set();
    const roleNamesToRemovePromise = Promise.all(configLambdasRemove.map(async (func) => {
        const roleName = await getRoleNameFromFunction(func.functionName);
        roleNamesToRemoveSet.add(roleName);
    }));
    // Analyze roles to add
    await roleNamesToAddPromise;
    const roleNamesToAdd = Array.from(roleNamesToAddSet);
    const rolesToAddPromise = Promise.all(roleNamesToAdd.map(async (roleName) => {
        const roleUpdate = await analyzeRoleAdd(roleName);
        return roleUpdate.addPolicy ? roleUpdate.roleName : undefined;
    }));
    // Analyze roles to remove
    await roleNamesToRemovePromise;
    let roleNamesToRemove = Array.from(roleNamesToRemoveSet);
    // make sure that roles removed are not in the list to add
    roleNamesToRemove = roleNamesToRemove.filter((role) => !roleNamesToAdd.includes(role));
    const rolesToRemovePromise = Promise.all(roleNamesToRemove.map(async (roleName) => {
        const roleRemoval = await analyzeRoleRemove(roleName);
        return roleRemoval.needToRemovePolicy ? roleRemoval.roleName : undefined;
    }));
    const lambdasToUpdate = await lambdasToUpdatePromise;
    const lambdasToAddFiltered = lambdasToUpdate.filter((l) => l);
    const rolesToAdd = await rolesToAddPromise;
    const rolesToAddFiltered = [
        ...new Set(rolesToAdd.filter((r) => r)),
    ];
    const lambdasToRemove = await lambdasToRemovePromise;
    const lambdasToRemoveFiltered = lambdasToRemove.filter((l) => l);
    const rolesToRemove = await rolesToRemovePromise;
    const rolesToRemoveFiltered = rolesToRemove.filter((r) => r);
    return {
        deployLayer: !existingLayer,
        existingLayerVersionArn: existingLayer?.LayerVersionArn,
        lambdasToAdd: lambdasToAddFiltered,
        rolesToAdd: rolesToAddFiltered,
        lambdasToRemove: lambdasToRemoveFiltered,
        rolesToRemove: rolesToRemoveFiltered,
    };
}
/**
 * Check what needs to be removed from a Lambda function
 * @param func - Lambda function properties
 * @returns Lambda update configuration or undefined if no update needed
 */
async function analyzeLambdaRemove(functionName) {
    try {
        const { environmentVariables, ddlLayerArns, otherLayerArns, initialTimeout, } = await getLambdaConfiguration(functionName);
        const needToRemoveLayer = ddlLayerArns.length > 0;
        let needToRemoveEnvironmentVariables = false;
        if (needToRemoveLayer) {
            Logger.verbose(`[Function ${functionName}] Lambda Live Debugger layer(s) detected: ${ddlLayerArns.join(', ')}. Marked for removal.`);
        }
        else {
            Logger.verbose(`[Function ${functionName}] No Lambda Live Debugger layer(s) to remove.`);
        }
        const ddlEnvironmentVariables = getEnvironmentVariablesForDebugger({
            // set dummy data, so we just get the list of environment variables
            functionName: 'xxx',
            timeout: 0,
            verbose: true,
            initialExecWrapper: 'test',
        });
        // check if environment variables are set for each property
        for (const [key] of Object.entries(ddlEnvironmentVariables)) {
            if (environmentVariables && environmentVariables[key]) {
                needToRemoveEnvironmentVariables = true;
                break;
            }
        }
        Logger.verbose(`[Function ${functionName}] ${needToRemoveEnvironmentVariables ? 'Environment variables needed to be removed' : 'No environment variables to remove'}. Existing environment variables: ` +
            JSON.stringify(environmentVariables, null, 2));
        const needToRemove = needToRemoveLayer || needToRemoveEnvironmentVariables;
        if (needToRemove) {
            const initialExecWrapper = environmentVariables.LLD_INITIAL_AWS_LAMBDA_EXEC_WRAPPER;
            const ddlEnvironmentVariables = getEnvironmentVariablesForDebugger({
                functionName: 'xxx',
                timeout: 0,
                verbose: true,
                initialExecWrapper: 'test',
            });
            // Remove LLD environment variables
            const cleanedEnvironmentVariables = { ...environmentVariables };
            for (const [key] of Object.entries(ddlEnvironmentVariables)) {
                if (key === 'AWS_LAMBDA_EXEC_WRAPPER') {
                    if (cleanedEnvironmentVariables[key] === lldWrapperPath) {
                        delete cleanedEnvironmentVariables[key];
                    }
                }
                else {
                    delete cleanedEnvironmentVariables[key];
                }
            }
            if (initialExecWrapper) {
                cleanedEnvironmentVariables.AWS_LAMBDA_EXEC_WRAPPER =
                    initialExecWrapper;
            }
            return {
                functionName,
                layers: otherLayerArns,
                environmentVariables: cleanedEnvironmentVariables,
                timeout: initialTimeout,
            };
        }
        return undefined;
    }
    catch (error) {
        throw new Error(`Failed to analyze removal from lambda ${functionName}.`, {
            cause: error,
        });
    }
}
/**
 * Get the planned removal changes
 */
async function getInfraChangesForRemoving() {
    Logger.verbose('Analyzing infrastructure changes for removing Lambda Live Debugger');
    const allLambdas = Configuration.getLambdasAll();
    const lambdasToRemovePromise = Promise.all(allLambdas.map(async (func) => {
        return analyzeLambdaRemove(func.functionName);
    }));
    // Get all role names for lambdas to remove, ensure uniqueness, then analyze
    const roleNamesToRemoveSet = new Set();
    await Promise.all(allLambdas.map(async (func) => {
        const roleName = await getRoleNameFromFunction(func.functionName);
        roleNamesToRemoveSet.add(roleName);
    }));
    const roleNamesToRemove = Array.from(roleNamesToRemoveSet);
    const rolesToRemovePromise = Promise.all(roleNamesToRemove.map(async (roleName) => {
        const roleRemoval = await analyzeRoleRemove(roleName);
        return roleRemoval.needToRemovePolicy ? roleRemoval.roleName : undefined;
    }));
    const lambdasToRemove = await lambdasToRemovePromise;
    const lambdasToRemoveFiltered = lambdasToRemove.filter((l) => l);
    const rolesToRemove = await rolesToRemovePromise;
    const rolesToRemoveFiltered = rolesToRemove.filter((r) => r);
    return {
        lambdasToRemove: lambdasToRemoveFiltered,
        rolesToRemove: rolesToRemoveFiltered,
    };
}
/**
 * Remove the infrastructure
 */
async function applyRemoveInfra(changes) {
    Logger.verbose('Starting infrastructure removal');
    const promises = [];
    for (const lambdaData of changes.lambdasToRemove) {
        promises.push(removeLayerFromLambda(lambdaData));
    }
    for (const roleName of changes.rolesToRemove) {
        promises.push(removePolicyFromLambdaRole(roleName));
    }
    await Promise.all(promises);
}
/**
 * Get the Lambda function configuration including layers, environment variables, and timeout
 * @param functionName - The name of the Lambda function
 * @returns Lambda configuration details
 */
async function getLambdaConfiguration(functionName) {
    try {
        const getFunctionResponse = await getLambdaClient().send(new GetFunctionCommand({
            FunctionName: functionName,
        }));
        const timeout = getFunctionResponse.Configuration?.Timeout;
        // get all layers this function has by name
        const layers = getFunctionResponse.Configuration?.Layers || [];
        const layerArns = layers.map((l) => l.Arn).filter((arn) => arn);
        const ddlLayerArns = layerArns.filter((arn) => arn?.includes(`:layer:${layerName}:`));
        const otherLayerArns = layerArns.filter((arn) => !arn?.includes(`:layer:${layerName}:`));
        const environmentVariables = getFunctionResponse.Configuration?.Environment?.Variables ?? {};
        let initialTimeout;
        const initialTimeoutStr = environmentVariables?.LLD_INITIAL_TIMEOUT;
        if (!initialTimeoutStr || isNaN(Number(initialTimeoutStr))) {
            initialTimeout = timeout;
        }
        else {
            initialTimeout = Number(initialTimeoutStr);
        }
        return {
            environmentVariables,
            ddlLayerArns,
            otherLayerArns,
            initialTimeout,
        };
    }
    catch (error) {
        throw new Error(`Failed to get lambda configuration ${functionName}.`, {
            cause: error,
        });
    }
}
/**
 * Attach the layer to the Lambda function and update the environment variables
 * @param lambdaData
 */
async function addLayerToLambda(lambdaData) {
    Logger.verbose(`[Function ${lambdaData.functionName}] Adding layer and environment variables`);
    try {
        await updateLambda(lambdaData);
    }
    catch (error) {
        throw new Error(`Failed to update add layer to lambda ${lambdaData.functionName}.`, { cause: error });
    }
}
/**
 * Remove the layer from the Lambda function and update the environment variables
 * @param lambdaData
 */
async function removeLayerFromLambda(lambdaData) {
    Logger.verbose(`[Function ${lambdaData.functionName}] Removing layer and environment variables`);
    try {
        await updateLambda(lambdaData);
    }
    catch (error) {
        throw new Error(`Failed to remove layer from lambda ${lambdaData.functionName}.`, { cause: error });
    }
}
/**
 * General function to update the Lambda function configuration
 */
async function updateLambda(lambdaData) {
    const updateFunctionConfigurationCommand = new UpdateFunctionConfigurationCommand({
        FunctionName: lambdaData.functionName,
        Layers: lambdaData.layers,
        Environment: {
            Variables: lambdaData.environmentVariables,
        },
        Timeout: lambdaData.timeout,
    });
    await getLambdaClient().send(updateFunctionConfigurationCommand);
}
/**
 * Analyze the Lambda function to determine if it needs to be updated
 * @param func - Lambda function properties
 * @param existingLayerVersionArn - ARN of existing layer version if available
 * @returns Lambda update configuration or undefined if no update needed
 */
async function analyzeLambdaAdd(functionName, existingLayerVersionArn) {
    const { environmentVariables, ddlLayerArns, otherLayerArns, initialTimeout } = await getLambdaConfiguration(functionName);
    if (!existingLayerVersionArn) {
        const ddlEnvironmentVariables = getEnvironmentVariablesForDebugger({
            functionName,
            timeout: initialTimeout,
            verbose: Configuration.config.verbose,
            initialExecWrapper: environmentVariables.AWS_LAMBDA_EXEC_WRAPPER !== lldWrapperPath
                ? environmentVariables.AWS_LAMBDA_EXEC_WRAPPER
                : undefined,
        });
        Logger.verbose(`[Function ${functionName}] The layer for this version does not exist in the account. We need to add it and attach it to the function`);
        return {
            functionName,
            layers: otherLayerArns,
            environmentVariables: {
                ...environmentVariables,
                ...ddlEnvironmentVariables,
            },
            timeout: Math.max(initialTimeout, 300),
        };
    }
    else {
        let needToUpdateLayer = false;
        // check if layer is already attached
        if (!ddlLayerArns?.find((arn) => arn === existingLayerVersionArn)) {
            needToUpdateLayer = true;
            Logger.verbose(`[Function ${functionName}] Layer not attached to the function`);
        }
        else {
            Logger.verbose(`[Function ${functionName}] Layer already attached to the function`);
        }
        // check if layers with the wrong version are attached
        if (!needToUpdateLayer &&
            ddlLayerArns.find((arn) => arn !== existingLayerVersionArn)) {
            needToUpdateLayer = true;
            Logger.verbose(`[Function ${functionName}] Layer with the wrong version attached to the function`);
        }
        // support for multiple internal Lambda extensions
        const initialExecWrapper = environmentVariables.AWS_LAMBDA_EXEC_WRAPPER !== lldWrapperPath
            ? environmentVariables.AWS_LAMBDA_EXEC_WRAPPER
            : undefined;
        if (initialExecWrapper) {
            Logger.warn(`[Function ${functionName}] Another internal Lambda extension is already attached to the function, which might cause unpredictable behavior.`);
        }
        const ddlEnvironmentVariables = getEnvironmentVariablesForDebugger({
            functionName,
            timeout: initialTimeout,
            verbose: Configuration.config.verbose,
            initialExecWrapper,
        });
        let needToUpdateEnvironmentVariables = false;
        // check if environment variables are already set for each property
        for (const [key, value] of Object.entries(ddlEnvironmentVariables)) {
            if (!environmentVariables || environmentVariables[key] !== value) {
                needToUpdateEnvironmentVariables = true;
                break;
            }
        }
        Logger.verbose(`[Function ${functionName}] ${needToUpdateEnvironmentVariables ? 'Need to update environment variables' : 'No need to update environment variables'}. Existing environment variables: ` +
            JSON.stringify(environmentVariables, null, 2));
        return needToUpdateLayer || needToUpdateEnvironmentVariables
            ? {
                functionName,
                layers: [existingLayerVersionArn, ...otherLayerArns],
                environmentVariables: {
                    ...environmentVariables,
                    ...ddlEnvironmentVariables,
                },
                timeout: Math.max(initialTimeout, 300),
            }
            : undefined;
    }
}
/**
 * Add the policy to the Lambda role
 */
async function addPolicyToRole(roleName) {
    Logger.verbose(`[Role ${roleName}] Attaching policy to the role`);
    try {
        await getIAMClient().send(new PutRolePolicyCommand({
            RoleName: roleName,
            PolicyName: inlinePolicyName,
            PolicyDocument: JSON.stringify(policyDocument),
        }));
    }
    catch (error) {
        throw new Error(`Failed to attach policy to role ${roleName}.`, {
            cause: error,
        });
    }
}
/**
 * Prepare the Lambda role for the update
 * @param roleName
 * @returns
 */
async function analyzeRoleAdd(roleName) {
    try {
        Logger.verbose(`[Role ${roleName}] Analyzing role for policy attachment`);
        const existingPolicy = await createPolicyDocument(roleName);
        let addPolicy = true;
        // compare existing policy with the new one
        if (existingPolicy) {
            if (JSON.stringify(existingPolicy) === JSON.stringify(policyDocument)) {
                Logger.verbose(`[Role ${roleName}] Policy already attached to the role`);
                addPolicy = false;
            }
            else {
                Logger.verbose(`[Role ${roleName}] Different policy found on role, will update`);
            }
        }
        else {
            Logger.verbose(`[Role ${roleName}] No policy found on role, will attach`);
        }
        return { addPolicy, roleName };
    }
    catch (error) {
        throw new Error(`Failed to analyze role ${roleName} for policy attachment.`, {
            cause: error,
        });
    }
}
/**
 * Get the environment variables for the Lambda function
 */
function getEnvironmentVariablesForDebugger({ functionName, timeout, verbose, initialExecWrapper, }) {
    const env = {
        LLD_FUNCTION_ID: functionName,
        AWS_LAMBDA_EXEC_WRAPPER: lldWrapperPath,
        LLD_DEBUGGER_ID: Configuration.config.debuggerId,
        LLD_INITIAL_TIMEOUT: timeout ? timeout.toString() : '-1', // should never be negative
        LLD_OBSERVABLE_MODE: Configuration.config.observable ? 'true' : 'false',
        LLD_OBSERVABLE_INTERVAL: Configuration.config.interval.toString(),
    };
    if (initialExecWrapper) {
        env.LLD_INITIAL_AWS_LAMBDA_EXEC_WRAPPER = initialExecWrapper;
    }
    if (verbose) {
        env.LLD_VERBOSE = 'true';
    }
    return env;
}
export const InfraDeploy = {
    getInfraChangesForAdding,
    getInfraChangesForRemoving,
    applyAddingInfra,
    applyRemoveInfra,
    deleteLayer,
};
