#!/usr/bin/env node

const process = require('process');
const path = require('path');
const fs = require('fs');
const fsPromises = require('fs').promises;
const childProcess = require('child_process');
const tar = require('tar');
const asar = require('asar');
const needle = require('needle');

const riotDesktopPackageJson = require('../package.json');

const PUB_KEY_URL = "https://packages.riot.im/element-release-key.asc";
const PACKAGE_URL_PREFIX = "https://github.com/vector-im/element-web/releases/download/";
const ASAR_PATH = 'webapp.asar';

const {setPackageVersion} = require('./set-version.js');

async function getLatestDevelopUrl(bkToken) {
    const buildsResult = await needle('get',
        "https://api.buildkite.com/v2/organizations/matrix-dot-org/pipelines/element-web/builds",
        {
            branch: 'develop',
            state: 'passed',
            per_page: 1,
        },
        {
            headers: {
                authorization: "Bearer " + bkToken,
            },
        },
    );
    const latestBuild = buildsResult.body[0];
    console.log("Latest build is " + latestBuild.number);
    let artifactUrl;
    for (const job of latestBuild.jobs) {
        // Strip any colon-form emoji from the build name
        if (job.name && job.name.replace(/:\w*:\s*/, '') === 'Package') {
            artifactUrl = job.artifacts_url;
            break;
        }
    }
    if (artifactUrl === undefined) {
        throw new Error("Couldn't find artifact URL - has the name of the package job changed?");
    }

    const artifactsResult = await needle('get', artifactUrl, {},
        {
            headers: {
                authorization: "Bearer " + bkToken,
            },
        },
    );
    let dlUrl;
    let dlFilename;
    for (const artifact of artifactsResult.body) {
        if (artifact.filename && /^element-.*\.tar.gz$/.test(artifact.filename)) {
            dlUrl = artifact.download_url;
            dlFilename = artifact.filename;
            break;
        }
    }
    if (dlUrl === undefined) {
        throw new Error("Couldn't find artifact download URL - has the artifact filename changed?");
    }
    console.log("Fetching artifact URL...");
    const dlResult = await needle('get', dlUrl, {},
        {
            headers: {
                authorization: "Bearer " + bkToken,
            },
            // This URL will give us a Location header, but will also give us
            // a JSON object with the direct URL. We'll take the URL and pass it
            // back, then we can easily support specifying a URL directly.
            follow_max: 0,
        },
    );
    return [dlFilename, dlResult.body.url];
}

async function downloadToFile(url, filename) {
    console.log("Downloading " + url + "...");

    try {
        await needle('get', url, null,
            {
                follow_max: 5,
                output: filename,
            },
        );
    } catch (e) {
        try {
            await fsPromises.unlink(filename);
        } catch (_) {}
        throw e;
    }
}

async function verifyFile(filename) {
    return new Promise((resolve, reject) => {
        childProcess.execFile('gpg', ['--verify', filename + '.asc', filename], (error) => {
            if (error) {
                reject(error);
            } else {
                resolve();
            }
        });
    });
}

async function main() {
    let verify = true;
    let importkey = false;
    let pkgDir = 'packages';
    let deployDir = 'deploys';
    let cfgDir;
    let targetVersion;
    let filename;
    let url;
    let setVersion = false;

    while (process.argv.length > 2) {
        switch (process.argv[2]) {
            case '--noverify':
                verify = false;
                break;
            case '--importkey':
                importkey = true;
                break;
            case '--packages':
                process.argv.shift();
                pkgDir = process.argv[2];
                break;
            case '--deploys':
                process.argv.shift();
                deployDir = process.argv[2];
                break;
            case '--cfgdir':
            case '-d':
                process.argv.shift();
                cfgDir = process.argv[2];
                break;
            default:
                targetVersion = process.argv[2];
        }
        process.argv.shift();
    }

    if (targetVersion === undefined) {
        targetVersion = 'v' + riotDesktopPackageJson.version;
        filename = 'element-' + targetVersion + '.tar.gz';
        url = PACKAGE_URL_PREFIX + targetVersion + '/' + filename;
    } else if (targetVersion === 'develop') {
        const buildKiteApiKey = process.env.BUILDKITE_API_KEY;
        if (buildKiteApiKey === undefined) {
            console.log("Set BUILDKITE_API_KEY to fetch latest develop version");
            console.log(
                "Sorry - Buildkite's API requires authentication to access builds, " +
                "even if those builds are accessible on the web with no auth.",
            );
            process.exit(1);
        }
        [filename, url] = await getLatestDevelopUrl(buildKiteApiKey);
        verify = false; // develop builds aren't signed
    } else {
        filename = 'element-' + targetVersion + '.tar.gz';
        url = PACKAGE_URL_PREFIX + targetVersion + '/' + filename;
        setVersion = true;
    }

    const haveGpg = await new Promise((resolve) => {
        childProcess.execFile('gpg', ['--version'], (error) => {
            resolve(!error);
        });
    });

    if (importkey) {
        if (!haveGpg) {
            console.log("Can't import key without working GPG binary: install GPG and try again");
            return 1;
        }

        await new Promise((resolve) => {
            const gpgProc = childProcess.execFile('gpg', ['--import'], (error) => {
                if (error) {
                    console.log("Failed to import key", error);
                } else {
                    console.log("Key imported!");
                }
                resolve(!error);
            });
            needle.get(PUB_KEY_URL).pipe(gpgProc.stdin);
        });
        return 0;
    }

    if (cfgDir === undefined) {
        console.log("No config directory set");
        console.log("Specify a config directory with --cfgdir or -d");
        console.log("To build with no config (and no auto-update), pass the empty string (-d '')");
        return 1;
    }

    if (verify && !haveGpg) {
        console.log("No working GPG binary: install GPG or pass --noverify to skip verification");
        return 1;
    }

    let haveDeploy = false;
    const expectedDeployDir = path.join(deployDir, path.basename(filename).replace(/\.tar\.gz/, ''));
    try {
        await fs.opendir(expectedDeployDir);
        console.log(expectedDeployDir + "already exists");
        haveDeploy = true;
    } catch (e) {
    }

    if (!haveDeploy) {
        const outPath = path.join(pkgDir, filename);
        try {
            await fsPromises.stat(outPath);
            console.log("Already have " + filename + ": not redownloading");
        } catch (e) {
            try {
                await downloadToFile(url, outPath);
            } catch (e) {
                console.log("Failed to download " + url, e);
                return 1;
            }
        }

        if (verify) {
            try {
                await fsPromises.stat(outPath+'.asc');
                console.log("Already have " + filename + ".asc: not redownloading");
            } catch (e) {
                try {
                    await downloadToFile(url + '.asc', outPath + '.asc');
                } catch (e) {
                    console.log("Failed to download " + url, e);
                    return 1;
                }
            }

            try {
                await verifyFile(outPath);
                console.log(outPath + " downloaded and verified");
            } catch (e) {
                console.log("Signature verification failed!", e);
                return 1;
            }
        } else {
            console.log(outPath + " downloaded but NOT verified");
        }

        await tar.x({
            file: outPath,
            cwd: deployDir,
        });
    }

    try {
        await fsPromises.stat(ASAR_PATH);
        console.log(ASAR_PATH + " already present: removing");
        await fsPromises.unlink(ASAR_PATH);
    } catch (e) {
    }

    if (cfgDir.length) {
        const configJsonSource = path.join(cfgDir, 'config.json');
        const configJsonDest = path.join(expectedDeployDir, 'config.json');
        console.log(configJsonSource + ' -> ' + configJsonDest);
        await fsPromises.copyFile(configJsonSource, configJsonDest);
    } else {
        console.log("Skipping config file");
    }

    console.log("Pack " + expectedDeployDir + " -> " + ASAR_PATH);
    await asar.createPackage(expectedDeployDir, ASAR_PATH);

    if (setVersion) {
        const semVer = targetVersion.slice(1);
        console.log("Updating version to " + semVer);
        await setPackageVersion(semVer);
    }

    console.log("Done!");
}

main().then((ret) => process.exit(ret)).catch(e => process.exit(1));
