"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const core_1 = require("@oclif/core");
const autocomplete = require("inquirer-autocomplete-prompt");
// import { ImageRegistry, KvpStoreDef, KvpStoreTypesEnum } from '@codiac.io/codiac-domain';
const contracts_1 = require("../../../apis/codiac-api/contracts");
const contracts_2 = require("@codiac.io/codiac-common/contracts");
const command_base_enterprise_1 = require("../../../command-base-enterprise");
const codiac_constants_1 = require("../../../codiac-constants");
const prettify_1 = require("../../../prettify");
const cloud_resource_configurator_factory_1 = require("../../../uilogic/cloud-resource-configurator-factory");
const cloud_provider_contextualizer_1 = require("../../../uilogic/cloud-provider-contextualizer");
const image_registry_contextualizer_1 = require("../../../uilogic/image-registry-contextualizer");
const image_contextualizer_1 = require("../../../uilogic/image-contextualizer");
const pull_secret_1 = require("@codiac.io/codiac-domain/pull-secret");
const codiac_domain_1 = require("@codiac.io/codiac-domain");
const type_safety_utils_1 = require("../../../uilogic/type-safety-utils");
// Has to be module-scoped bc the autocomplete redefines "this", so we can't use it inside searchPorts().
let ports = [];
class ImageRegistryPullSecretSet extends command_base_enterprise_1.CommandBaseEnterprise {
    bootstrap() {
        super.bootstrap();
        cloud_provider_contextualizer_1.CloudProviderContextualizer.bootstrap(this.ioc);
        this.ioc.bind(image_registry_contextualizer_1.ImageRegistryContextualizer).toSelf();
        this.ioc.bind(image_contextualizer_1.ImageContextualizer).toSelf();
    }
    // --------------------------------------------------------------------
    // Lazy-loaded properties
    get uiRegistry() {
        if (this._uiRegistry_backer === undefined) {
            this._uiRegistry_backer = this.ioc.get(image_registry_contextualizer_1.ImageRegistryContextualizer);
        }
        return this._uiRegistry_backer;
    }
    // --------------------------------------------------------------------
    run() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            this.bootstrap();
            const { args, flags } = yield this.parse(ImageRegistryPullSecretSet);
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                try {
                    this._userCntx = yield this.establishTenantContext(this._userCntx);
                    let partial = {};
                    let isPrimary = flags.item === "primary";
                    // TODO: Revisit for pull secrets on kits
                    // overlay given values
                    if (flags.registry !== undefined)
                        partial.name = flags.name;
                    if (flags.storeType !== undefined)
                        partial.type = type_safety_utils_1.TypeSafetyUtils.toEnum(contracts_1.KvpStoreTypesEnum, flags.storeType);
                    if (flags.storeName !== undefined)
                        partial.name = flags.storeName;
                    if (flags.name !== undefined)
                        partial.key = flags.name;
                    if (flags.scope != undefined)
                        partial.scope = type_safety_utils_1.TypeSafetyUtils.toEnum(codiac_domain_1.PullSecretScopesEnum, flags.scope);
                    let registries = yield this._codiacApi.ImageRegistry.readMany({ all: true }, { throwNotFound: false });
                    if (registries.length === 0) {
                        throw new Error(`Cannot set pull secrets: No image registries are defined yet.`);
                    }
                    let candidate;
                    if (flags.silent == true) {
                        candidate = this.buildCandidate_silently(partial, isPrimary, flags.registry, registries);
                    }
                    else {
                        candidate = yield this.buildCandidate_interactively(partial, isPrimary, flags.registry, registries);
                    }
                    // FINAL EXECUTION (or not)
                    if (candidate !== undefined) {
                        if (flags.toScript === true) {
                            let script = this.buildScript(candidate);
                            this._ui.userMessage(script);
                        }
                        else {
                            yield this.applyPullSecret(candidate.secretRef, candidate.isPrimary, registries.find(r => r.code === candidate.regCode));
                        }
                    }
                    else {
                        this.logger.notice(`Operation cancelled. No work was done.`);
                        this._ui.userMessage(`Operation cancelled. No work was done.`);
                    }
                }
                catch (err) {
                    this.handleError(err);
                }
                finally {
                    yield this.closeConnections();
                    resolve();
                }
            }));
        });
    }
    buildCandidate_silently(partial, isPrimary, regCode, registries) {
        // Rough validation for missing args
        if (!regCode)
            throw new Error("A registry code must be set");
        if (!partial.type)
            throw new Error("store type name must be set");
        if (!partial.name)
            throw new Error("store name must be set");
        if (!partial.key)
            throw new Error("secret name must be set");
        if (!registries.find(r => r.code === regCode)) {
            throw new Error(`No ImageRegistry record currently exists with code: [${regCode}]. You might need to check your spelling or capture your image registry.`);
        }
        if (!partial.expiresOn) {
            // TODO: need to use the vault client to look this up
            partial.expiresOn = new Date(Date.now() + (1000 * 60 * 60 * 24 * 365 * 5)); // Stub to 5 years
        }
        try {
            let secretRef = new pull_secret_1.PullSecretRef(partial.type, partial.name, partial.key, partial.expiresOn);
            if (partial.scope)
                secretRef.scope = partial.scope;
            // TODO: Validate existence of secretRef in the secretStore
            return { secretRef: secretRef, isPrimary: isPrimary, regCode: regCode };
        }
        catch (err) {
            throw err;
        }
    }
    /** Walks the user through a Q & A to build out the specs for the asset. */
    buildCandidate_interactively(partialPullSecretRef, isPrimary, regCode, registries, allowAddSecrets = false) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                var _a, _b, _c;
                let candidate;
                try {
                    cloud_resource_configurator_factory_1.RegistryConfiguratorFactory.bootstrap(this.ioc);
                    let expiration;
                    let partial = {
                        storeType: partialPullSecretRef.type,
                        storeName: partialPullSecretRef.name,
                        secretName: partialPullSecretRef.key,
                        expiresOn: expiration,
                        isPrimary: isPrimary,
                        regCode: regCode,
                        scope: (_a = partialPullSecretRef.scope) !== null && _a !== void 0 ? _a : codiac_domain_1.PullSecretScopesEnum.everywhere
                    };
                    let secretStores;
                    let regInput = {};
                    if (partial.regCode) {
                        let found = registries.find(r => r.code === partial.regCode);
                        if (found)
                            regInput = found;
                    }
                    let confirmed;
                    while (!confirmed) {
                        // NOTE:  We are gating on "confirmed != undefined" because that means they asked to revise the values. 
                        console.clear();
                        // REGISTRY  (we've already gated this method for registries.length > 0)
                        let selectedRegistry;
                        let keepRegistry = (partial.regCode === undefined) ? false : yield this._ui.promptConfirm("keepRegistry", `${this._styler.hilite(partial.regCode)} pull secret`);
                        if (!keepRegistry) {
                            selectedRegistry = (yield this.uiRegistry.promptForExistingRegistry(contracts_1.PackageType.container, false, registries));
                            partial.regCode = selectedRegistry.code;
                        }
                        // PRIMARY/SECONDARY
                        partial.isPrimary = (_b = partial.isPrimary) !== null && _b !== void 0 ? _b : true;
                        let keepPrimary = yield this._ui.promptConfirm("keepPrimary", `${this._styler.hilite(partial.isPrimary ? "Primary" : "Secondary")} pull secret`);
                        if (!keepPrimary) {
                            partial.isPrimary = (yield this._ui.promptSelect("isPrimary", "Which pull secret?: ", [{ name: "Primary", value: true }, { name: "Secondary", value: false }]));
                        }
                        // SCOPE
                        let keepScope = yield this._ui.promptConfirm("keepScope", `Scope: ${this._styler.hilite(partial.scope)}`);
                        if (!keepScope) {
                            // let scopeChoices = Object.values(PullSecretScopesEnum).map(s => { return { name: s, value: s }; })
                            let scopeChoices = [
                                { name: codiac_domain_1.PullSecretScopesEnum.everywhere.concat(" (always implement this)"), value: codiac_domain_1.PullSecretScopesEnum.everywhere },
                                { name: codiac_domain_1.PullSecretScopesEnum.unmatchingSubx.concat(" (only when registry and cluster subscriptions differ)"), value: codiac_domain_1.PullSecretScopesEnum.unmatchingSubx }
                            ];
                            const selected = (yield this._ui.promptSelect("scope", "Scope: ", scopeChoices));
                            partial.scope = type_safety_utils_1.TypeSafetyUtils.toEnum(codiac_domain_1.PullSecretScopesEnum, selected);
                        }
                        // SECRET STORE
                        let keepSecret = false;
                        while (keepSecret === false) {
                            let keepStore = (partial.storeName === undefined) ? false : yield this._ui.promptConfirm("keepStore", `Secret Store: ${this._styler.hilite(partial.storeName)}`);
                            if (!keepStore) {
                                let original = `${partial.storeType}|${partial.storeName}`;
                                // Lazy-load existing secret stores
                                secretStores = secretStores !== null && secretStores !== void 0 ? secretStores : yield this._codiacApi.KvpStoreDef.readMany({ all: true }, { throwNotFound: false });
                                // limit generic kvp stores to only secret stores...
                                secretStores = secretStores.filter(s => [contracts_1.KvpStoreTypesEnum.awsKeyVault, contracts_1.KvpStoreTypesEnum.azKeyVault].map(x => x.toString()).includes(s.type));
                                if (secretStores.length === 0)
                                    throw new Error(`Cannot define pull secret: No secret stores have been declared.`);
                                let storeChoices = secretStores.map((s, n) => { return { name: `${s.name} (${s.type})`, value: n }; });
                                let index = (yield this._ui.promptSelect("secretStore", "Secret Store?: ", storeChoices));
                                partial.storeName = secretStores[index].name;
                                partial.storeType = secretStores[index].type;
                                // Reset the secret name if we changed the secret store 
                                if (`${partial.storeType}|${partial.storeName}` !== original) {
                                    partial.secretName = undefined;
                                }
                            }
                            // SECRET
                            keepSecret = (partial.secretName === undefined) ? false : yield this._ui.promptConfirm("keepSecret", `Secret name: ${this._styler.hilite(partial.secretName)}`);
                            if (!keepSecret) {
                                // SELECT EXISTING...
                                this._ui.startSpinner("Loading list of secrets...");
                                // let secrets = await this.relayClient.listSecretsFrom(partial.storeName!);  // Legacy relay client
                                let secrets = [];
                                if (partial.storeType != undefined && partial.storeName != undefined) {
                                    let def = {
                                        name: partial.storeName,
                                        type: partial.storeType
                                    }; // it wants a provider, which can be inferred from type (and audit info)
                                    let keysRes = yield this.genRelayClient.Call.postReadVaultStoreKeys({ kvpStoreDef: def }); // cast is due to id property
                                    if (keysRes.error)
                                        throw keysRes.error;
                                    secrets = (_c = keysRes.output) !== null && _c !== void 0 ? _c : [];
                                }
                                this._ui.stopSpinner(`found ${secrets.length}`);
                                let origSecretName = partial.secretName;
                                partial.secretName = undefined;
                                if (secrets.length > 0) {
                                    let choices = secrets.map(s => { return { name: s, value: s }; });
                                    if (allowAddSecrets === true) {
                                        choices.push({ name: "** other **", value: codiac_constants_1.CliConstants.PROMPT_ME_DESIGNATOR });
                                    }
                                    partial.secretName = (yield this._ui.promptSelect("secretName", "Secret name: ", choices));
                                    keepSecret = true;
                                }
                                // DECLARE NEW...
                                if (allowAddSecrets === true && (partial.secretName === undefined || partial.secretName === codiac_constants_1.CliConstants.PROMPT_ME_DESIGNATOR)) {
                                    partial.secretName = (yield this._ui.prompt("newSecretName", "Name of NEW secret: ", true, origSecretName));
                                    let secretValue = yield this._ui.promptPassword("newSecret", "Enter the secret value: ");
                                    // TODO: write secret
                                    // this._ui.startSpinner(`Writing secret value to store...`);
                                    // await this.relayClient.setSecret(partial.storeName!, partial.secretName, secretValue);
                                    // this._ui.stopSpinner("complete.");
                                    keepSecret = true;
                                }
                                else if (secrets.length === 0) {
                                    this._ui.userMessage(`No secrets exist in [${partial.storeName}]`);
                                    keepSecret = false;
                                }
                            }
                        }
                        candidate = {
                            secretRef: new pull_secret_1.PullSecretRef(partial.storeType, partial.storeName, partial.secretName, partial.expiresOn),
                            isPrimary: partial.isPrimary,
                            regCode: partial.regCode
                        };
                        this._ui.userMessage((0, prettify_1.prettify)(candidate));
                        this._ui.userMessage(`You are about to set the ${candidate.isPrimary ? "primary" : "secondary"} pull secret for ${this._styler.bold(this._styler.hilite(candidate.regCode))} [Tenant: ${this._userCntx.currentTenantCode}].`);
                        confirmed = yield this._ui.promptConfirm("confirmPullSecret", "Continue?", false);
                    }
                    if (candidate === undefined)
                        throw new Error("No PullSecret was defined."); // this shd never happen; it's just here to pacify typescript.
                    resolve(candidate);
                }
                catch (err) {
                    // this.logger.error(`${err.name ?? ''} ${err.message ?? ''}`, err.stack);
                    reject(err);
                }
            }));
        });
    }
    applyPullSecret(pullSecretRef, isPrimary, registry) {
        return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
            var _a;
            try {
                let pullSecretField = isPrimary ? "pullSecret" : "pullSecretSecondary";
                let patchSpec = {
                    id: registry.id,
                    operations: [
                        {
                            op: contracts_2.patchOperationType.replace,
                            path: pullSecretField,
                            value: pullSecretRef
                        }
                    ]
                };
                let result = yield this._codiacApi.ImageRegistry.tryPatch(patchSpec);
                if (result.success) {
                    this._ui.userMessage(`Successfully updated ${pullSecretField} on ${registry.type} image registry [${this._styler.hilite(registry.code)}]`);
                }
                else if (result.error) {
                    this._ui.userMessage(`${this._styler.colorCritical("Error")} updating ${pullSecretField} on ${registry.type} image registry [${registry.code}]: ${result.error.name} ${(_a = result.error.message) !== null && _a !== void 0 ? _a : result.error.stack}`);
                }
                resolve();
            }
            catch (err) {
                this.logger.error(err, pullSecretRef);
                resolve();
                // reject(err);
            }
        }));
    }
    buildScript(candidate) {
        let output;
        try {
            output = `codiac imageRegistry:pullSecret:set --silent`;
            let args;
            let flags = {};
            flags.registry = candidate.regCode;
            if (candidate.isPrimary !== true)
                flags.item = "secondary";
            flags.storeType = candidate.secretRef.type;
            flags.storeName = candidate.secretRef.name;
            flags.name = candidate.secretRef.key;
            Object.keys(flags).forEach(k => output = output.concat(" ", `--${k}=${flags[k]}`));
            return output;
        }
        catch (err) {
            throw err;
        }
    }
}
exports.default = ImageRegistryPullSecretSet;
ImageRegistryPullSecretSet.description = 'Upserts the primary and secondary pull secret references for the given container registry.';
ImageRegistryPullSecretSet.aliases = ['pullsecret'];
ImageRegistryPullSecretSet.dependsOn = ["registry", "storeType", "storeName", "name"];
ImageRegistryPullSecretSet.flags = {
    // kit: Flags.string({ char: 'k', required: false, exclusive: ["image", "registry"], description: "The name of the kit (with library prefix, eg: 'library/kitName') to use to create the asset.  Use '?' to be prompted for a kit (Optional: used instead of calling for the image)." }),
    registry: core_1.Flags.string({ char: 'r', required: false, exclusive: ["kit"], description: "The container registry in which the image exists (defaults to docker hub)." }),
    // name: Flags.string({ char: 'n', required: false, description: "The name to give the enterprise asset (Optional: defaults to the image name without the scope prefix)." }),
    // code: Flags.string({ char: 'c', required: false, description: "The host name to assign this asset in the domain url.  eg: 'myasset' in 'myasset.your-domain.com' (Optional: defaults to the asset name)." }),
    // enterprise: Flags.string({ char: 'e', required: false, description: 'The name of the enterprise in which to create this asset. (defaults to the current enterprise context)' }),
    item: core_1.Flags.string({ char: 'f', options: ["primary", "secondary"], required: false, default: "primary", description: "Applies the command to either the primary (default) or secondary secret for the image registry." }),
    scope: core_1.Flags.string({ char: 'f', options: Object.values(codiac_domain_1.PullSecretScopesEnum), required: false, description: " Applicability of the pullsecret.  Allows for other authorization methods when the cluster and image registry are in the same subscription." }),
    storeType: core_1.Flags.string({ char: 't', required: false, options: Object.values(contracts_1.KvpStoreTypesEnum), description: "The type of secret store containing the actual pull secrets." }),
    storeName: core_1.Flags.string({ char: 's', required: false, description: "The name of the secret store containing the actual pull secrets." }),
    name: core_1.Flags.string({ char: 'n', required: false, description: "The name of the secret within the secret store." }),
    toScript: core_1.Flags.boolean({ char: 'z', required: false, description: `Outputs an assembled command string, NO execution` }),
    silent: core_1.Flags.boolean({ char: 'y', dependsOn: ImageRegistryPullSecretSet.dependsOn, required: false, description: `(Optional: defaults to false) Prevents confirmations of user-values that are remembered from prior runs. Requires: ${ImageRegistryPullSecretSet.dependsOn}` }),
    help: core_1.Flags.help({ char: 'h' })
};
ImageRegistryPullSecretSet.args = {};
//# sourceMappingURL=set.js.map