"use strict";
var CloudProviderContextualizer_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CloudProviderContextualizer = void 0;
const tslib_1 = require("tslib");
const inversify_1 = require("inversify");
const contracts_1 = require("@codiac.io/codiac-common/contracts");
const cloud_resource_configurator_factory_1 = require("./cloud-resource-configurator-factory");
const codiac_relay_1 = require("../apis/codiac-relay");
const contracts_2 = require("../apis/codiac-relay/contracts");
const sdk_1 = require("../sdk");
const ops_1 = require("../ops");
/** Prompts the user for a cloud resource provider,
 *  their credentials there, attempts a login,
 *  and then persists the credentials into the keyring under that tenant and provider.
 *
 * In subsequent sessions, this logic will allows the user to confirm/change accounts
 * for the given provider if they are already authenticated.
 */
let CloudProviderContextualizer = CloudProviderContextualizer_1 = class CloudProviderContextualizer {
    constructor(relayClient, loginUiFactory, _ui, _styler) {
        this.relayClient = relayClient;
        this.loginUiFactory = loginUiFactory;
        this._ui = _ui;
        this._styler = _styler;
        this.takeDefaults = false;
    }
    static bootstrap(ioc) {
        if (this.isBootstrapped !== true) {
            let sdk = new sdk_1.CodiacSdkForCli(ioc);
            // COMMENTED OUT BC THEYRE FUTURE AND THEY COLLIDE WITH EXISTING IOC REGISTRATIONS
            // sdk.registerCspAdapter(AzureCliAdapter);
            // sdk.registerCspAdapter(AwsCliAdapter);
            // sdk.registerCspAdapter(DockerHubCliAdapter);
            // sdk.registerCspAdapter(ArtifactHubCliAdapter);
            // sdk.registerCspAdapter(LocalMachineCliAdapter);
            cloud_resource_configurator_factory_1.LoginConfiguratorFactory.bootstrap(ioc);
            if (!ioc.isBound(CloudProviderContextualizer_1)) {
                ioc.bind(CloudProviderContextualizer_1).toSelf();
            }
            this.isBootstrapped = true;
        }
    }
    promptForCloudProvider(allPvds, defaultValue) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                let output;
                try {
                    // let allPvds = this._regClientFactory.getAllProviders();
                    // TODO: support the default selection
                    let keep = this.takeDefaults;
                    if (defaultValue && allPvds.find(x => x == defaultValue) && (!this.takeDefaults)) {
                        keep = yield this._ui.promptConfirm("keepPvd", `Cloud Provider: ${this._styler.hilite(defaultValue)} `);
                    }
                    if (keep && defaultValue !== undefined) {
                        output = defaultValue;
                    }
                    else {
                        output = (yield this._ui.promptSelect("pvdName", "Cloud Provider:", allPvds.map(t => { return { name: t, value: t }; })));
                    }
                    resolve(output);
                }
                catch (err) {
                    // this.logger.error(`${err.name ?? ''} ${err.message ?? ''}`, err.stack);
                    reject(err);
                }
            }));
        });
    }
    /** Suffixes the username with its applicable csp subscriptionId
     *
     * as:
     * ```
     * "<username> (<subscriptionId>)"
     * ```
     * @param defaultSubxId Renders when user.subscriptionId is undefined.
     * */
    userAndSubx(user, defaultSubxId, hilite = false) {
        var _a, _b, _c;
        let userString = (hilite) ? this._styler.hilite(user.username) : user.username;
        return ((_a = user.subscriptionId) !== null && _a !== void 0 ? _a : defaultSubxId) ? userString.concat(" (", user.provider == contracts_2.CloudProviderEnum.aws ? "Acct:" : "Subx:", (_c = (_b = user.subscriptionId) !== null && _b !== void 0 ? _b : defaultSubxId) !== null && _c !== void 0 ? _c : "", ")") : userString;
    }
    /** Enables the user to enter, use existing, or change the credentials for the given cloud service provider.
     *
     * Throws on authentication failure of cached creds,
     * or loops a login prompt otherwise.
     *
     * TODO: Return an authenticated user object.
     * @returns username
     *
     * @param suppressConfirmations TODO: Remove this argument and apply the non-tty ui provider for silent mode.
     * @param providerUserName TODO: Remove this argument and rely on relay state (via separate login step).
     * @param providerSecret TODO: Remove this argument and rely on relay state (via separate login step).
     * @param proprietaryInput Input specific to the selected cloud provider. (eg: Aws session token).
     */
    promptForCloudProviderCreds(ioc, providerCode, suppressConfirmations, providerUserName, providerSecret, reqSubscriptionId, proprietaryInput) {
        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, _d, _e, _f;
                let output;
                try {
                    // let regUser = await this.relayClient.getCurrentUserFor(providerCode);
                    let cachedCred;
                    // if (providerSubscriptionId) {
                    //   let userResults = await this.RelayClient.Call.get_current_users_for({ providerCode: providerCode, providerSubscriptionId: providerSubscriptionId });
                    //   if (userResults.error) throw userResults.error;
                    //   regUser = userResults.output ? userResults.output[0] : undefined;
                    // } else {
                    let userResults = new contracts_1.ListResult(false);
                    if ((providerUserName == undefined || providerUserName.trim() == "") && (providerSecret == undefined || providerSecret.trim() == "")) { // Skip existing creds if they gave us a username and password
                        userResults = yield this.relayClient.Call.get_current_users_for({ providerCode: providerCode, providerSubscriptionId: reqSubscriptionId });
                        if (userResults.error)
                            throw userResults.error;
                    }
                    let keep = this.takeDefaults || (suppressConfirmations !== null && suppressConfirmations !== void 0 ? suppressConfirmations : false);
                    if (userResults.output) {
                        if (userResults.output.length == 1) {
                            cachedCred = userResults.output[0];
                            // 1.a Confirm existing creds
                            if (cachedCred !== undefined) {
                                // if ((!this.takeDefaults) && (!(suppressConfirmations ?? false))) {
                                //   let prompt = `Continue in ${this._styler.hilite(providerCode)} as [${this.userAndSubx(cachedCred, reqSubscriptionId, true)}]?`;
                                //   keep = await this._ui.promptConfirm("reauth", prompt.concat(` (hit "n" to change)`));
                                // } else if (keep == true) {
                                this._ui.userMessage("Continuing in ".concat(this._styler.hilite(providerCode), " as [", this.userAndSubx(cachedCred, reqSubscriptionId, true), "]"));
                                keep = true;
                                // }
                            }
                        }
                        else if (userResults.output.length > 1) {
                            // 1.b Select existing creds
                            let index = 0;
                            let choices = userResults.output.map(x => { return { name: this.userAndSubx(x), value: index++ }; });
                            choices.push({ name: "** enter new credentials **", value: -1 });
                            const selected = (yield this._ui.promptSelect("cspSubxUser", `Select ${providerCode} credentials`, choices));
                            if (selected >= 0) {
                                cachedCred = userResults.output[selected];
                                keep = true;
                            }
                        }
                    }
                    // }
                    // 2. Attempt auth with existing creds
                    let isAuthed = false;
                    if (cachedCred && keep === true) {
                        this._ui.startSpinner(`Authenticating with ${providerCode}...`);
                        try {
                            const loginResult = yield this.relayClient.Call.csp_login({ providerCode: providerCode, providerSubscription: cachedCred.subscriptionId }); // We already have creds, but let's validate with the service anyway  (az cli is the only one who needs this bc it runs off a machine-user state and has to set the state on the local machine).
                            if (loginResult.error)
                                throw loginResult.error;
                            if (loginResult.output == undefined)
                                throw new Error(`Login failed ${(_a = loginResult.message) !== null && _a !== void 0 ? _a : "[no details provided]"}`);
                            output = loginResult.output.cspUser;
                            this._ui.stopSpinner(`Success!`);
                            isAuthed = true;
                        }
                        catch (err) {
                            this._ui.stopSpinner(`Failed.`);
                            if (err.message)
                                this._ui.userMessage(err.message);
                            isAuthed = false;
                            // throw err;
                        }
                    }
                    // 3. If reqd, Log in interactively
                    if (!isAuthed) {
                        let loginUi = this.loginUiFactory.create(providerCode, ioc);
                        let cspUser;
                        // if (suppressConfirmations)
                        let subx = (_b = cachedCred === null || cachedCred === void 0 ? void 0 : cachedCred.subscriptionId) !== null && _b !== void 0 ? _b : reqSubscriptionId;
                        let loginOptions = {
                            providerSubscriptionId: subx,
                            suppressConfirmations: suppressConfirmations,
                            providerUserName: providerUserName,
                            providerSecret: providerSecret,
                            proprietaryInput: proprietaryInput
                        };
                        [isAuthed, cspUser] = yield loginUi.promptToLogin(loginOptions);
                        if (!isAuthed)
                            throw new Error(`Login to ${providerCode} failed${((_c = cspUser.username) !== null && _c !== void 0 ? _c : providerUserName) ? " for " + ((_d = cspUser.username) !== null && _d !== void 0 ? _d : providerUserName) : ""}${((_e = cspUser.subscriptionId) !== null && _e !== void 0 ? _e : subx) ? " (subx:" + ((_f = cspUser.subscriptionId) !== null && _f !== void 0 ? _f : subx) + ")" : ""}.`);
                        output = cspUser;
                    }
                    resolve(output); // assertion operator ok bc logic forces output to be defined.
                }
                catch (err) {
                    // this.logger.error(`${err.name ?? ''} ${err.message ?? ''}`, err.stack);
                    reject(err);
                }
            }));
        });
    }
    /** Used when cloud service provider has special proprietary input
     * required for csp login that breaks the standard patterns.
     * This method allows that provider a chance to build the login request however they see fit. */
    assembleLoginRequest(ioc, providerCode, user, secret, subscriptionId, proprietaryInput) {
        let loginUi = this.loginUiFactory.create(providerCode, ioc);
        return loginUi.assembleLoginRequest(user, secret, subscriptionId, proprietaryInput);
    }
    /** Signs the user out from the given cloud service provider.
     *
     * @returns username
     *
     * @param suppressConfirmations TODO: Remove this argument and apply the non-tty ui provider for silent mode.
     * @param providedUserName TODO: Remove this argument and rely on relay state (via separate login step).
     */
    promptToLogout(ioc, providerCode, suppressConfirmations, providedUserName) {
        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 output;
                try {
                    let regUsers = yield this.relayClient.Call.get_current_users_for({ providerCode: providerCode });
                    let regUser;
                    if (regUsers.output && (regUsers.output.length > 0)) {
                        if (regUsers.output.length == 1) {
                            regUser = regUsers.output[0];
                        }
                        else {
                            // TODO: Consider selecting one of the many
                            regUser = regUsers.output[0];
                        }
                    }
                    // // 1. Confirm existing creds
                    // let keep = this.takeDefaults || (suppressConfirmations ?? false);
                    // if (regUser !== undefined && (!this.takeDefaults) && (!(suppressConfirmations ?? false))) {
                    //   keep = await this._ui.promptConfirm("reauth", `Continue in ${this._styler.hilite(providerCode)} as [${this._styler.hilite(regUser!)}]? (hit "n" to change)`);
                    // }
                    // 3. Sign out interactively
                    let loginUi = this.loginUiFactory.create(providerCode, ioc);
                    let succeeded;
                    let userName;
                    // if (suppressConfirmations)
                    [succeeded, userName] = yield loginUi.promptToLogout();
                    // TODO: Support a tri-state: 1) Completed successfully, 2) Failed with an error, and 3) tokens purged or not (and we arent waiting for popup)
                    if (!succeeded)
                        throw new Error(`Logout from ${providerCode} failed${providedUserName ? (_b = (_a = " for " + userName) !== null && _a !== void 0 ? _a : regUser) !== null && _b !== void 0 ? _b : providedUserName : ""}.`);
                    // while (!isAuthed && !suppressConfirmations) {
                    //   loginUi = this.loginUiFactory.create(providerCode, ioc);
                    //   [isAuthed, userName] = await loginUi.promptToLogin(undefined, suppressConfirmations, providedUserName, providedSecret);
                    // }
                    output = { username: (_c = userName !== null && userName !== void 0 ? userName : regUser === null || regUser === void 0 ? void 0 : regUser.username) !== null && _c !== void 0 ? _c : "" }; // assertion operator ok; logic forces it.
                    resolve(output.username); // operator ok bc logic forces output to be defined.
                }
                catch (err) {
                    // this.logger.error(`${err.name ?? ''} ${err.message ?? ''}`, err.stack);
                    reject(err);
                }
            }));
        });
    }
};
CloudProviderContextualizer.isBootstrapped = false;
CloudProviderContextualizer = CloudProviderContextualizer_1 = tslib_1.__decorate([
    (0, inversify_1.injectable)(),
    tslib_1.__param(0, (0, inversify_1.inject)(codiac_relay_1.RelayClient)),
    tslib_1.__param(1, (0, inversify_1.inject)(cloud_resource_configurator_factory_1.LoginConfiguratorFactory)),
    tslib_1.__param(2, (0, inversify_1.inject)(ops_1.INTERFACES_CLI.IUserInputProvider)),
    tslib_1.__param(3, (0, inversify_1.inject)(ops_1.INTERFACES_CLI.IOutputStyler)),
    tslib_1.__metadata("design:paramtypes", [codiac_relay_1.RelayClient,
        cloud_resource_configurator_factory_1.LoginConfiguratorFactory, Object, Object])
], CloudProviderContextualizer);
exports.CloudProviderContextualizer = CloudProviderContextualizer;
//# sourceMappingURL=cloud-provider-contextualizer.js.map