"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_exec_tenant_1 = require("../../command-base-exec-tenant");
const delete_1 = require("./delete");
const type_safety_utils_1 = require("../../uilogic/type-safety-utils");
class HostMap extends command_base_exec_tenant_1.CommandBaseExecTenant {
    captureArgs() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const { flags } = yield this.parse(HostMap);
            return [{}, false];
        });
    }
    buildCandidate_silently(_partial, rootMap) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const { args, flags } = yield this.parse(HostMap);
            if (!args.domain)
                throw new Error(`Domain argument is required in silent mode.`);
            const scopesAvail = yield this.getCabinetScopes(rootMap);
            const scope = this.validateGivenTarget(flags, scopesAvail, true);
            if (!scope)
                throw new Error(`Invalid or ambiguous scope. Provide --enterprise, --environment, or --cabinet.`);
            const naming = this.validateGivenNaming(flags.naming, true);
            if (!naming)
                throw new Error(`Invalid host naming strategy [${flags.naming}].`);
            const isPrivate = type_safety_utils_1.TypeSafetyUtils.toBoolean(flags.private);
            const thisMachine = this.isLocalMachine(scope, rootMap);
            return { domain: args.domain.trim(), scope, naming, isPrivate, thisMachine };
        });
    }
    buildCandidate_interactively(_partial, _takeDefaults, rootMap) {
        var _a;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const { args, flags } = yield this.parse(HostMap);
            const scopesAvail = yield this.getCabinetScopes(rootMap);
            const domain = ((_a = args.domain) !== null && _a !== void 0 ? _a : (yield this._ui.prompt("domain", "Domain: "))).trim();
            // Get the target scope
            let scope = this.validateGivenTarget(flags, scopesAvail, false);
            if (!scope) {
                const level = yield this._ui.promptSelect("scope", "Scope", ["enterprise", "environment", "cabinet"]);
                if (!level) {
                    this._ui.userMessage("Operation cancelled. No work was done.");
                    return undefined;
                }
                const targets = this.getAvailableTargets(level, scopesAvail);
                if (targets.length === 0) {
                    this._ui.userMessage(`No ${level}s were found. Operation cancelled.`);
                    return undefined;
                }
                const selected = yield this._ui.promptSelect("target", level, targets);
                if (!selected) {
                    this._ui.userMessage("Operation cancelled. No work was done.");
                    return undefined;
                }
                scope = this.parseSelectedTarget(level, selected);
            }
            // Get the host naming strategy
            let naming = this.validateGivenNaming(flags.naming, false);
            if (!naming) {
                const strategies = Object.values(host_name_strategy_enum_1.HostNameStrategyEnum).sort().map(x => ({
                    value: x,
                    name: x.replace(/_dot_/g, ".").replace(/_slash_/g, "/")
                }));
                const selected = yield this._ui.promptSelect("naming", "Host Naming Strategy", strategies);
                if (!selected) {
                    this._ui.userMessage("Operation cancelled. No work was done.");
                    return undefined;
                }
                naming = selected;
            }
            // External/Internal
            let isPrivate = type_safety_utils_1.TypeSafetyUtils.toBoolean(flags.private);
            const keep = yield this._ui.promptConfirm("isPrivateKeep", "Usage: ".concat(this._styler.hilite(isPrivate ? "private" : "external")), true);
            if (!keep) {
                const choices = [
                    { name: "External (Public DNS)", value: "false" },
                    { name: "Private (not available on the internet)", value: "true" }
                ];
                const selected = yield this._ui.promptSelect("isPrivate", "Usage:", choices, false, true);
                isPrivate = type_safety_utils_1.TypeSafetyUtils.toBoolean(selected);
            }
            const thisMachine = this.isLocalMachine(scope, rootMap);
            const 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));
            const confirm = yield this._ui.promptConfirm("confirmGo", "Continue? ", false);
            if (!confirm) {
                this._ui.userMessage("Cancelled. No work was done.");
                return undefined;
            }
            return { domain, scope, naming, isPrivate, thisMachine };
        });
    }
    buildCommandString(candidate, cmd) {
        cmd.push(candidate.domain);
        if (candidate.scope.level === "enterprise" && candidate.scope.target)
            cmd.push("-e", candidate.scope.target);
        if (candidate.scope.level === "environment" && candidate.scope.target)
            cmd.push("-v", candidate.scope.target);
        if (candidate.scope.level === "cabinet" && candidate.scope.target)
            cmd.push("-c", candidate.scope.target);
        if (candidate.scope.enterprise)
            cmd.push("-e", candidate.scope.enterprise);
        cmd.push("-n", candidate.naming);
        if (candidate.isPrivate)
            cmd.push("--private");
        cmd.push("--silent");
        return cmd;
    }
    executeTask(candidate) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            this._ui.startSpinner("Applying domain mapping");
            const result = yield this.genRelayClient.Call.executeDomainUpsert({
                domain: candidate.domain,
                namingStrategy: candidate.naming,
                scope: candidate.scope.level,
                target: candidate.scope.target,
                enterprise: candidate.scope.enterprise,
                isPrivate: candidate.isPrivate,
                thisMachine: candidate.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}`);
            }
        });
    }
    isLocalMachine(scope, rootMap) {
        let isLocal;
        if (scope && scope.level !== contracts_1.ScopeLevel.enterprise) {
            const 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 (const env of ent.environments) {
                            const cab = env.cabinets.find(c => c.name === scope.target);
                            if (cab)
                                isLocal = cab.thisMachine;
                        }
                        break;
                }
            }
        }
        return isLocal;
    }
    getCabinetScopes(rootMap) {
        var _a;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                const scopes = [];
                for (const ent of rootMap.enterprises) {
                    const entScope = { id: undefined, name: ent.name, environments: [] };
                    for (const env of ent.environments) {
                        const envScope = {
                            name: env.name,
                            cabinets: env.cabinets.map(c => ({ name: c.name, cluster: c.cluster }))
                        };
                        entScope.environments.push(envScope);
                    }
                    scopes.push(entScope);
                }
                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* () {
            const candidate = {
                enterprise: scope.enterprise, domainName: domain, strategy: naming, scope: scope.level, target: scope.target, isPrivate
            };
            return delete_1.HostNameUiLogic.displayNameFor(candidate, this._styler);
        });
    }
    validateGivenNaming(naming, nonInteractive = false) {
        const 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;
    }
    getAvailableTargets(level, scopesAvail) {
        let targets = [];
        switch (level) {
            case "enterprise":
                targets = scopesAvail.map(e => 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;
    }
    parseSelectedTarget(level, selected) {
        let parts;
        switch (level) {
            case "enterprise":
                return { level, target: selected };
            case "environment":
                parts = selected.split("/");
                return { level, target: parts[1], enterprise: parts[0] };
            case "cabinet":
                parts = selected.split("/");
                return { level, target: parts[2], enterprise: parts[0] };
        }
    }
    validateGivenTarget(given, scopesAvail, nonInteractive = false) {
        let scope;
        const level = (given.cabinet) ? "cabinet" : (given.environment) ? "environment" : (given.enterprise) ? "enterprise" : undefined;
        if (!level)
            return undefined;
        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.`);
                    break;
                }
                if (given.enterprise) {
                    target = scopesAvail.find(e => e.name === given.enterprise) ? given.enterprise : undefined;
                }
                else if (scopesAvail.length === 1) {
                    target = scopesAvail[0].name;
                }
                else if (nonInteractive) {
                    throw new Error(`Enterprise argument with unspecified value was ambiguous because there is more than one enterprise available.`);
                }
                break;
            case "environment": {
                const 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.`);
                    break;
                }
                if (given.environment && given.enterprise) {
                    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 (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 {
                        const found = scopesAvail.flatMap(e => e.environments.filter(v => v.name === given.environment).map(v => ({ ent: e.name, env: v.name })));
                        if (found.length === 1) {
                            target = found[0].env;
                            enterprise = found[0].ent;
                        }
                        else if (found.length > 1 && nonInteractive)
                            throw new Error(`Environment [${given.environment}] was ambiguous across multiple enterprises.`);
                        else if (found.length === 0 && nonInteractive)
                            throw new Error(`Environment [${given.environment}] does not exist.`);
                    }
                }
                else 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 is more than one environment available.`);
                }
                break;
            }
            case "cabinet": {
                const 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.`);
                    break;
                }
                if (given.cabinet && given.enterprise) {
                    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) {
                    const 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 && nonInteractive)
                        throw new Error(`Cabinet [${given.cabinet}] was ambiguous across multiple enterprises.`);
                    else if (found.length === 0 && nonInteractive)
                        throw new Error(`Cabinet [${given.cabinet}] does not exist.`);
                }
                else if (cabs.length === 1) {
                    target = cabs[0].name;
                    enterprise = cabs[0].enterprise;
                }
                else if (nonInteractive) {
                    throw new Error(`Cabinet argument with unspecified value was ambiguous because there is more than one cabinet available.`);
                }
                break;
            }
        }
        if (target) {
            scope = { level, target };
            if (enterprise)
                scope.enterprise = enterprise;
        }
        if (!scope && nonInteractive)
            throw new Error(`Invalid scope.`);
        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. Leave blank if there is only one.' }),
    environment: core_1.Flags.string({ char: 'v', exclusive: ["cabinet"], description: 'The name of the environment on which to apply this naming strategy. Must be qualified by also setting enterprise if there are more than one.' }),
    cabinet: core_1.Flags.string({ char: 'c', exclusive: ["environment"], description: 'The name of the cabinet on which to apply this naming strategy. Must be qualified by also setting enterprise if cabinet names are not unique.' }),
    private: core_1.Flags.boolean({ description: "Declares that this domain mapping is for internal use and not available from the internet." }),
    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.' }),
};
HostMap.args = {
    domain: core_1.Args.string({ required: false, description: 'The domain name to route (eg: "my-company.com").' })
};
HostMap.examples = [
    { description: "Map a domain interactively.", command: "<%= config.bin %> <%= command.id %> my-company.com" },
    { description: "Map an enterprise-level domain.", command: "<%= config.bin %> <%= command.id %> my-company.com -e myEnterprise -n subdomain_dot_cabinet --silent" },
    { description: "Renders the equivalent silent command without executing.", command: "<%= config.bin %> <%= command.id %> my-company.com -e myEnterprise --to-script" },
    { description: "Consume a mapping from a helm chart's own ingress spec: after mapping the domain here, reference it from any scoped setting via the ${hostmap(strategy, domain)} dynamic variable. See `cod config set --help` for setting-side examples.", command: "<%= config.bin %> <%= command.id %> mywebsite.com -e main -v primero-dev -n svc_dot_cab_dot_domain --silent" },
];
//# sourceMappingURL=map.js.map