"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const core_1 = require("@oclif/core");
const host_name_strategy_enum_1 = require("@codiac.io/codiac-domain/host-name-strategy-enum");
const contracts_1 = require("../../apis/codiac-api/contracts");
const command_base_enterprise_1 = require("../../command-base-enterprise");
const delete_1 = require("./delete");
const type_safety_utils_1 = require("../../uilogic/type-safety-utils");
class HostMap extends command_base_enterprise_1.CommandBaseEnterprise {
    run() {
        var _a;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            this.bootstrap();
            const { args, flags } = yield this.parse(HostMap);
            (_a = this._relayContainer) === null || _a === void 0 ? void 0 : _a.ready$.subscribe((ready) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                var _b;
                try {
                    this._userCntx = yield this.establishTenantContext(this._userCntx);
                    // let enterprise = (await this.establishEnterpriseContext()).enterprise;
                    // let aCntx = await this.establishAssetContext(enterprise, true);
                    // let cab = await this.establishCabinet(flags.cabinet, true);
                    // let scopesAvail: FullyQualifiedCabinetScopes = {
                    //   enterprises: [],
                    //   clusters: []
                    // }; 
                    // let scopesAvail = await repo.readMany(FullyQualifiedCabinetScopes, CabinetScopesCriteria, { all: true });
                    // let scopesAvailResult = await this._codiacApi.Call.getCabinetScopes({ all: true });
                    // if (scopesAvailResult.error) throw scopesAvailResult.error;
                    // let scopesAvail = scopesAvailResult.output ?? [];
                    // const rootMapResult = await this._codiacApi.Call.getRootMap({ all: true });
                    // if (rootMapResult.error) throw rootMapResult.error;
                    // if (rootMapResult.output == undefined) throw new Error("No enterprises found.");
                    // const rootMap = rootMapResult.output;
                    const rootMap = yield this.getRootMap();
                    let scopesAvail = yield this.getCabinetScopes(rootMap);
                    // this.logger.trace(`EnterpriseScopes: ${scopesAvail.length}`, scopesAvail);
                    let domain = (_b = args.domain) !== null && _b !== void 0 ? _b : (yield this._ui.prompt("domain", "Domain: "));
                    domain = domain.trim();
                    // Get the target scope
                    let scope = this.validateGivenTarget(flags, scopesAvail, flags.noninteractive);
                    if (!scope) {
                        let defLevel = (flags.cabinet) ? "cabinet" : ((flags.environment) ? "environment" : (flags.enterprise ? "enterprise" : undefined));
                        let level = yield this._ui.promptSelect("scope", "Scope", ["enterprise", "environment", "cabinet"]); // default = defLevel
                        if (!level) {
                            this._ui.userMessage("Operation cancelled.  No work was done.");
                        }
                        else {
                            // MarkStarthere("Make sure cabinet and environment scopes are fully qualified with enteprise");
                            let targets = this.getAvailableTargets(level, scopesAvail);
                            if (targets.length == 0) {
                                this._ui.userMessage(`No ${level}s were found.  Operation cancelled.`);
                            }
                            else {
                                let selected = yield this._ui.promptSelect("target", level, targets);
                                if (!selected) {
                                    this._ui.userMessage("Operation cancelled.  No work was done.");
                                }
                                else {
                                    // Parses the fully qualified scope from the target selection
                                    scope = this.parseSelectedTarget(level, selected);
                                }
                            }
                        }
                    }
                    let thisMachine = this.isLocalMachine(scope, rootMap);
                    // Get the host naming strategy
                    let naming;
                    if (scope) {
                        naming = this.validateGivenNaming(flags.naming, flags.noninteractive);
                        if (!naming) {
                            const strategies = Object.values(host_name_strategy_enum_1.HostNameStrategyEnum).sort().map(x => {
                                return { value: x, name: x.replace(/_dot_/g, ".").replace(/_slash_/g, "/") };
                            });
                            let selected = yield this._ui.promptSelect("naming", "Host Naming Strategy", strategies);
                            if (!selected) {
                                this._ui.userMessage("Operation cancelled.  No work was done.");
                            }
                            else {
                                naming = selected;
                            }
                        }
                    }
                    // External/Internal | Public/Private
                    let isPrivate = type_safety_utils_1.TypeSafetyUtils.toBoolean(flags.private);
                    let keep = yield this._ui.promptConfirm("isPrivateKeep", "Usage: ".concat(this._styler.hilite(isPrivate ? "private" : "external")), true);
                    if (!keep) {
                        let choices = [{ name: "External (Public DNS)", value: "false" }, { name: "Private (not available on the internet)", value: "true" }];
                        let selected = yield this._ui.promptSelect("isPrivate", "Usage:", choices, false, true);
                        isPrivate = type_safety_utils_1.TypeSafetyUtils.toBoolean(selected);
                    }
                    if (scope && naming) {
                        this.logger.trace("Scope", scope);
                        this.logger.trace("domain", domain);
                        this.logger.trace("Naming", naming);
                        let preview = yield this.buildPreview(scope, domain, naming, isPrivate);
                        this._ui.userMessage("You are about to submit the following domain host mapping strategy: ".concat("\n", preview));
                        let confirm = yield this._ui.promptConfirm("confirmGo", "Continue? ", false);
                        if (!confirm) {
                            this._ui.userMessage("Cancelled.  No work was done.");
                        }
                        else {
                            this._ui.startSpinner("Applying domain mapping");
                            let result = yield this.genRelayClient.Call.executeDomainUpsert({
                                domain: domain.trim(),
                                namingStrategy: naming,
                                scope: scope.level,
                                target: scope.target,
                                enterprise: scope.enterprise,
                                isPrivate: isPrivate,
                                thisMachine: thisMachine
                            });
                            this._ui.stopSpinner(result.success ? "Completed" : "Failed");
                            if (result.success) {
                                this._ui.userMessage(`Success!`);
                            }
                            else if (result.error) {
                                this.logger.debug(`Error`, result);
                                this._ui.userMessage(`Error: ${result.error.message}`);
                            }
                        }
                    }
                }
                catch (err) {
                    this.handleError(err);
                }
                finally {
                    yield this.closeConnections();
                    return;
                }
            }));
        });
    }
    /** Determines whether the given scope is local machine or not. */
    isLocalMachine(scope, rootMap) {
        let isLocal;
        if (scope && (scope.level != contracts_1.ScopeLevel.enterprise)) {
            let ent = rootMap.enterprises.find(e => e.name == scope.enterprise);
            if (ent != undefined) {
                switch (scope.level) {
                    case contracts_1.ScopeLevel.environment:
                        const env = ent.environments.find(e => e.name == scope.target);
                        isLocal = env === null || env === void 0 ? void 0 : env.thisMachine;
                        break;
                    case contracts_1.ScopeLevel.cabinet:
                        for (let env of ent.environments) {
                            let cab = env.cabinets.find(c => c.name == scope.target);
                            isLocal = cab === null || cab === void 0 ? void 0 : cab.thisMachine;
                        }
                        break;
                }
            }
        }
        return isLocal;
    }
    /** Consider moving this to RootMapUtils. */
    getCabinetScopes(rootMap) {
        var _a;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                let scopes = [];
                for (let ent of rootMap.enterprises) {
                    let entScope = { id: undefined, name: ent.name, environments: [] };
                    for (let env of ent.environments) {
                        let cabs = env.cabinets; // .filter(c => c.enterpriseCode == ent.code);
                        let envScope = {
                            name: env.name,
                            cabinets: cabs.map(c => { return { name: c.name, cluster: c.cluster }; })
                        };
                        entScope.environments.push(envScope);
                        // cabs.forEach(c => {
                        //   if (c.cluster && !scopes.clusters.includes(c.cluster)) scopes.clusters.push(c.cluster);
                        // });
                    }
                    scopes.push(entScope);
                    // this.logger.write(`Available scopes: ${scopes.length} enterprises.`);
                    // this.logger.write(JSON.stringify(scopes, undefined, 2));
                }
                return scopes;
            }
            catch (err) {
                err.message = "Error loading cabinet scopes: ".concat((_a = err.message) !== null && _a !== void 0 ? _a : "(no message given. check logs for more info)");
                throw err;
            }
        });
    }
    buildPreview(scope, domain, naming, isPrivate) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            // TODO: Ask the relay to assemble a preview of all the resolved urls for each service, rendered by cabinet
            // return `${domain} as ${naming} ---> ${scope.enterprise?.concat(">") ?? ""}${scope.level}>${scope.target}`;
            let candidate = {
                enterprise: scope.enterprise, domainName: domain, strategy: naming, scope: scope.level, target: scope.target, isPrivate: isPrivate
            };
            return delete_1.HostNameUiLogic.displayNameFor(candidate, this._styler); // casting to ignore audit fields
        });
    }
    validateGivenNaming(naming, nonInteractive = false) {
        let selected = (naming && naming in host_name_strategy_enum_1.HostNameStrategyEnum) ? naming : undefined;
        if (!selected && nonInteractive) {
            throw new Error(`Invalid host naming strategy [${naming}].`);
        }
        return selected;
    }
    /** Returns a sorted array of all available scope targets for the given level. */
    getAvailableTargets(level, scopesAvail) {
        let targets = [];
        switch (level) {
            case "enterprise":
                targets = scopesAvail.map(e => e.name); // TODO: sorty by e.name
                break;
            case "environment":
                scopesAvail.forEach(e => {
                    e.environments.forEach(v => {
                        if (!targets.includes(v.name)) {
                            targets.push({ name: `${e.name}/${v.name}`, value: `${e.name}/${v.name}` });
                        }
                    });
                });
                break;
            case "cabinet":
                scopesAvail.forEach(e => {
                    e.environments.forEach(v => {
                        v.cabinets.forEach(c => {
                            if (!targets.includes(c.name)) {
                                targets.push({ name: `${e.name}/${v.name}/${c.name}`, value: `${e.name}/${v.name}/${c.name}` });
                            }
                        });
                    });
                });
                break;
            default:
                throw new Error(`Invalid scope: [${level}].`);
        }
        return targets;
    }
    /** Parses the fully-qualified scope from the selected target,
     * which was built by this.getAvailableTargets().
     */
    parseSelectedTarget(level, selected) {
        let scope;
        let parts;
        switch (level) {
            case "enterprise":
                scope = { level: level, target: selected };
                break;
            case "environment":
                parts = selected.split("/");
                scope = {
                    level: level,
                    target: parts[1],
                    enterprise: parts[0]
                };
                break;
            case "cabinet":
                parts = selected.split("/");
                scope = {
                    level: level,
                    target: parts[2],
                    enterprise: parts[0]
                };
                break;
        }
        return scope;
    }
    /** Returns the requested target by name if it exists in the available scopes.
     * Returns undefined otherwise.
     * Throws on an invalid scopes if not interactive.
     */
    validateGivenTarget(given, scopesAvail, nonInteractive = false) {
        let scope;
        let level = (given.cabinet) ? "cabinet" : (given.environment) ? "environment" : (given.enterprise) ? "enterprise" : undefined;
        if (level) {
            let target;
            let enterprise;
            switch (level) {
                case "enterprise":
                    if (scopesAvail.length == 0) {
                        if (nonInteractive) {
                            throw new Error(`Cannot scope to the enterprise level because no enterprises currently exist.`);
                        }
                    }
                    if (given.enterprise) { // if they gave us one, it's gotta exist
                        target = (scopesAvail.find(e => e.name == given.enterprise)) ? given.enterprise : undefined;
                    }
                    else if (scopesAvail.length == 1) {
                        // if they called for the "only", there can be only one.
                        target = scopesAvail[0].name;
                    }
                    else {
                        if (nonInteractive) {
                            throw new Error(`Enterprise argument with unspecified value was ambiguous because there are more than one enterprise available.`);
                        }
                    }
                    break;
                case "environment":
                    let envs = [];
                    scopesAvail.forEach(ent => {
                        ent.environments.forEach(env => {
                            if (!envs.find(x => x.ent == ent.name && x.env == env.name))
                                envs.push({ ent: ent.name, env: env.name });
                        });
                    });
                    if (envs.length == 0) {
                        if (nonInteractive) {
                            throw new Error(`Cannot scope to the environment level because no environments currently exist.`);
                        }
                    }
                    if (given.environment && given.enterprise) { // if they gave us one, it's gotta exist
                        if (scopesAvail.find(e => e.name == given.enterprise && e.environments.find(v => v.name == given.environment))) {
                            target = given.environment;
                            enterprise = given.enterprise;
                        }
                    }
                    else if (given.environment) {
                        if (scopesAvail.length == 1) {
                            // If there's only one enterprise, and if their env is in it, we're good.
                            if (scopesAvail[0].environments.find(v => v.name == given.environment)) {
                                target = given.environment;
                                enterprise = scopesAvail[0].name;
                            }
                            else {
                                if (nonInteractive) {
                                    throw new Error(`Environment [${given.environment}] does not exist.`);
                                }
                            }
                        }
                        else if (scopesAvail.length > 1) {
                            // if the env is unique, then we can derive the enterprise
                            let found = [];
                            for (let eScope of scopesAvail) {
                                let envFound = eScope.environments.find(env => env.name == given.environment);
                                if (envFound)
                                    found.push({ ent: eScope.name, env: envFound.name });
                            }
                            if (found.length == 1) {
                                target = found[0].env;
                                enterprise = found[0].ent;
                            }
                            else if (found.length > 1) {
                                if (nonInteractive) {
                                    throw new Error(`Environment argument [${given.environment}] with unspecified enterprise value was ambiguous because there are more than one environments with that name.`);
                                }
                                // Here we should ask them to specify which one. of the ones that were found
                            }
                            else {
                                if (nonInteractive) {
                                    throw new Error(`Environment [${given.environment}] does not exist.`);
                                }
                            }
                        }
                    }
                    else {
                        // if they called for "the only", then there can be only one.
                        if (envs.length == 1) {
                            target = envs[0].env;
                            enterprise = envs[0].ent;
                        }
                        else if (nonInteractive) {
                            throw new Error(`Environment argument with unspecified value was ambiguous because there are more than one environments available.`);
                        }
                    }
                    break;
                case "cabinet":
                    let cabs = [];
                    scopesAvail.forEach(ent => {
                        ent.environments.forEach(env => {
                            env.cabinets.forEach(c => {
                                if (!cabs.find(exc => exc.name == c.name && exc.enterprise == ent.name))
                                    cabs.push({ name: c.name, enterprise: ent.name });
                            });
                        });
                    });
                    if (cabs.length == 0) {
                        if (nonInteractive) {
                            throw new Error(`Cannot scope to the cabinet level because no cabinets currently exist.`);
                        }
                    }
                    if (given.cabinet && given.enterprise) { // if they gave us one, it's gotta exist
                        if (cabs.find(c => c.enterprise == given.enterprise && c.name == given.cabinet)) {
                            target = given.cabinet;
                            enterprise = given.enterprise;
                        }
                        else {
                            if (nonInteractive) {
                                if (scopesAvail.find(s => s.name == given.enterprise)) {
                                    throw new Error(`No cabinet [${given.cabinet}] exists in enterprise [${given.enterprise}].`);
                                }
                                else {
                                    throw new Error(`Enterprise [${given.enterprise}] does not exist.`);
                                }
                            }
                        }
                    }
                    else if (given.cabinet) {
                        // if the cab is unique, then we can derive the enterprise
                        let found = cabs.filter(c => c.name == given.cabinet);
                        if (found.length == 1) {
                            target = found[0].name;
                            enterprise = found[0].enterprise;
                        }
                        else if (found.length > 1) {
                            if (nonInteractive) {
                                throw new Error(`Cabinet argument [${given.cabinet}] with unspecified enterprise value was ambiguous because there are more than one cabinets with that name.`);
                            }
                            // Here we should ask them to specify which one. of the ones that were found
                        }
                        else if (found.length == 0) {
                            if (nonInteractive) {
                                throw new Error(`Cabinet [${given.cabinet}] does not exist.`);
                            }
                        }
                    }
                    else {
                        // if they called for "the only", then there can be only one.
                        if (cabs.length == 1) {
                            target = cabs[0].name; // TODO: Consider adding enteprise as prefix
                            enterprise = cabs[0].enterprise;
                        }
                        else {
                            if (nonInteractive) {
                                throw new Error(`Cabinet argument with unspecified value was ambiguous because there are more than one cabinets available.`);
                            }
                        }
                    }
                    break;
            }
            if (target) {
                scope = { level: level, target: target };
                if (enterprise)
                    scope.enterprise = enterprise;
            }
            if (!scope) {
                if (nonInteractive) {
                    throw new Error(`Invalid scope.`); // logic above should already have caught this scenario.  This is here as a double check.
                }
            }
        }
        return scope;
    }
}
exports.default = HostMap;
HostMap.description = 'Declares a strategy for mapping assets to ingress routes for a domain name.';
HostMap.aliases = ['domain:map'];
HostMap.flags = {
    help: core_1.Flags.help({ char: 'h' }),
    naming: core_1.Flags.string({ char: 'n', required: false, description: "The host naming strategy to apply." }),
    enterprise: core_1.Flags.string({ char: 'e', exclusive: ["environment"], description: 'The name of the enterprise on which to apply this naming strategy. NOTE: You can leave this blank if there is only one.' }),
    environment: core_1.Flags.string({ char: 'v', /* dependsOn: ["enterprise"], */ exclusive: ["cabinet"], description: 'The name of the environment on which to apply this naming strategy. Must be qualified by also setting enterprise.  NOTE: You can leave this blank if there is only one.' }),
    cabinet: core_1.Flags.string({ char: 'c', /* dependsOn: ["enterprise"], */ exclusive: ["environment"], description: 'The name of the cabinet on which to apply this naming strategy.  Must be qualified by also setting enterprise.  NOTE: You can leave this blank if there is only one. If cabinet names are not unique, use the --enterprise/-e argument to fully qualify the cabinet name.' }),
    private: core_1.Flags.boolean({ description: "Declares that this domain mapping is for internal use and not available from the internet." }),
    noninteractive: core_1.Flags.boolean({ char: 'x', default: false, description: "Throws errors if user interaction is required." })
};
HostMap.args = {
    domain: core_1.Args.string({ required: false, description: 'The domain name to route (eg: "my-company.com")' })
};
//# sourceMappingURL=map.js.map