"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AzureInteractiveLoginConfigurator = void 0;
const tslib_1 = require("tslib");
const open = require("open");
const _ = require("lodash");
const inversify_1 = require("inversify");
const codiac_common_1 = require("@codiac.io/codiac-common");
const codiac_domain_1 = require("@codiac.io/codiac-domain");
const codiac_ops_common_1 = require("@codiac.io/codiac-ops-common");
const codiac_relay_1 = require("../../apis/codiac-relay");
const entities_1 = require("../../ops/entities");
const ops_1 = require("../../ops");
function unblockAndSleep(durationInSecs) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        return new Promise(resolve => {
            setTimeout(() => resolve(), durationInSecs * 1000);
        });
    });
}
/** Implements AuthorizationCode Flow with a popup-browser interactive login
 * for user to authenticate and provide consent.
 */
let AzureInteractiveLoginConfigurator = class AzureInteractiveLoginConfigurator {
    constructor(opMeta, genRelayClient, msgClient, codiacUser, _ui, _styler, logger) {
        this.opMeta = opMeta;
        this.genRelayClient = genRelayClient;
        this.msgClient = msgClient;
        this.codiacUser = codiacUser;
        this._ui = _ui;
        this._styler = _styler;
        this.logger = logger;
    }
    get cloudProvider() {
        return codiac_domain_1.CloudProviderEnum.azure;
    }
    /** Performs an EXPLICIT login sequence in ALL CASES.
     * It does NOT attempt or defer to a silent authentication.
     *
     * iow:  Invoke this when the silent methods have failed. */
    promptToLogin(options) {
        var _a, _b, _c;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                // 1. Establish 2-way messaging channel w the relay
                let commChannel;
                // if (webSocketId) {
                //   commChannel = { socketId: webSocketId };
                // } else {
                //   commChannel = {
                //     socketId: await this.openWebSocket()
                //   };
                // }
                // commChannel = {
                //   socketId: await this.msgClient.connect()  // .start()
                // };
                commChannel = {
                    socketId: "not-used"
                };
                // 2. Get the login url
                const req = {
                    providerCode: this.cloudProvider,
                    commChannel: commChannel,
                    correlationId: this.opMeta.correlationId,
                    providerSubscriptionId: options === null || options === void 0 ? void 0 : options.providerSubscriptionId,
                    providerTenantId: options === null || options === void 0 ? void 0 : options.providerTenantId
                };
                let authResult = yield this.genRelayClient.Call.csp_login_interactive(req, {});
                if (authResult.error)
                    throw authResult.error;
                let loginUrl = (_a = authResult.output) === null || _a === void 0 ? void 0 : _a.loginUrl;
                if (loginUrl == undefined)
                    throw new Error(`No interactive login url found.`);
                // 3. Trigger the external auth sequence in a browser window.
                let [success, username] = yield this.invokeBrowserSequence(loginUrl);
                let cspUser = {
                    tenantCode: this.codiacUser.tenantCode,
                    provider: this.cloudProvider,
                    // subscriptionId: undefined,   // <-- Azure deals with this explicitly later
                    username: username,
                    created: new Date(Date.now()),
                    cspTenantId: undefined,
                    subscriptionId: undefined
                };
                return [success, cspUser];
            }
            catch (err) {
                if (_.isEmpty(err.message)) {
                    this.logger.error(`Error logging into ${this.cloudProvider}`, err);
                }
                throw new Error(`Error logging into ${this.cloudProvider}: ${(_b = err.name) !== null && _b !== void 0 ? _b : ""} ${(_c = err.message) !== null && _c !== void 0 ? _c : "(not details available)"}`);
                // } finally {
                //   if (this.msgClient.isConnected()) {
                //     await this.msgClient.disconnect();
                //   }
            }
        });
    }
    assembleLoginRequest(user, secret, subscriptionId, proprietaryInput) {
        throw new Error('Azure does not use proprietary input for csp login.');
    }
    /** Opens the the external login url in a browser window.
     * Returns at the end of the sequence.
     * Throws on any errors.
     */
    invokeBrowserSequence(loginUrl) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                try {
                    let isCompleted = false;
                    let isAuthed = false; // Read this from the websocket message / fail / timeout.
                    let cspUserName; // Read this from the websocket message
                    // if (!this.msgClient.isConnected()) {
                    //   // Why didnt we need this yesterday, but we suddenly need this assertConnection() now ???
                    //   // BECAUSE TokenCreds was previously bound as a class but is now bound as TokenCreds.SYMBOL
                    //   await this.msgClient.connect();
                    // }
                    let handlerId = this.msgClient.subscribe("loginComplete", (err, msg, meta, raw) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                        return new Promise((resolve) => {
                            var _a, _b;
                            isCompleted = true;
                            // TODO: get these from the completion message
                            isAuthed = Boolean((_a = msg === null || msg === void 0 ? void 0 : msg.success) !== null && _a !== void 0 ? _a : "false") === true;
                            cspUserName = (_b = msg === null || msg === void 0 ? void 0 : msg.cspUserName) !== null && _b !== void 0 ? _b : "anonymous@domain.com";
                            // const responded = (<nats.Msg>raw).respond(msg as nats.Payload, {});
                            this.logger.debug(`Interactive ${this.cloudProvider} login sequence completed [${cspUserName}]`, msg);
                            this.msgClient.unsubscribe("loginComplete", { subscriptionId: handlerId });
                            // resolve([isAuthed, cspUserName]);
                            resolve();
                            // return;
                        });
                    }), { max: 1 }); // TODO: Consider using the timeout below here instead/both? { timeout: 5*60*1000 }
                    // 3. Sprout the login interaction in a browser popup
                    let childProcess = yield open(loginUrl, {});
                    // 4. Wait for the completion message via websocket
                    let timeout = Date.now() + 300000; // 5 mins
                    this._ui.startSpinner(`Waiting on interactive login...`);
                    while (!isCompleted && Boolean(Date.now() <= timeout)) {
                        yield unblockAndSleep(0.25);
                    }
                    if (isCompleted) {
                        // Kill the popup browser process (ie: close the window) cuz we know the sequence has completed.
                        // childProcess.kill();  // Commneted out bc it doesnt actually close the browser window.
                        this._ui.stopSpinner("Completed");
                        resolve([isAuthed, cspUserName !== null && cspUserName !== void 0 ? cspUserName : ""]);
                    }
                    else if (Date.now() > timeout) {
                        // console.log(".");
                        this._ui.stopSpinner("Timed out");
                        resolve([false, "none"]); // <--- This is our safety net
                    }
                    // let intervalId = setInterval(() => {
                    //   if (isCompleted) {
                    //     // Kill the popup browser process (ie: close the window) cuz we know the sequence has completed.
                    //     // childProcess.kill();  // Commneted out bc it doesnt actually close the browser window.
                    //     this._ui.stopSpinner("Completed");
                    //     clearInterval(intervalId);
                    //     resolve([isAuthed, cspUserName ?? ""])
                    //   } else if (Date.now() > timeout) {
                    //     // console.log(".");
                    //     this._ui.stopSpinner("Timed out");
                    //     clearInterval(intervalId);
                    //     resolve([false, "none"]);  // <--- This is our safety net
                    //   }
                    // }, 250);
                }
                catch (err) {
                    reject(err);
                }
            }));
        });
    }
    /** Performs an EXPLICIT logout sequence in ALL CASES.
     * It does NOT attempt or defer to a silent authentication.
     *  */
    promptToLogout(accountFilter) {
        var _a, _b, _c, _d;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            // try {
            //     // if (webSocketId) {
            //     //   commChannel = { socketId: webSocketId };
            //     // } else {
            //     //   commChannel = {
            //     //     socketId: await this.openWebSocket()
            //     //   };
            //     // }
            //   } catch (err) {
            //     const errMsg = "Error establishing messaging connection: ".concat(err.message ?? "no details available");
            //     this.logger.error(errMsg, err.stack);
            //     throw new Error(errMsg);
            //   }
            try {
                // 1. Establish 2-way messaging channel w the relay
                let commChannel;
                commChannel = {
                    // socketId: await this.msgClient.connect()  // .start()
                    socketId: "not-used"
                };
                // 2. Get the logout url
                let authResult = yield this.genRelayClient.Call.csp_logout_interactive({ providerCode: this.cloudProvider, accountRef: accountFilter, commChannel: commChannel, correlationId: this.opMeta.correlationId }, {});
                if (authResult.error)
                    throw authResult.error;
                let logoutUrl = (_a = authResult.output) === null || _a === void 0 ? void 0 : _a.logoutUrl;
                if (logoutUrl == undefined)
                    throw new Error(`No interactive login url found.`);
                // 3. Trigger the external auth sequence in a browser window.
                let result = yield this.invokeLogoutBrowserSequence(logoutUrl, (_b = authResult.output) === null || _b === void 0 ? void 0 : _b.cspUserName);
                return result;
            }
            catch (err) {
                if (_.isEmpty(err.message)) {
                    this.logger.error(`Error logging out from ${this.cloudProvider}`, err);
                }
                throw new Error(`Error logging out from ${this.cloudProvider}: ${(_c = err.name) !== null && _c !== void 0 ? _c : ""} ${(_d = err.message) !== null && _d !== void 0 ? _d : "(no details available)"}`);
                // } finally {
                //   if (this.msgClient.isConnected()) {
                //     await this.msgClient.disconnect();
                //   }
            }
        });
    }
    /** Opens the the external login url in a browser window.
   * Returns at the end of the sequence.
   * Throws on any errors.
   *
   * @param currentCspUserName (Required, but may be undefined) The username assocaited with any tokens or credentials we may currently have on file for the given Cloud Service Provider (CSP).  If this argument is undefined or empty, this command will NOT wait for the logout sequence to complete in the browser.
   */
    invokeLogoutBrowserSequence(logoutUrl, currentCspUserName) {
        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;
                try {
                    let isCompleted = false;
                    let isAuthed = false; // Read this from the websocket message / fail / timeout.
                    let cspUserName; // Read this from the websocket message
                    /** If false, this method will simply open the logout popoup without standing by for the ultimate result of the logout sequence.
                     *
                     * Set this to false when we already have no tokens or credentials to clear for that cloud provider.
                     *
                     * EXPLANATION: We need this parameter because Azure decides AFTER we open the popup whether or not to redirect to our completion endpoint
                     * *(it wont redirect if we are already logged out. Remember: Microsoft caches our csp session in the browser,
                     * whereas Codiac caches our tokens in encrypted stores on the local machine)*.
                     * That is, if we are already logged out, then we're gonna end up hanging here waiting for a message that'll never come (thanks, M$).
                     * SO instead, we can set this parameter to false when we dont have any creds or tokens on file,
                     * then we can just popup the logout page and not worry about waiting for a response,
                     * because we dont have anything to clean up on our end.
                     *
                     * Do we need to fire the logout at all in that scenario?  Perhaps. We may want to run this command anyway to make sure our systems are in sync. */
                    let waitForResult = !_.isEmpty(currentCspUserName);
                    let subxId;
                    if (waitForResult) {
                        // if (!this.msgClient.isConnected()) {
                        //   // Why didnt we need this yesterday, but we suddenly need this assertConnection() now ???
                        //   // BECAUSE TokenCreds was previously bound as a class but is now bound as TokenCreds.SYMBOL
                        //   await this.msgClient.connect();
                        // }
                        subxId = this.msgClient.subscribe("logoutComplete", (err, msg, meta, raw) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                            var _b;
                            try {
                                isCompleted = true;
                                // TODO: get these from the completion message
                                isAuthed = true;
                                cspUserName = currentCspUserName;
                                this.logger.debug(`Interactive logout sequence completed`, msg);
                                this.msgClient.unsubscribe("logoutComplete", { subscriptionId: subxId });
                            }
                            catch (err) {
                                this.logger.error(`Error handling logoutComplete on [subxId: ${subxId}]: ${(_b = err.message) !== null && _b !== void 0 ? _b : "no details available"}`, err.stack);
                            }
                            finally {
                                return;
                            }
                        }), { max: 1 }); // TODO: Consider using the timeout below here instead/both? { timeout: 5*60*1000 }
                    }
                    // 3. Sprout the login interaction in a browser popup
                    let childProcess = yield open(logoutUrl, {});
                    if (waitForResult) {
                        // 4. Wait for the completion message via websocket
                        let timeout = Date.now() + 300000; // 5 mins
                        this._ui.startSpinner(`Waiting on interactive logout...`);
                        while (!isCompleted && Boolean(Date.now() <= timeout)) {
                            yield unblockAndSleep(0.25);
                        }
                        if (isCompleted) {
                            // Kill the popup browser process (ie: close the window) cuz we know the sequence has completed.
                            // childProcess.kill();  // Commneted out bc it doesnt actually close the browser window.
                            this._ui.stopSpinner("Completed");
                            resolve([isAuthed, (_a = cspUserName !== null && cspUserName !== void 0 ? cspUserName : currentCspUserName) !== null && _a !== void 0 ? _a : ""]);
                        }
                        else if (Date.now() > timeout) {
                            // console.log(".");
                            this._ui.stopSpinner("Timed out");
                            resolve([false, "none"]); // <--- This is our safety net
                        }
                        // let intervalId = setInterval(() => {
                        //   if (isCompleted) {
                        //     // Kill the popup browser process (ie: close the window) cuz we know the sequence has completed.
                        //     // childProcess.kill();  // Commneted out bc it doesnt actually close the browser window.
                        //     this._ui.stopSpinner("Completed");
                        //     clearInterval(intervalId);
                        //     resolve([isAuthed, cspUserName ?? currentCspUserName ?? ""])
                        //   } else if (Date.now() > timeout) {
                        //     // console.log(".");
                        //     this._ui.stopSpinner("Timed out");
                        //     clearInterval(intervalId);
                        //     resolve([false, "none"]);  // <--- This is our safety net
                        //   }
                        // }, 250);
                    }
                    else {
                        resolve([true, ""]);
                    }
                }
                catch (err) {
                    reject(err);
                }
            }));
        });
    }
};
AzureInteractiveLoginConfigurator = tslib_1.__decorate([
    (0, inversify_1.injectable)(),
    tslib_1.__param(0, (0, inversify_1.inject)(entities_1.OperationMeta)),
    tslib_1.__param(1, (0, inversify_1.inject)(codiac_relay_1.RelayClient)),
    tslib_1.__param(2, (0, inversify_1.inject)(ops_1.INTERFACES_CLI.IMessagingClient)),
    tslib_1.__param(3, (0, inversify_1.inject)(codiac_ops_common_1.UserNameTenantCode)),
    tslib_1.__param(4, (0, inversify_1.inject)(ops_1.INTERFACES_CLI.IUserInputProvider)),
    tslib_1.__param(5, (0, inversify_1.inject)(ops_1.INTERFACES_CLI.IOutputStyler)),
    tslib_1.__param(6, (0, inversify_1.inject)(codiac_common_1.INTERFACES.IBetterLogger)),
    tslib_1.__metadata("design:paramtypes", [entities_1.OperationMeta,
        codiac_relay_1.RelayClient, Object, codiac_ops_common_1.UserNameTenantCode, Object, Object, Object])
], AzureInteractiveLoginConfigurator);
exports.AzureInteractiveLoginConfigurator = AzureInteractiveLoginConfigurator;
//# sourceMappingURL=az-interactive-login-configurator.js.map