"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.Service = void 0;
const _1 = require("./");
const fs_1 = require("fs");
const child_process_1 = require("child_process");
const logs_1 = require("../utils/logs");
const service_status_1 = require("./enums/service.status");
const rxjs_1 = require("rxjs");
const chalk_1 = __importDefault(require("chalk"));
const operators_1 = require("rxjs/operators");
const external_binaries_1 = require("../utils/external-binaries");
const ui_1 = require("../ui");
const chokidar_1 = require("chokidar");
const packagr_1 = require("../package/packagr");
class Service extends _1.LernaNode {
    constructor(scheduler, graph, node, nodes, elements) {
        super(scheduler, graph, node, nodes, elements);
        this.status = service_status_1.ServiceStatus.STOPPED;
        this._port = graph.getPort(node.name);
        this._logs = {
            offline: [],
            createDomain: [],
            deploy: [],
        };
    }
    getStatus() {
        return this.status;
    }
    get logs() {
        return this._logs;
    }
    get port() {
        return this._port;
    }
    stop() {
        return new rxjs_1.Observable((observer) => {
            this.getGraph()
                .logger.log('service')
                .debug('Requested to stop', this.name, 'which status is', this.status);
            switch (this.status) {
                case service_status_1.ServiceStatus.RUNNING:
                case service_status_1.ServiceStatus.STARTING:
                    this.process.kill();
                    break;
                case service_status_1.ServiceStatus.STOPPING:
                    this.getGraph()
                        .logger.log('service')
                        .warn('Requested to stop a service that already stopping', this.name);
                    break;
                case service_status_1.ServiceStatus.CRASHED:
                case service_status_1.ServiceStatus.STOPPED:
                    this.getGraph()
                        .logger.log('service')
                        .warn('Requested to stop a service that is not running', this.name);
                    observer.next(this);
                    return observer.complete();
            }
            this.process.on('close', (code) => {
                if (code === 0) {
                    this.getGraph()
                        .logger.log('service')
                        .info(`Service ${this.name} exited with code ${code}`);
                    this._updateStatus(service_status_1.ServiceStatus.STOPPED);
                }
                else {
                    this.getGraph()
                        .logger.log('service')
                        .error(`Service ${this.name} exited with code ${code}`);
                    this._updateStatus(service_status_1.ServiceStatus.CRASHED);
                }
                this.process = null;
                observer.next(this);
                return observer.complete();
            });
        });
    }
    start() {
        return new rxjs_1.Observable((observer) => {
            this.getGraph()
                .logger.log('service')
                .debug('Requested to start', this.name, 'which status is', this.status);
            switch (this.status) {
                case service_status_1.ServiceStatus.CRASHED:
                case service_status_1.ServiceStatus.STOPPED:
                    this._startProcess();
                    this._watchStarted().subscribe((next) => observer.next(next), (err) => observer.error(err), () => observer.complete());
                    break;
                case service_status_1.ServiceStatus.STOPPING:
                    this.getGraph()
                        .logger.log('service')
                        .warn('Service is already stopping', this.name);
                    this.stop()
                        .pipe(operators_1.tap(() => this._startProcess()), operators_1.concatMap(() => this._watchStarted()))
                        .subscribe((next) => observer.next(next), (err) => observer.error(err), () => observer.complete());
                    break;
                case service_status_1.ServiceStatus.STARTING:
                    this.getGraph()
                        .logger.log('service')
                        .warn('Service is already starting', this.name);
                    this._watchStarted().subscribe((next) => observer.next(next), (err) => observer.error(err), () => observer.complete());
                    break;
                case service_status_1.ServiceStatus.RUNNING:
                    this.getGraph()
                        .logger.log('service')
                        .warn('Service is already running', this.name);
                    observer.next(this);
                    return observer.complete();
            }
        });
    }
    _watchServerlessYaml() {
        this._slsYamlWatcher = chokidar_1.watch(`${this.location}/serverless.{yml,yaml}`);
        this._slsYamlWatcher.on('change', (path) => {
            this.getGraph()
                .logger.log('node')
                .info(`${chalk_1.default.bold(this.name)}: ${path} changed. Recompiling`);
            this._scheduler.restartOne(this);
        });
    }
    _unwatchServerlessYaml() {
        return __awaiter(this, void 0, void 0, function* () {
            if (this._slsYamlWatcher) {
                yield this._slsYamlWatcher.close();
            }
        });
    }
    _startProcess() {
        this._updateStatus(service_status_1.ServiceStatus.STARTING);
        logs_1.createLogFile(this.graph.getProjectRoot(), this.name, 'offline');
        this.getGraph()
            .logger.log('service')
            .info(`Starting ${this.name} on localhost:${this.port}`);
        this.getGraph()
            .logger.log('service')
            .debug('Location:', this.location);
        this.getGraph()
            .logger.log('service')
            .debug('Env:', process.env.ENV);
        this.logStream = fs_1.createWriteStream(logs_1.getLogsPath(this.graph.getProjectRoot(), this.name, 'offline'));
        this.process = child_process_1.spawn('npm', ['start', '--', '--port', this.port.toString()], {
            cwd: this.location,
            env: Object.assign(Object.assign({}, process.env), { FORCE_COLOR: '2' }),
        });
        this.process.stderr.on('data', (data) => {
            this.getGraph()
                .logger.log('service')
                .error(`${chalk_1.default.bold(this.name)}: ${data}`);
            this._handleLogs(data);
        });
    }
    _watchStarted() {
        return new rxjs_1.Observable((started) => {
            this.process.stdout.on('data', (data) => {
                this._handleLogs(data);
                if (data.includes('listening on')) {
                    this.getGraph()
                        .logger.log('service')
                        .info(`${chalk_1.default.bold.bgGreenBright('success')}: ${this.name} listening localhost:${this.port}`);
                    this._updateStatus(service_status_1.ServiceStatus.RUNNING);
                    started.next(this);
                    return started.complete();
                }
            });
            this.process.on('close', (code) => {
                if (code !== 0) {
                    this.getGraph()
                        .logger.log('service')
                        .error(`Service ${this.name} exited with code ${code}`);
                    this._updateStatus(service_status_1.ServiceStatus.CRASHED);
                    return started.error();
                }
            });
            this.process.on('error', (err) => {
                this.getGraph()
                    .logger.log('service')
                    .error(`Could not start service ${this.name}`, err);
                this._updateStatus(service_status_1.ServiceStatus.CRASHED);
                return started.error(err);
            });
        });
    }
    _handleLogs(data) {
        this.logStream.write(data);
        this._logs.offline.push(data.toString());
        this.getGraph().io.handleServiceLog(this.name, data.toString());
    }
    _updateStatus(status) {
        if (status === service_status_1.ServiceStatus.RUNNING) {
            this._watchServerlessYaml();
        }
        else {
            this._unwatchServerlessYaml().then(() => {
                this.getGraph()
                    .logger.log('service')
                    .debug(`${this.name}: Unwatched serverless.yml`);
            });
        }
        this.status = status;
        this._ipc.graphUpdated();
        this.getGraph().io.statusUpdated(this, this.status);
        ui_1.actions.updateServiceStatus(this);
    }
    isRunning() {
        return this.status === service_status_1.ServiceStatus.RUNNING;
    }
    package(level = 4) {
        return new rxjs_1.Observable((obs) => {
            const packagr = new packagr_1.Packager(this.graph, this, this.graph.logger);
            packagr
                .generateZip(this, level)
                .then((megabytes) => {
                obs.next({ service: this, megabytes });
                obs.complete();
            })
                .catch((err) => obs.error(err));
        });
    }
    deploy(region, stage) {
        return __awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => {
                logs_1.createLogFile(this.graph.getProjectRoot(), this.name, 'deploy');
                const writeStream = fs_1.createWriteStream(logs_1.getLogsPath(this.graph.getProjectRoot(), this.name, 'deploy'));
                const deployProcess = child_process_1.spawn('npm', ['run', 'deploy'], {
                    cwd: this.location,
                    env: Object.assign(Object.assign({}, process.env), { ENV: stage, FORCE_COLOR: '2', MILA_REGION: region, AWS_REGION: region }),
                    stdio: 'pipe',
                });
                deployProcess.stderr.on('data', (data) => {
                    writeStream.write(data);
                    this._logs.deploy.push(data);
                });
                deployProcess.stdout.on('data', (data) => {
                    writeStream.write(data);
                    this._logs.deploy.push(data);
                });
                deployProcess.on('close', (code) => {
                    writeStream.close();
                    if (code !== 0) {
                        return reject(code);
                    }
                    return resolve();
                });
                deployProcess.on('error', (err) => {
                    writeStream.close();
                    return reject(err);
                });
            });
        });
    }
    createCustomDomain(region, stage) {
        return __awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => {
                logs_1.createLogFile(this.graph.getProjectRoot(), this.name, 'createDomain');
                const writeStream = fs_1.createWriteStream(logs_1.getLogsPath(this.graph.getProjectRoot(), this.name, 'createDomain'));
                const createDomainProcess = child_process_1.spawn(external_binaries_1.getBinary('sls', this.graph.getProjectRoot(), this.getGraph().logger, this), ['create_domain'], {
                    cwd: this.location,
                    env: Object.assign(Object.assign({}, process.env), { ENV: stage, FORCE_COLOR: '2', AWS_REGION: region }),
                    stdio: 'pipe',
                });
                createDomainProcess.stderr.on('data', (data) => {
                    writeStream.write(data);
                    this._logs.createDomain.push(data);
                });
                createDomainProcess.stdout.on('data', (data) => {
                    writeStream.write(data);
                    this._logs.createDomain.push(data);
                });
                createDomainProcess.on('close', (code) => {
                    writeStream.close();
                    if (code !== 0) {
                        return reject(code);
                    }
                    return resolve();
                });
                createDomainProcess.on('error', (err) => {
                    writeStream.close();
                    return reject(err);
                });
            });
        });
    }
}
exports.Service = Service;
//# sourceMappingURL=service.js.map