"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const core_1 = require("@oclif/core");
const _ = require("lodash");
const contracts_1 = require("../../../apis/codiac-api/contracts");
const contracts_2 = require("../../../apis/codiac-relay/contracts");
const cluster_def_1 = require("@codiac.io/codiac-domain/cluster-def");
const security_account_1 = require("@codiac.io/codiac-domain/security-account");
const codiac_ops_common_1 = require("@codiac.io/codiac-ops-common");
const command_base_exec_tenant_1 = require("../../../command-base-exec-tenant");
const cloud_provider_contextualizer_1 = require("../../../uilogic/cloud-provider-contextualizer");
const prettify_1 = require("../../../prettify");
class NocClusterDefine extends command_base_exec_tenant_1.CommandBaseExecTenant {
    // ---------------------------------------------------------------------------
    // Lazy-loaded dependencies
    // ---------------------------------------------------------------------------
    get uiCloudPvd() {
        if (this._uiCloudPvd === undefined)
            this._uiCloudPvd = this.ioc.get(cloud_provider_contextualizer_1.CloudProviderContextualizer);
        return this._uiCloudPvd;
    }
    // ---------------------------------------------------------------------------
    // Bootstrap
    // ---------------------------------------------------------------------------
    bootstrap() {
        super.bootstrap();
        cloud_provider_contextualizer_1.CloudProviderContextualizer.bootstrap(this.ioc);
    }
    // ---------------------------------------------------------------------------
    // Lifecycle methods
    // ---------------------------------------------------------------------------
    captureArgs() {
        var _a;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const { args, flags } = yield this.parse(NocClusterDefine);
            const partial = {
                name: args.name,
                provider: this.stringToEnum(contracts_2.CloudProviderEnum, flags.provider),
                providerSubscription: flags.providerSubscription,
                providerGroup: flags.resourceGroup,
                locationName: flags.location,
                nodeSpec: flags.nodeSpec,
                startingNodeCount: flags.nodeQty,
                k8sVersion: flags.k8sVersion,
                nodesMin: flags.nodesMin,
                nodesMax: flags.nodesMax,
                subnet: flags.subnet,
                networkPlugin: flags.networkPlugin,
            };
            if (args.name) {
                partial.identitySpec = {
                    type: cluster_def_1.ClusterDefConstants.DEFAULT_IDENTITY_TYPE,
                    name: cluster_def_1.ClusterDefConstants.buildDefaultIdentityName(args.name),
                };
            }
            if (flags.identityUserId && flags.identityUserName) {
                partial.identityInstance = new security_account_1.SecurityAccount(flags.identityUserId, flags.identityUserName, flags.identityUserName, flags.identityUserPassword);
            }
            const takeDefaults = (_a = flags['take-defaults']) !== null && _a !== void 0 ? _a : false;
            return [partial, takeDefaults];
        });
    }
    buildCandidate_silently(partial, _rootMap) {
        var _a;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!partial.name)
                throw new Error('Cluster name must be provided.');
            if (!partial.provider)
                throw new Error('--provider must be provided.');
            if (!partial.providerSubscription)
                throw new Error('--providerSubscription must be provided.');
            if (!partial.locationName)
                throw new Error('--location must be provided.');
            if (!partial.nodeSpec)
                throw new Error('--nodeSpec must be provided.');
            if (!partial.startingNodeCount)
                throw new Error('--nodeQty must be provided.');
            if (!partial.providerGroup && partial.provider === contracts_2.CloudProviderEnum.azure)
                throw new Error('--resourceGroup must be provided.');
            if (!partial.k8sVersion)
                throw new Error('--k8sVersion must be provided.');
            partial.privateOwner = this.ioc.get(codiac_ops_common_1.UserNameTenantCode).tenantCode;
            partial.identitySpec = (_a = partial.identitySpec) !== null && _a !== void 0 ? _a : { type: contracts_1.SecurityAccountTypeEnum.servicePrincipal, name: '' };
            return partial;
        });
    }
    buildCandidate_interactively(partial, takeDefaults, _rootMap) {
        var _a, _b;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            let candidate = Object.assign({}, partial);
            // Pre-populate from existing record if the cluster is already defined
            if (candidate.name) {
                const existing = yield this.tryLoadExistingDef(candidate.name);
                if (existing) {
                    this._ui.userMessage(this._styler.dim(`Loaded existing definition for [${candidate.name}]. Edit to update.`));
                    candidate = _.defaultsDeep(candidate, existing);
                }
            }
            let confirmed = false;
            do {
                this._ui.userMessage(this._styler.bold('Define Cluster:'));
                const allPvds = this.cspUiFactory.getAllClusterProviders(this.ioc);
                candidate.provider = yield this.uiCloudPvd.promptForCloudProvider(allPvds, candidate.provider);
                const cspUser = yield this.uiCloudPvd.promptForCloudProviderCreds(this.ioc, candidate.provider, undefined, undefined, undefined, candidate.providerSubscription);
                if (candidate.providerSubscription == undefined && cspUser.subscriptionId) {
                    candidate.providerSubscription = cspUser.subscriptionId;
                }
                const regCfg = this.cspUiFactory.create(candidate.provider, this.ioc);
                const def = yield regCfg.defineCluster(candidate, undefined, cspUser.cspTenantId);
                candidate = def.spec;
                if (typeof candidate.startingNodeCount === 'string') {
                    candidate.startingNodeCount = Number.parseInt(candidate.startingNodeCount);
                }
                console.clear();
                this._ui.userMessage((0, prettify_1.prettify)(candidate, [(_a = candidate.identityInstance) === null || _a === void 0 ? void 0 : _a.password]));
                this._ui.userMessage('Codiac will save this cluster definition (no infrastructure will be created or modified).');
                confirmed = yield this._ui.promptConfirm('confirmDefine', `Continue? (hit 'N' to edit your input)`, false);
            } while (!confirmed);
            candidate.privateOwner = this.ioc.get(codiac_ops_common_1.UserNameTenantCode).tenantCode;
            candidate.identitySpec = (_b = candidate.identitySpec) !== null && _b !== void 0 ? _b : { type: contracts_1.SecurityAccountTypeEnum.servicePrincipal, name: '' };
            return candidate;
        });
    }
    buildCommandString(candidate, cmd) {
        cmd.push(candidate.name);
        cmd.push('--provider', candidate.provider);
        cmd.push('--providerSubscription', candidate.providerSubscription);
        cmd.push('--resourceGroup', candidate.providerGroup);
        cmd.push('--location', candidate.locationName);
        cmd.push('--nodeSpec', candidate.nodeSpec);
        cmd.push('--nodeQty', candidate.startingNodeCount.toString());
        if (candidate.nodesMin != undefined)
            cmd.push('--nodesMin', candidate.nodesMin.toString());
        if (candidate.nodesMax != undefined)
            cmd.push('--nodesMax', candidate.nodesMax.toString());
        if (candidate.k8sVersion)
            cmd.push('--k8sVersion', candidate.k8sVersion);
        if (candidate.networkPlugin)
            cmd.push('--networkPlugin', candidate.networkPlugin);
        if (candidate.subnet && !_.isEmpty(candidate.subnet)) {
            (typeof candidate.subnet === 'string' ? [candidate.subnet] : candidate.subnet)
                .forEach(s => cmd.push('--subnet', s));
        }
        if (candidate.identityInstance) {
            cmd.push('--identityUserName', candidate.identityInstance.name);
            cmd.push('--identityUserId', candidate.identityInstance.id);
            if (candidate.identityInstance.password)
                cmd.push('--identityUserPassword', candidate.identityInstance.password);
        }
        cmd.push('--silent');
        return cmd;
    }
    executeTask(candidate) {
        var _a, _b, _c;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const result = yield this.genRelayClient.Call.clusterDefine({ spec: candidate });
            if (result.error) {
                const msg = `Error defining cluster [${candidate.name}]: ${(_a = result.message) !== null && _a !== void 0 ? _a : ''} ${(_b = result.error.message) !== null && _b !== void 0 ? _b : ''}`.trimEnd();
                this.logger.error(msg, result.error);
                this._ui.userMessage(`${this._styler.colorCritical('ERROR!')} ${msg}`);
            }
            else if (!result.success) {
                const msg = `Cluster define failed for [${candidate.name}]: ${(_c = result.message) !== null && _c !== void 0 ? _c : ''}`.trimEnd();
                this.logger.notice(msg, result);
                this._ui.userMessage(`${this._styler.colorCritical('FAILED!')} ${msg}`);
            }
            else {
                this._ui.userMessage(`${this._styler.colorGood('SUCCESS!')} Cluster [${this._styler.hilite(candidate.name)}] defined. ` +
                    `Run \`cod cluster restore ${candidate.name}\` to provision and bootstrap it.`);
            }
        });
    }
    // ---------------------------------------------------------------------------
    // Helpers
    // ---------------------------------------------------------------------------
    /** Attempts to load an existing ClusterDef by name. Returns undefined if not found. */
    tryLoadExistingDef(clusterName) {
        var _a;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                const found = yield this._codiacApi.ClusterDef.tryReadMany({
                    name: { $eq: clusterName },
                    privateOwner: this._userCntx.currentTenantCode,
                });
                return (_a = found.output) === null || _a === void 0 ? void 0 : _a[0];
            }
            catch (_b) {
                return undefined;
            }
        });
    }
    stringToEnum(enumObj, str) {
        if (str === undefined)
            return undefined;
        return Object.values(enumObj).includes(str) ? str : undefined;
    }
}
exports.default = NocClusterDefine;
NocClusterDefine.description = 'Creates or updates a cluster metadata record (ClusterDef) without provisioning any physical infrastructure. ' +
    'Use this to register a cluster definition before running `cod noc cluster restore`, or to update metadata ' +
    'on a cluster that has no live instance. If the cluster already has a running instance, destroy it first ' +
    '(`cod cluster destroy <name>`), then redefine.';
NocClusterDefine.aliases = ['cluster:define'];
NocClusterDefine.examples = [
    {
        description: 'Define a cluster interactively.',
        command: '<%= config.bin %> <%= command.id %>',
    },
    {
        description: 'Load and edit an existing cluster definition interactively by name.',
        command: '<%= config.bin %> <%= command.id %> my-cluster',
    },
    {
        description: 'Define an Azure cluster non-interactively.',
        command: '<%= config.bin %> <%= command.id %> my-cluster --provider azure --providerSubscription <sub-id> --resourceGroup my-rg --location eastus --nodeSpec Standard_D4s_v3 --nodeQty 3 --k8sVersion 1.29.0 --silent',
    },
    {
        description: 'Renders the equivalent silent command and then executes.',
        command: '<%= config.bin %> <%= command.id %> my-cluster --echo',
    },
    {
        description: 'Renders the equivalent silent command without executing.',
        command: '<%= config.bin %> <%= command.id %> my-cluster --to-script',
    },
];
NocClusterDefine.flags = {
    help: core_1.Flags.help({ char: 'h' }),
    provider: core_1.Flags.string({
        options: [contracts_2.CloudProviderEnum.azure, contracts_2.CloudProviderEnum.aws, contracts_2.CloudProviderEnum.localMachine, contracts_2.CloudProviderEnum.other],
        description: 'Cloud service provider on which the cluster resides or will reside.',
    }),
    providerSubscription: core_1.Flags.string({
        char: 's',
        description: 'The cloud provider subscription Id (Azure) or account Id (AWS) for the cluster.',
    }),
    resourceGroup: core_1.Flags.string({
        char: 'g',
        description: '(Azure only, required) The resource group associated with the cluster.',
    }),
    location: core_1.Flags.string({
        char: 'l',
        description: 'The short name of the regional data center where the cluster resides.',
    }),
    nodeSpec: core_1.Flags.string({
        char: 'n',
        description: 'The VM size used for cluster nodes.',
    }),
    nodeQty: core_1.Flags.integer({
        char: 'q',
        default: 2,
        description: 'Starting node count recorded in the cluster definition.',
    }),
    k8sVersion: core_1.Flags.string({
        description: 'The version of Kubernetes the cluster runs.',
    }),
    nodesMin: core_1.Flags.integer({
        description: 'Minimum number of nodes for autoscaling.',
    }),
    nodesMax: core_1.Flags.integer({
        description: 'Maximum number of nodes for autoscaling.',
    }),
    subnet: core_1.Flags.string({
        char: 'k',
        multiple: true,
        description: '(Optional) Resource Id of the subnet in which the cluster was or will be created.',
    }),
    networkPlugin: core_1.Flags.string({
        description: '(Optional) Network plugin for the cluster.',
        options: ['kubenet', 'azure'],
    }),
    identityUserName: core_1.Flags.string({
        char: 'u',
        description: '(Optional) Name of the Service Principal or Managed Identity the cluster executes as.',
    }),
    identityUserId: core_1.Flags.string({
        description: '(Optional) Id of the Service Principal or Managed Identity the cluster executes as.',
    }),
    identityUserPassword: core_1.Flags.string({
        char: 'c',
        hidden: true,
        description: '(Optional) Password of the Service Principal or Managed Identity the cluster executes as.',
    }),
    silent: core_1.Flags.boolean({
        exclusive: ['echo', 'to-script'],
        description: 'Executes without any user interaction; fails on missing or invalid arguments.',
        relationships: [
            { type: 'all', flags: ['k8sVersion', 'provider', 'providerSubscription', 'location', 'nodeSpec', 'nodeQty'] },
        ],
    }),
    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.',
    }),
    'take-defaults': core_1.Flags.boolean({
        exclusive: ['silent'],
        description: 'Prevents prompting for confirmation on parameters that were passed in or automatically set to default values.',
    }),
};
NocClusterDefine.args = {
    name: core_1.Args.string({ description: 'Name of the cluster to define. If a definition already exists with this name, it is loaded and opened for editing.' }),
};
//# sourceMappingURL=define.js.map