"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.deploy = exports.requestCertificates = void 0;
const package_1 = require("./package");
const read_config_1 = require("../config/read-config");
const generate_certificates_1 = require("../deploy/utils/generate-certificates");
const spinnies_1 = __importDefault(require("spinnies"));
const chalk_1 = __importDefault(require("chalk"));
const inquirer_1 = require("inquirer");
const aws_account_1 = require("../utils/aws-account");
const yaml_1 = require("../utils/yaml");
const rxjs_1 = require("rxjs");
const create_cname_records_1 = require("../deploy/utils/create-cname-records");
var IDeployEventType;
(function (IDeployEventType) {
    IDeployEventType[IDeployEventType["BACKING_UP_YAML"] = 0] = "BACKING_UP_YAML";
    IDeployEventType[IDeployEventType["REFORMATTING_YAML"] = 1] = "REFORMATTING_YAML";
    IDeployEventType[IDeployEventType["CREATING_CUSTOM_DOMAIN"] = 2] = "CREATING_CUSTOM_DOMAIN";
    IDeployEventType[IDeployEventType["CREATED_CUSTOM_DOMAIN"] = 3] = "CREATED_CUSTOM_DOMAIN";
    IDeployEventType[IDeployEventType["DEPLOYING"] = 4] = "DEPLOYING";
    IDeployEventType[IDeployEventType["DEPLOYED"] = 5] = "DEPLOYED";
    IDeployEventType[IDeployEventType["CREATING_RECORDS"] = 6] = "CREATING_RECORDS";
    IDeployEventType[IDeployEventType["CREATED_RECORDS"] = 7] = "CREATED_RECORDS";
    IDeployEventType[IDeployEventType["RESTORING_YAML"] = 8] = "RESTORING_YAML";
    IDeployEventType[IDeployEventType["RESTORED_YAML"] = 9] = "RESTORED_YAML";
    IDeployEventType[IDeployEventType["SUCCESS"] = 10] = "SUCCESS";
    IDeployEventType[IDeployEventType["FAILURE"] = 11] = "FAILURE";
})(IDeployEventType || (IDeployEventType = {}));
exports.requestCertificates = (certificateManager) => {
    return new Promise((resolve, reject) => {
        const spinnies = new spinnies_1.default({
            failColor: 'white',
            succeedColor: 'white',
            spinnerColor: 'cyan',
        });
        const onNext = (evt) => {
            const key = `${evt.domain}@${evt.region}`;
            switch (evt.type) {
                case generate_certificates_1.CertificateEventType.CREATING: {
                    spinnies.add(key, { text: `${chalk_1.default.cyan(`[${evt.region}]`)} Creating certificate for ${evt.domain}` });
                    break;
                }
                case generate_certificates_1.CertificateEventType.ACTIVATING: {
                    spinnies.update(key, {
                        text: `${chalk_1.default.cyan(`[${evt.region}]`)} Activating certificate for ${evt.domain} ${chalk_1.default.gray('Please be patient, this can take up to thirty minutes')}`,
                    });
                    break;
                }
                case generate_certificates_1.CertificateEventType.ACTIVATED: {
                    spinnies.add(key, {
                        text: `${chalk_1.default.cyan(`[${evt.region}]`)} Successfully created certificate for ${evt.domain}`,
                    });
                    break;
                }
            }
        };
        const onError = (evt) => __awaiter(void 0, void 0, void 0, function* () {
            const key = `${evt.domain}@${evt.region}`;
            spinnies.fail(key, { text: `${chalk_1.default.cyan(`[${evt.region}]`)} Error creating certificate for ${evt.domain}` });
            spinnies.stopAll();
            return reject(evt.error);
        });
        const onComplete = () => {
            return resolve();
        };
        certificateManager.doRequestCertificates().subscribe(onNext, onError, onComplete);
    });
};
exports.deploy = (cmd, logger, scheduler) => __awaiter(void 0, void 0, void 0, function* () {
    const log = logger.log('deploy');
    const config = new read_config_1.ConfigReader(logger);
    config.readConfig();
    if (!cmd.E) {
        console.error(chalk_1.default.red('You must provide a target stage to deploy services'));
        process.exit(1);
    }
    if (config.config.stages && !config.config.stages.includes(cmd.E)) {
        console.error(chalk_1.default.red('Target stage is not part of allowed stages.'));
        console.error(chalk_1.default.red('Allowed stages are:', config.config.stages));
        process.exit(1);
    }
    const currentIAM = yield aws_account_1.getAccountIAM().catch((err) => {
        console.error(chalk_1.default.red('You are not authenticated to AWS. Please check your keypair or AWS profile.'));
        console.error(chalk_1.default.red('Original error: ' + err));
        process.exit(1);
    });
    console.info(chalk_1.default.bold('\nDeployment information'));
    console.info('Stage:', cmd.E);
    console.info('Services:', cmd.S != null ? cmd.S : 'all');
    console.info('As:', currentIAM);
    if (cmd.prompt) {
        const answers = yield inquirer_1.prompt([
            {
                type: 'confirm',
                name: 'ok',
                message: 'Are you sure you want to execute this deployment',
            },
        ]);
        if (!answers.ok) {
            process.exit(0);
        }
    }
    console.log('\n');
    const { projectRoot, concurrency, graph, service } = yield package_1.beforePackage(cmd, scheduler, logger);
    config.validate(graph);
    if (cmd.package) {
        console.info('\nPackaging services\n');
        yield package_1.packageService(scheduler, concurrency, cmd.S ? service : graph).catch((e) => {
            console.error(chalk_1.default.red('Error packaging services'));
            console.error(e);
            process.exit(1);
        });
    }
    let needActions;
    let certificateManager;
    const services = cmd.S ? [service] : graph.getServices();
    log.info('Resolving certificates to generate');
    try {
        certificateManager = new generate_certificates_1.CertificateManager(logger, services, config);
        needActions = yield certificateManager.prepareCertificatesRequests(cmd.E);
    }
    catch (e) {
        console.error(chalk_1.default.red('Cannot determine certificate to request'));
        console.error(e);
        process.exit(1);
    }
    if (needActions) {
        try {
            console.info('\nGenerating certificates\n');
            yield exports.requestCertificates(certificateManager);
        }
        catch (e) {
            console.error(chalk_1.default.red('Error generating certificates'));
            console.error(chalk_1.default.red(e));
            process.exit(1);
        }
    }
    else {
        log.info('No certificate to generate');
    }
    const failures = new Map();
    const deployOne = (service, region) => {
        return new rxjs_1.Observable((obs) => {
            log.info('Deploying', service.getName());
            obs.next({ service, type: IDeployEventType.BACKING_UP_YAML });
            log.info(service.getName(), 'Back-up YAML');
            yaml_1.backupYaml([service]);
            obs.next({ service, type: IDeployEventType.REFORMATTING_YAML });
            const failed = (type, e, restore = true) => {
                log.error(service.getName(), 'Failure', service.getName());
                log.error(type);
                log.error(e);
                failures.set(service, { type, error: e });
                obs.next({ service, type: IDeployEventType.RESTORING_YAML });
                if (restore) {
                    log.info(service.getName(), 'Restoring YAML');
                    yaml_1.restoreYaml([service]);
                    log.info(service.getName(), 'YAML restored');
                }
                obs.next({ service, type: IDeployEventType.FAILURE, error: e });
            };
            yaml_1.reformatYaml(projectRoot, config, [service], region, cmd.E)
                .then(() => __awaiter(void 0, void 0, void 0, function* () {
                try {
                    const customDomain = config.getCustomDomain(service.getName(), cmd.E);
                    log.info(service.getName(), 'Custom domain', customDomain);
                    if (customDomain) {
                        obs.next({ service, type: IDeployEventType.CREATING_CUSTOM_DOMAIN });
                        try {
                            log.info(service.getName(), 'Creating Custom domain', customDomain);
                            yield service.createCustomDomain(region, cmd.E);
                        }
                        catch (e) {
                            failed('creatingDomain', e);
                            return obs.complete();
                        }
                        obs.next({ service, type: IDeployEventType.CREATED_CUSTOM_DOMAIN });
                    }
                    obs.next({ service, type: IDeployEventType.DEPLOYING });
                    try {
                        log.info(service.getName(), 'running npm run deploy');
                        yield service.deploy(region, cmd.E);
                    }
                    catch (e) {
                        failed('deploying', e);
                        return obs.complete();
                    }
                    obs.next({ service, type: IDeployEventType.DEPLOYED });
                    obs.next({ service, type: IDeployEventType.RESTORING_YAML });
                    log.info(service.getName(), 'restoring YAML');
                    yaml_1.restoreYaml([service]);
                    log.info(service.getName(), 'restored YAML');
                    obs.next({ service, type: IDeployEventType.RESTORED_YAML });
                    obs.next({ service, type: IDeployEventType.CREATING_RECORDS });
                    try {
                        obs.next({ service, type: IDeployEventType.CREATED_RECORDS });
                        log.info(service.getName(), 'Creating records');
                        const recordsManager = new create_cname_records_1.RecordsManager(logger);
                        yield recordsManager.createRecords(config, cmd.E, [service]);
                        log.info(service.getName(), 'Created records');
                    }
                    catch (e) {
                        failed('creatingRecords', e, false);
                        return obs.complete();
                    }
                    obs.next({ service, type: IDeployEventType.SUCCESS });
                    return obs.complete();
                }
                catch (e) {
                    failed('other', e);
                    return obs.complete();
                }
            }))
                .catch((e) => {
                log.error(service.getName(), 'Error reformatting');
                failed('reformatting', e);
                return obs.complete();
            });
        });
    };
    console.info('\nDeploying services\n');
    let deployments;
    if (cmd.S) {
        log.info('Deploying one service', service.getName());
        const regions = config.getRegions(service.getName(), cmd.E);
        deployments = rxjs_1.concat(...regions.map((region) => deployOne(service, region)));
    }
    else {
        log.info('Deploying all services');
        const schedule = config.scheduleDeployments(cmd.E);
        const steps = [];
        for (const step of schedule) {
            const toDeploy = new Map();
            const regions = step.keys();
            for (const region of regions) {
                const stepServices = step.get(region);
                for (const serviceName of stepServices) {
                    const stepService = graph.getServices().find((s) => s.getName() === serviceName);
                    if (!stepService) {
                        throw Error('Unresolved service ' + serviceName);
                    }
                    if (toDeploy.has(stepService)) {
                        toDeploy.get(stepService).add(region);
                    }
                    else {
                        toDeploy.set(stepService, new Set([region]));
                    }
                }
            }
            const stepDeployments = [];
            for (const [stepService, regions] of toDeploy.entries()) {
                stepDeployments.push(rxjs_1.concat(...Array.from(regions).map((region) => deployOne(stepService, region))));
            }
            steps.push(rxjs_1.merge(...stepDeployments, concurrency));
        }
        deployments = rxjs_1.concat(...steps);
    }
    const spinnies = new spinnies_1.default({
        failColor: 'white',
        succeedColor: 'white',
        spinnerColor: 'cyan',
    });
    deployments.subscribe((evt) => {
        switch (evt.type) {
            case IDeployEventType.BACKING_UP_YAML: {
                spinnies.add(evt.service.getName(), {
                    text: `Deploying ${evt.service.getName()} ${chalk_1.default.cyan('Backing-up YAML')}`,
                });
                break;
            }
            case IDeployEventType.REFORMATTING_YAML: {
                spinnies.update(evt.service.getName(), {
                    text: `Deploying ${evt.service.getName()} ${chalk_1.default.cyan('Reformatting YAML')}`,
                });
                break;
            }
            case IDeployEventType.CREATING_CUSTOM_DOMAIN: {
                spinnies.update(evt.service.getName(), {
                    text: `Deploying ${evt.service.getName()} ${chalk_1.default.cyan('Creating custom domain')}`,
                });
                break;
            }
            case IDeployEventType.DEPLOYING: {
                spinnies.update(evt.service.getName(), {
                    text: `Deploying ${evt.service.getName()} ${chalk_1.default.cyan('Updating CloudFormation stack')}`,
                });
                break;
            }
            case IDeployEventType.RESTORING_YAML: {
                spinnies.update(evt.service.getName(), {
                    text: `Deploying ${evt.service.getName()} ${chalk_1.default.cyan('Restoring YAML')}`,
                });
                break;
            }
            case IDeployEventType.CREATING_RECORDS: {
                spinnies.update(evt.service.getName(), {
                    text: `Deploying ${evt.service.getName()} ${chalk_1.default.cyan('Creating latency-based DNS records')}`,
                });
                break;
            }
            case IDeployEventType.SUCCESS: {
                spinnies.succeed(evt.service.getName(), { text: `Successfully deployed ${evt.service.getName()}` });
                break;
            }
            case IDeployEventType.FAILURE: {
                spinnies.fail(evt.service.getName(), { text: `Failed to deploy ${evt.service.getName()}` });
                break;
            }
        }
    }, (err) => {
        console.error(chalk_1.default.red('Error deploying services'));
        console.error(err);
        process.exit(1);
    }, () => {
        log.info('Deployment process completed', failures.values());
        if (failures.size) {
            const getMessage = (type) => {
                switch (type) {
                    case 'reformatting':
                        return 'Error reformatting YAML';
                    case 'creatingDomain':
                        return 'Error creating custom domain';
                    case 'deploying':
                        return 'Error deploying CloudFormation stack';
                    case 'creatingRecords':
                        return 'Error creating latency-based DNS records';
                    case 'other':
                        return 'Unknown error';
                }
            };
            for (const [service, failure] of failures.entries()) {
                console.info(`\n${chalk_1.default.bold(service.getName())}`);
                console.error(chalk_1.default.red(getMessage(failure.type)));
                if (failure.error) {
                    console.error(failure.error);
                }
                if (failure.type === 'creatingDomain') {
                    console.error(service.logs.createDomain.join(''));
                }
                else if (failure.type === 'deploying') {
                    console.error(service.logs.deploy.join(''));
                }
            }
            process.exit(1);
        }
        console.info(`Successfully deployed to ${cmd.E} :rocket:`);
        process.exit(0);
    });
});
//# sourceMappingURL=deploy.js.map