"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PROMPT_ME_DESIGNATOR_MACOS = exports.PROMPT_ME_DESIGNATOR = void 0;
const tslib_1 = require("tslib");
require("reflect-metadata");
const core_1 = require("@oclif/core");
const _ = require("lodash");
const autocomplete = require("inquirer-autocomplete-prompt");
const inquirer = require("inquirer");
const fuzzy = require("fuzzy");
const contracts_1 = require("@codiac.io/codiac-common/contracts");
const command_base_exec_1 = require("../command-base-exec");
const cloud_provider_contextualizer_1 = require("../uilogic/cloud-provider-contextualizer");
let assetVersionChoices = [];
/** The value provided as input that indicates
 * a user's desire to be prompted with a list from which choose the value(s),
 * instead of entering them manually or taking a default. */
exports.PROMPT_ME_DESIGNATOR = "?";
exports.PROMPT_ME_DESIGNATOR_MACOS = "??";
class Deploy extends command_base_exec_1.CommandBaseExec {
    bootstrap() {
        super.bootstrap();
        cloud_provider_contextualizer_1.CloudProviderContextualizer.bootstrap(this.ioc);
    }
    captureArgs() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const { flags } = yield this.parse(Deploy);
            let partial = {};
            if (flags.enterpriseCode)
                partial.enterprise = flags.enterpriseCode;
            if (flags["providerUserName"])
                partial.providerUserName = flags["providerUserName"];
            if (flags["providerPassword"])
                partial.providerPassword = flags["providerPassword"];
            if (flags["override-conditions"])
                partial.overrideConditions = true;
            return [partial, false];
        });
    }
    buildCandidate_silently(partial, rootMap) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const { flags } = yield this.parse(Deploy);
            if (!flags.asset)
                throw new Error("Asset name (--asset or -a) is required in silent mode.");
            if (!flags.version)
                throw new Error("Asset version (--version or -u) is required in silent mode.");
            const assetVersion = { a: flags.asset, v: flags.version };
            if (flags.tagAsset && flags.tagAsset.length > 0)
                assetVersion.t = flags.tagAsset;
            const [cabinetScope, cabCluster] = yield this.resolveCabinetSilently(partial.enterprise, rootMap, flags);
            // CSP creds
            const cluster = cabCluster !== null && cabCluster !== void 0 ? cabCluster : (rootMap.clusters.length === 1 ? rootMap.clusters[0].name : undefined);
            if (cluster) {
                yield this.establishCloudProviderCreds(rootMap, cluster, true, partial.providerUserName, partial.providerPassword);
            }
            else {
                this.logger.warn(`Cannot confirm cloud provider credentials: cabinet has no cluster specified.`);
            }
            return { enterprise: partial.enterprise, assetVersions: [assetVersion], cabinetScope, cabCluster, providerUserName: partial.providerUserName, providerPassword: partial.providerPassword, overrideConditions: partial.overrideConditions };
        });
    }
    buildCandidate_interactively(partial, rootMap, takeDefaults) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const { flags } = yield this.parse(Deploy);
            // Select asset + version
            let assetVersions;
            const assetFlag = flags.asset;
            if (assetFlag === exports.PROMPT_ME_DESIGNATOR || assetFlag === exports.PROMPT_ME_DESIGNATOR_MACOS || assetFlag || this._projCfg === undefined) {
                const resolvedAsset = (assetFlag === exports.PROMPT_ME_DESIGNATOR || assetFlag === exports.PROMPT_ME_DESIGNATOR_MACOS)
                    ? undefined
                    : assetFlag;
                const asset = yield this.promptForAsset(partial.enterprise, resolvedAsset);
                const selected = yield this.promptForVersion(partial.enterprise, asset, flags.version);
                if (flags.tagAsset && flags.tagAsset.length > 0)
                    selected.t = flags.tagAsset;
                assetVersions = [selected];
            }
            else {
                const requestedAssets = yield this.establishAssetContext(rootMap.enterprises.find(e => e.name === partial.enterprise), false);
                const versions = yield this.declareAssetVersions(requestedAssets, partial.enterprise, this._projCfg.codeVersion);
                assetVersions = requestedAssets.map(a => {
                    const version = versions.find(v => v.assetName === a);
                    const av = { a, v: version ? version.assetVersion : undefined };
                    if (flags.tagAsset && flags.tagAsset.length > 0)
                        av.t = flags.tagAsset;
                    return av;
                });
            }
            // Select cabinet
            let cabinetScope;
            let cabCluster;
            if (flags.tag !== undefined) {
                [cabinetScope, cabCluster] = yield this.resolveCabinetByTag(partial.enterprise, rootMap, flags);
            }
            else {
                const cabinet = yield this.establishCabinet(flags.cabinetName, false);
                cabinetScope = { enterprise: cabinet.enterpriseCode, environment: cabinet.environment, cabinet: cabinet.name };
                cabCluster = cabinet.cluster;
            }
            // CSP creds
            const cluster = cabCluster !== null && cabCluster !== void 0 ? cabCluster : (rootMap.clusters.length === 1 ? rootMap.clusters[0].name : undefined);
            if (cluster) {
                yield this.establishCloudProviderCreds(rootMap, cluster, false, partial.providerUserName, partial.providerPassword);
            }
            else {
                this.logger.warn(`Cannot confirm cloud provider credentials: cabinet has no cluster specified.`);
            }
            return { enterprise: partial.enterprise, assetVersions, cabinetScope, cabCluster, providerUserName: partial.providerUserName, providerPassword: partial.providerPassword, overrideConditions: partial.overrideConditions };
        });
    }
    buildCommandString(candidate, cmd) {
        cmd.push("-e", candidate.enterprise);
        if (candidate.assetVersions.length > 0) {
            cmd.push("-a", candidate.assetVersions[0].a);
            if (candidate.assetVersions[0].v)
                cmd.push("-u", candidate.assetVersions[0].v);
        }
        cmd.push("-c", candidate.cabinetScope.cabinet);
        if (candidate.providerUserName)
            cmd.push("-n", candidate.providerUserName);
        if (candidate.providerPassword)
            cmd.push("-p", candidate.providerPassword);
        if (candidate.overrideConditions)
            cmd.push("--override-conditions");
        cmd.push("--silent");
        return cmd;
    }
    executeTask(candidate) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const cabLabel = `${candidate.cabinetScope.enterprise}/${candidate.cabinetScope.environment}/${this._styler.hilite(candidate.cabinetScope.cabinet)}`;
            yield this._ui.startSpinner(`Deploying to cabinet [${cabLabel}]...`);
            const result = yield this.genRelayClient.Call.postExecuteDeployment({ assets: candidate.assetVersions, target: candidate.cabinetScope, overrideConditions: candidate.overrideConditions });
            yield this._ui.stopSpinner(result.success ? "SUCCESS" : (result.error ? "ERROR" : "FAILED"));
            if (result.success) {
                this._ui.userMessage(`${this._styler.colorGood("SUCCESS")}: ${result.message}`);
                this.reportSkipped(result.skipped);
            }
            else if (result.error) {
                this._ui.userMessage(`${this._styler.colorCritical("ERROR")}: ${result.message}`);
                throw result.error;
            }
            else {
                this._ui.userMessage(`FAILED: ${result.message}`);
                throw result.error;
            }
        });
    }
    reportSkipped(skipped) {
        var _a, _b;
        if (!skipped || skipped.length === 0)
            return;
        this._ui.userMessage(`Skipped (${skipped.length}):`);
        for (const item of skipped) {
            const reason = (_b = (_a = item.reason) !== null && _a !== void 0 ? _a : item.condition) !== null && _b !== void 0 ? _b : "condition evaluated to false";
            this._ui.userMessage(`  - ${item.name}: ${reason}`);
        }
    }
    resolveCabinetSilently(enterprise, rootMap, flags) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (flags.tag !== undefined) {
                return this.resolveCabinetByTag(enterprise, rootMap, flags);
            }
            if (!flags.cabinetName)
                throw new Error("Cabinet name (--cabinetName or -c) or tags (--tag or -t) are required in silent mode.");
            const cabinet = yield this.establishCabinet(flags.cabinetName, true);
            const cabinetScope = { enterprise: cabinet.enterpriseCode, environment: cabinet.environment, cabinet: cabinet.name };
            return [cabinetScope, cabinet.cluster];
        });
    }
    resolveCabinetByTag(enterprise, rootMap, flags) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const selector = { enterprise, tags: flags.tag };
            if (flags.tagMatch !== undefined) {
                if (!["any", "all", "none"].includes(flags.tagMatch))
                    throw new Error("tagMatch must be one of: 'any', 'all', or 'none'.");
                selector.matchTags = flags.tagMatch;
            }
            const cabRes = yield this.genRelayClient.Call.GetCabinets(selector);
            if (!cabRes.output || cabRes.output.length === 0)
                throw new Error("No matching cabinets were found.");
            if (cabRes.output.length > 1) {
                this.logger.detail("Found multiple cabinets matching deployment request; selecting the first of ".concat(cabRes.output.length.toString()));
            }
            const cab = cabRes.output[0];
            const cabinetScope = Object.assign(Object.assign({}, _.pick(cab, "enterprise", "environment")), { cabinet: cab.name });
            return [cabinetScope, cab.cluster];
        });
    }
    promptForAsset(enterprise, assetSearch) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const ecrit = { code: enterprise };
            const enterprises = yield this._codiacApi.Enterprise.readMany(ecrit, { throwNotFound: false });
            if (!enterprises || enterprises.length === 0)
                throw new Error(`Could not find enterprise [${enterprise}].`);
            if (!enterprises[0].assets || enterprises[0].assets.length === 0)
                throw new Error(`The enterprise [${enterprise}] has no assets defined.`);
            let assets = enterprises[0].assets;
            if (assetSearch && assetSearch !== exports.PROMPT_ME_DESIGNATOR) {
                assets = assets.filter(a => a.name.startsWith(assetSearch));
            }
            const choices = assets.map(a => ({ name: a.name, value: a.name })).sort((a, b) => a.name.localeCompare(b.name));
            const selectedName = yield this._ui.promptSelectFuzzy('asset', 'Select an Asset:', () => tslib_1.__awaiter(this, void 0, void 0, function* () { return choices; }));
            const selected = assets.find(a => a.name === selectedName);
            if (!selected)
                throw new Error(`User failed to select a valid asset to deploy.`);
            return selected;
        });
    }
    promptForVersion(enterpriseCode, asset, versionSearch) {
        var _a, _b;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const providerSubscriptionId = (_a = asset.image.registry.accountId) !== null && _a !== void 0 ? _a : asset.image.registry.subscriptionId;
            yield this.cspContextualizer.promptForCloudProviderCreds(this.ioc, asset.image.registry.provider, undefined, undefined, undefined, providerSubscriptionId);
            this._ui.startSpinner("Loading versions...");
            const result = yield this.genRelayClient.Call.FetchAssetVersions({ image: asset.image, assetType: asset.type, versionSearch });
            if (result.error)
                throw result.error;
            const sorted = (_b = result.output) !== null && _b !== void 0 ? _b : [];
            const latest = sorted.length > 0 ? sorted[0] : undefined;
            assetVersionChoices = sorted.map(v => ({ value: v, name: v }));
            this._ui.stopSpinner(`found ${assetVersionChoices.length}`);
            inquirer.registerPrompt('autocomplete', autocomplete);
            const verSelect = yield inquirer.prompt([{
                    type: 'autocomplete',
                    name: 'version',
                    message: 'Select the asset version:',
                    source: this.searchVersions,
                    default: latest
                }]);
            return { a: asset.name, v: verSelect.version };
        });
    }
    searchVersions(answers, input) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            input = input || "";
            return new Promise((resolve) => {
                setTimeout(() => {
                    const options = { extract: (ver) => ver.value };
                    const results = fuzzy.filter(input, assetVersionChoices, options);
                    resolve(results.map((fr) => fr.original));
                }, _.random(30, 500));
            });
        });
    }
    /** Expands a string list of key=value into properties on an object. */
    parseToDictionary(theseEqualThose) {
        if (theseEqualThose && theseEqualThose.length > 0) {
            const labels = {};
            theseEqualThose.forEach(x => {
                const [key, val] = x.split("=");
                labels[key] = val;
            });
            return labels;
        }
        return {};
    }
    /** Writes Asset versions to the Codiac API. */
    declareAssetVersions(assets, enterpriseCode, versionToUse) {
        var _a, _b, _c, _d;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const versions = [];
            const newVersions = [];
            for (const asset of assets) {
                const versionName = `${asset}-${versionToUse}`;
                const vc = Object.assign({ name: versionName, enterprise: enterpriseCode, assetName: asset, assetVersion: versionToUse }, this.createAuditable());
                const crit = { enterprise: vc.enterprise, assetName: vc.assetName, assetVersion: vc.assetVersion };
                const check = yield this._codiacApi.VersionConfig.tryReadMany(crit);
                if (check.error) {
                    this.logger.debug(`Error checking existence of VersionConfig: [${vc.assetName}, v${vc.assetVersion}]: ${check.message || ""}; ${check.error.stack}`);
                }
                else if (check.count === 0) {
                    newVersions.push(vc);
                }
                else {
                    versions.push(...check.output);
                    this.logger.debug(`Skipping version declaration for [${vc.assetName}:${vc.assetVersion}]: VersionConfig already exists.`);
                }
            }
            if (newVersions.length > 0) {
                const result = yield this._codiacApi.VersionConfig.tryCreateMany(newVersions);
                if (result.success) {
                    versions.push(...result.successes);
                }
                else {
                    const failureCount = (_b = (_a = result.failures) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0;
                    const successCount = (_d = (_c = result.successes) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0;
                    if (result.error) {
                        throw new contracts_1.CodiacError(`Error declaring version configs for [${assets.join(",")}]: ${result.message || ""}; ${result.error.message}`, result.error);
                    }
                    else {
                        throw new contracts_1.CodiacError(`Partial failure declaring version configs [${successCount} succeeded, ${failureCount} failed] for ${assets.join(",")}: ${result.message}`);
                    }
                }
            }
            return versions;
        });
    }
}
exports.default = Deploy;
Deploy.description = 'Deploys a given asset to a given cabinet. Defaults to current version.';
Deploy.flags = {
    help: core_1.Flags.help({ char: 'h' }),
    enterpriseCode: core_1.Flags.string({ char: 'e', required: false, description: 'Code name of the enterprise to target.' }),
    cabinetName: core_1.Flags.string({ char: 'c', required: false, description: 'Target cabinet name to deploy to.' }),
    tag: core_1.Flags.string({ char: 't', exclusive: ["cabinetName"], required: false, multiple: true, description: 'Cabinet tags to select the target cabinet (alternative to --cabinetName).' }),
    tagMatch: core_1.Flags.string({ char: 'm', required: false, dependsOn: ["tag"], options: ["any", "all", "none"], description: 'Logic for matching the given tags when selecting cabinets (defaults to any).' }),
    asset: core_1.Flags.string({ char: 'a', required: false, description: `Name of the asset to deploy (use ${exports.PROMPT_ME_DESIGNATOR} or ${exports.PROMPT_ME_DESIGNATOR_MACOS} to be prompted).` }),
    version: core_1.Flags.string({ char: 'u', required: false, description: `Version of the asset to deploy (use ${exports.PROMPT_ME_DESIGNATOR} or ${exports.PROMPT_ME_DESIGNATOR_MACOS} to be prompted).` }),
    tagAsset: core_1.Flags.string({ char: 'w', multiple: true, required: false, description: 'Tags to apply to the selected asset version.' }),
    // TODO: Deprecate camel-cased args in favor of kebab-case (below)
    "providerUserName": core_1.Flags.string({ char: 'n', required: false, description: 'Cloud provider username for silent/scripted authentication.' }),
    "providerPassword": core_1.Flags.string({ char: 'p', required: false, description: 'Cloud provider password for silent/scripted authentication.' }),
    // "provider-username": Flags.string({ char: 'n', required: false, description: 'Cloud provider username for silent/scripted authentication.' }),
    // "provider-password": Flags.string({ char: 'p', required: false, description: 'Cloud provider password for silent/scripted authentication.' }),
    "override-conditions": core_1.Flags.boolean({ required: false, description: 'Deploy all assets regardless of any configured deploy conditions.' }),
    silent: core_1.Flags.boolean({ exclusive: ["echo", "to-script"], description: 'Executes without any user interaction; fails on missing or invalid arguments.' }),
    echo: core_1.Flags.boolean({ exclusive: ["to-script", "silent"], description: 'Renders the equivalent non-interactive command for future use before executing.' }),
    "to-script": core_1.Flags.boolean({ exclusive: ["echo", "silent"], description: 'Renders the equivalent non-interactive command without executing it.' }),
};
Deploy.args = {};
Deploy.examples = [
    { description: "Deploy interactively.", command: "<%= config.bin %> <%= command.id %>" },
    { description: "Deploy a specific asset version to a cabinet.", command: "<%= config.bin %> <%= command.id %> -e myEnterprise -a myApi -u 1.2.3 -c dev" },
    { description: "Deploy to a cabinet selected by tag.", command: "<%= config.bin %> <%= command.id %> -e myEnterprise -a myApi -u 1.2.3 -t staging" },
    { description: "Non-interactive (scripted) invocation.", command: "<%= config.bin %> <%= command.id %> -e myEnterprise -a myApi -u 1.2.3 -c dev --silent" },
    { description: "Renders the equivalent silent command without executing.", command: "<%= config.bin %> <%= command.id %> -e myEnterprise -a myApi -u 1.2.3 -c dev --to-script" },
];
//# sourceMappingURL=deploy.js.map