"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.processDockerAuthentication = exports.stopDockerComposeStack = exports.startDockerComposeStack = exports.deleteDockerComposeFile = exports.generateContainersDockerComposeFile = exports.DockerAuthenticationError = exports.CannotUseBothImageAndBuildContextError = exports.MissingImageOrBuildContextError = exports.DockerUnableToStartError = exports.DockerUnableToPullProxySidecarImage = exports.InvalidContainerServicePort = exports.K8S_AUTH_TOKEN_FILENAME = exports.CONTAINER_SERVICE_ASSETS = exports.PROXY_SIDECAR_VOLUME_DIR = void 0;
const tslib_1 = require("tslib");
const fs = tslib_1.__importStar(require("fs"));
const path = tslib_1.__importStar(require("path"));
const yaml = tslib_1.__importStar(require("yaml"));
const docker_compose_1 = require("docker-compose");
const shared_1 = require("../shared");
const text_1 = require("../ui/text");
const tunnel_options_1 = require("./tunnel-options");
const HIDDEN_DIR = '.services';
exports.PROXY_SIDECAR_VOLUME_DIR = '/forge/container';
exports.CONTAINER_SERVICE_ASSETS = '.container-service-assets';
exports.K8S_AUTH_TOKEN_FILENAME = 'local-account';
const PROXY_SIDECAR_SERVICE_NAME = 'proxy-sidecar';
class InvalidContainerServicePort extends shared_1.UserError {
    constructor(serviceKey) {
        super(text_1.Text.error.invalidServicePort(serviceKey));
    }
}
exports.InvalidContainerServicePort = InvalidContainerServicePort;
class DockerUnableToPullProxySidecarImage extends shared_1.UserError {
    constructor(err) {
        super(text_1.Text.tunnel.unableToPullProxySidecarImage(err?.message ?? 'Unknown Error Occurred.'));
    }
}
exports.DockerUnableToPullProxySidecarImage = DockerUnableToPullProxySidecarImage;
class DockerUnableToStartError extends shared_1.UserError {
    constructor(err) {
        super(text_1.Text.tunnel.unableToStartDockerComposeStack(err?.message ?? 'Unknown Error Occurred.'));
    }
}
exports.DockerUnableToStartError = DockerUnableToStartError;
class MissingImageOrBuildContextError extends shared_1.UserError {
    constructor(containerKey) {
        super(text_1.Text.tunnel.missingImageOrBuildContext(containerKey));
    }
}
exports.MissingImageOrBuildContextError = MissingImageOrBuildContextError;
class CannotUseBothImageAndBuildContextError extends shared_1.UserError {
    constructor(containerKey) {
        super(text_1.Text.tunnel.cannotUseBothImageAndBuildContext(containerKey));
    }
}
exports.CannotUseBothImageAndBuildContextError = CannotUseBothImageAndBuildContextError;
class DockerAuthenticationError extends shared_1.UserError {
    constructor(errCode) {
        super(text_1.Text.tunnel.unableToAuthenticateDocker(errCode));
    }
}
exports.DockerAuthenticationError = DockerAuthenticationError;
const generateContainersDockerComposeFile = async (services, appId, envId) => {
    const filesGenerated = {};
    for (const service of services) {
        const { key: serviceKey, containers } = service;
        const containersWithTunnelConfig = containers.filter((container) => !!container.tunnel);
        if (containersWithTunnelConfig.length > 0) {
            const port = await (0, tunnel_options_1.getServicePort)(services, serviceKey);
            if (!port || port < 1024 || port > 49152) {
                throw new InvalidContainerServicePort(serviceKey);
            }
            const containerConfig = Object.fromEntries(containersWithTunnelConfig.map((container) => {
                if (!container.tunnel?.docker?.image && !container.tunnel?.docker?.build) {
                    throw new MissingImageOrBuildContextError(container.key);
                }
                else if (container.tunnel?.docker?.image && container.tunnel?.docker?.build) {
                    throw new CannotUseBothImageAndBuildContextError(container.key);
                }
                const config = {
                    container_name: container.key,
                    ...container.tunnel?.docker,
                    depends_on: [PROXY_SIDECAR_SERVICE_NAME]
                };
                if (config.build?.context && !path.isAbsolute(config.build.context)) {
                    let context = config.build.context;
                    if (context.startsWith('./')) {
                        context = context.slice(2);
                    }
                    config.build.context = '../' + context;
                }
                const envArray = container?.tunnel?.docker.environment ?? [];
                const filteredEnvArray = envArray.filter((envVar) => !envVar.startsWith('FORGE_EGRESS_PROXY_URL='));
                filteredEnvArray.push('FORGE_EGRESS_PROXY_URL=http://proxy-sidecar:7072');
                config.environment = filteredEnvArray;
                return [container.key, config];
            }));
            const dockerComposeConfig = {
                services: {
                    ...containerConfig,
                    ...(await getProxySidecarConfig(serviceKey, Object.keys(containerConfig), port, appId, envId))
                }
            };
            const yamlString = yaml.stringify(dockerComposeConfig);
            const filePath = getContainerDockerComposePath(serviceKey);
            fs.writeFileSync(filePath, yamlString);
            filesGenerated[serviceKey] = filePath;
        }
    }
    return filesGenerated;
};
exports.generateContainersDockerComposeFile = generateContainersDockerComposeFile;
const getProxySidecarConfig = async (serviceKey, containerKeys, port, appId, envId) => {
    let fopBaseUrl = 'https://forge-outbound-proxy.services.atlassian.com';
    let jwksUrl = 'https://forge.cdn.prod.atlassian-dev.net/.well-known/jwks.json';
    let proxySidecarImage = 'forge-ecr.services.atlassian.com/forge-platform/proxy-sidecar:latest';
    if (process.env.FORGE_GRAPHQL_GATEWAY?.startsWith('https://api-private.stg.atlassian.com/graphql')) {
        fopBaseUrl = 'https://forge-outbound-proxy.stg.services.atlassian.com';
        jwksUrl = 'https://forge.cdn.stg.atlassian-dev.net/.well-known/jwks.json';
        proxySidecarImage = 'forge-ecr.stg.services.atlassian.com/forge-platform/proxy-sidecar:latest';
    }
    let appIdShort = appId;
    if (appId.startsWith('ari:cloud:ecosystem::app/')) {
        appIdShort = appId.split('/')[1];
    }
    return {
        [PROXY_SIDECAR_SERVICE_NAME]: {
            image: proxySidecarImage,
            container_name: getProxySidecarContainerName(serviceKey),
            environment: [
                `SERVICE_URL=http://${containerKeys[0]}:8080`,
                `FOP_BASE_URL=${fopBaseUrl}`,
                `APP_ID=ari:cloud:ecosystem::app/${appIdShort}`,
                `ENV_ID=ari:cloud:ecosystem::environment/${appIdShort}/${envId}`,
                `JWKS_URL=${jwksUrl}`,
                `IS_LOCAL_DEV=true`,
                `K8S_AUTH_TOKEN_PATH=${exports.PROXY_SIDECAR_VOLUME_DIR}/${exports.K8S_AUTH_TOKEN_FILENAME}`
            ],
            volumes: [`../${exports.CONTAINER_SERVICE_ASSETS}:${exports.PROXY_SIDECAR_VOLUME_DIR}:ro`],
            ports: [`${port}:${tunnel_options_1.DEFAULT_PROXY_INGRESS_PORT}`]
        }
    };
};
const getProxySidecarContainerName = (serviceKey) => {
    return `${PROXY_SIDECAR_SERVICE_NAME}-${serviceKey}`;
};
const getContainerDockerComposePath = (serviceKey) => {
    const hiddenDir = path.join(process.cwd(), HIDDEN_DIR);
    if (!fs.existsSync(hiddenDir)) {
        fs.mkdirSync(hiddenDir, { recursive: true });
    }
    return path.join(hiddenDir, `docker-compose-${serviceKey}.yml`);
};
const deleteDockerComposeFile = async (composeFile) => {
    if (fs.existsSync(composeFile)) {
        fs.unlinkSync(composeFile);
    }
    const hiddenDir = path.join(process.cwd(), HIDDEN_DIR);
    if (fs.existsSync(hiddenDir)) {
        const files = fs.readdirSync(hiddenDir);
        if (files.length === 0) {
            fs.rmdirSync(hiddenDir);
        }
    }
};
exports.deleteDockerComposeFile = deleteDockerComposeFile;
const startDockerComposeStack = async (dockerComposeFilePath, serviceKey) => {
    try {
        await (0, docker_compose_1.pullOne)('proxy-sidecar', {
            cwd: path.dirname(dockerComposeFilePath),
            log: true,
            config: dockerComposeFilePath
        });
    }
    catch (err) {
        throw new DockerUnableToPullProxySidecarImage(err);
    }
    try {
        await (0, docker_compose_1.upAll)({
            cwd: path.dirname(dockerComposeFilePath),
            log: true,
            config: dockerComposeFilePath,
            composeOptions: [`-p${serviceKey}`],
            commandOptions: ['--build']
        });
    }
    catch (err) {
        throw new DockerUnableToStartError(err);
    }
};
exports.startDockerComposeStack = startDockerComposeStack;
const stopDockerComposeStack = async (configFile, composeFiles) => {
    if (!composeFiles || Object.keys(composeFiles).length === 0)
        return;
    const { services } = await configFile.readConfig();
    const serviceWithTunnelConfigExists = services?.some((service) => service.containers?.some((container) => {
        return !!container.tunnel;
    }));
    if (!services || services.length === 0 || !serviceWithTunnelConfigExists)
        return;
    await Promise.all(Object.entries(composeFiles).map(async ([serviceKey, file]) => {
        try {
            await (0, docker_compose_1.downAll)({ cwd: '.', log: true, config: file, composeOptions: [`-p${serviceKey}`] });
            await (0, exports.deleteDockerComposeFile)(file);
        }
        catch (err) {
            throw new Error(text_1.Text.tunnel.unableToStopDockerComposeStack(serviceKey, err.message ?? 'Unknown Error Occurred.'));
        }
    }));
    deleteContainerServiceAssetsDir();
};
exports.stopDockerComposeStack = stopDockerComposeStack;
const processDockerAuthentication = async (childProcess) => {
    await new Promise((resolve, reject) => {
        childProcess.on('close', (code) => {
            if (code === 0) {
                resolve();
            }
            else {
                reject(new DockerAuthenticationError(code?.toString() || 'unknown'));
            }
        });
        childProcess.on('error', reject);
    });
};
exports.processDockerAuthentication = processDockerAuthentication;
const deleteContainerServiceAssetsDir = () => {
    const dirPath = path.join(process.cwd(), exports.CONTAINER_SERVICE_ASSETS);
    if (fs.existsSync(dirPath)) {
        const tokenFilePath = path.join(dirPath, exports.K8S_AUTH_TOKEN_FILENAME);
        if (fs.existsSync(tokenFilePath)) {
            fs.unlinkSync(tokenFilePath);
        }
        const files = fs.readdirSync(dirPath);
        if (files.length === 0) {
            fs.rmdirSync(dirPath);
        }
    }
};
