"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const command_1 = require("@oclif/command");
const fs_1 = tslib_1.__importDefault(require("fs"));
const crypto_1 = tslib_1.__importDefault(require("crypto"));
const boxen_1 = tslib_1.__importDefault(require("boxen"));
const chalk_1 = tslib_1.__importDefault(require("chalk"));
const os_1 = tslib_1.__importDefault(require("os"));
const express_1 = tslib_1.__importDefault(require("express"));
const cors_1 = tslib_1.__importDefault(require("cors"));
const base_1 = tslib_1.__importDefault(require("../base"));
const fsPromises = fs_1.default.promises;
function isValidPort(port) {
    return port > 0 && port < 65535;
}
// generate a string of random alphanumeric characters to serve as admin key
function generateKey() {
    return crypto_1.default.randomBytes(5).toString('hex');
}
class AdminServer extends base_1.default {
    async run() {
        const { flags: runFlags } = this.parse(AdminServer);
        const output = runFlags.target || runFlags.out || '.';
        const isDir = (await fsPromises.lstat(output)).isDirectory();
        this.target = isDir ? `${output}/config.json` : output;
        // check if the config file exists, if none exists write default
        const defaultConfig = {
            assemblies: [],
            configuration: {},
            connections: [],
            defaultSession: {
                name: 'New Session',
            },
            tracks: [],
        };
        if (fs_1.default.existsSync(this.target)) {
            this.debug(`Found existing config file ${this.target}`);
        }
        else {
            this.debug(`Creating config file ${this.target}`);
            await this.writeJsonFile('./config.json', defaultConfig);
        }
        // start server with admin key in URL query string
        let port = 9090;
        if (runFlags.port) {
            if (!isValidPort(parseInt(runFlags.port, 10))) {
                this.error(`${runFlags.port} is not a valid port`);
            }
            else {
                port = parseInt(runFlags.port, 10);
            }
        }
        const app = express_1.default();
        app.use(express_1.default.static('.'));
        app.use(cors_1.default());
        // POST route to save config
        app.use(express_1.default.json());
        app.post('/updateConfig', async (req, res) => {
            this.debug('Req body: ', req.body);
            if (req.body.adminKey === adminKey) {
                this.debug('Admin key matches');
                try {
                    await this.writeJsonFile(this.target, req.body.config);
                    res.send('Config written to disk');
                }
                catch (_a) {
                    res.status(500).send('Could not write config file');
                }
            }
            else {
                res.status(403).send('Admin key does not match');
            }
        });
        app.post('/shutdown', async (req, res) => {
            this.debug('Req body: ', req.body);
            if (req.body.adminKey === adminKey) {
                this.debug('Admin key matches');
                res.send('Exiting');
                server.close();
            }
            else {
                res.status(403).send('Admin key does not match');
            }
        });
        const adminKey = generateKey();
        const server = app.listen(port);
        // Server message adapted from `serve`
        // https://github.com/vercel/serve/blob/f65ac293c20058f809769a4dbf4951acc21df6df/bin/serve.js
        const details = server.address();
        let localAddress = '';
        let networkAddress = '';
        if (typeof details === 'string') {
            localAddress = details;
        }
        else if (details && typeof details === 'object') {
            const address = details.address === '::' ? 'localhost' : details.address;
            const ip = getNetworkAddress();
            localAddress = `http://${address}:${details.port}?adminKey=${adminKey}`;
            if (ip) {
                networkAddress = `http://${ip}:${details.port}?adminKey=${adminKey}`;
            }
        }
        let message = chalk_1.default.green('Now serving JBrowse\nNavigate to the below URL to configure');
        if (localAddress) {
            const prefix = networkAddress ? '- ' : '';
            const space = networkAddress ? '            ' : '  ';
            message += `\n\n${chalk_1.default.bold(`${prefix}Local:`)}${space}${localAddress}`;
        }
        if (networkAddress) {
            message += `\n${chalk_1.default.bold('- On Your Network:')}  ${networkAddress}`;
        }
        this.log(boxen_1.default(message, { padding: 1, borderColor: 'blue', margin: 1 }));
        this.log(`If you are running yarn start you can launch http://localhost:3000?adminKey=${adminKey}&adminServer=http://localhost:${port}/updateConfig`);
    }
}
exports.default = AdminServer;
AdminServer.description = 'Start up a small admin server for JBrowse configuration';
AdminServer.examples = ['$ jbrowse admin-server', '$ jbrowse admin-server -p 8888'];
AdminServer.flags = {
    port: command_1.flags.string({
        char: 'p',
        description: 'Specifified port to start the server on;\nDefault is 9090.',
    }),
    target: command_1.flags.string({
        description: 'path to config file in JB2 installation directory to write out to.\nCreates ./config.json if nonexistent',
    }),
    out: command_1.flags.string({
        description: 'synonym for target',
    }),
    skipCheck: command_1.flags.boolean({
        description: "Don't check whether or not you are in a JBrowse directory",
    }),
    help: command_1.flags.help({ char: 'h' }),
};
function getNetworkAddress() {
    for (const network of Object.values(os_1.default.networkInterfaces())) {
        for (const networkInterface of network || []) {
            const { address, family, internal } = networkInterface;
            if (family === 'IPv4' && !internal) {
                return address;
            }
        }
    }
    return undefined;
}
