/*
 * HTTP server to intercept benchmark data from Codspeed runner.
 *
 * Codspeed runner makes 2 API calls:
 * 1. Uploading metadata
 * 2. Uploading archive of CPU profile files
 *
 * Server starts on an available port, saves the files sent by Codspeed runner to a directory,
 * then shuts itself down.
 */

import {createWriteStream} from 'fs';
import fs from 'fs/promises';
import {join as pathJoin} from 'path';
import {pipeline} from 'stream/promises';
import express from 'express';
import {extract} from 'tar';

const DEFAULT_PORT = 3000,
    LISTEN_ATTEMPTS = 10;

// Create directory for saving assets
const rand = Math.round(Math.random() * 100000000000000).toString(16),
    dataDir = `/tmp/oxc_bench_data_${rand}`;
await fs.mkdir(dataDir);

let component = process.env.COMPONENT;
if (process.env.FIXTURE) component += process.env.FIXTURE;

const app = express();

const wrapHandler = fn => (req, res, next) => { fn(req, res).catch(next); };
const getFilePath = filename => pathJoin(dataDir, `${component}_${filename}`);

app.post('/upload', wrapHandler(async (req, res) => {
    const stream = createWriteStream(getFilePath('metadata.json'));
    await pipeline(req, stream);

    res.json({
        status: 'success',
        uploadUrl: `http://localhost:${port}/upload_archive`,
        runId: 'dummy_value',
    });
}));

app.put('/upload_archive', wrapHandler(async (req, res) => {
    // Stream uploaded tarball to file
    const path = getFilePath('archive.tar.gz');
    const stream = createWriteStream(path);
    await pipeline(req, stream);

    // Untar contents + delete tarball
    await extract({file: path, cwd: dataDir});
    await fs.rm(path);

    // Rename `.out` files + delete `.log` files
    const filenames = await fs.readdir(dataDir);
    for (const filename of filenames) {
        if (filename.endsWith('.log')) {
            await fs.rm(pathJoin(dataDir, filename));
        } else if (filename.endsWith('.out')) {
            await fs.rename(pathJoin(dataDir, filename), getFilePath(filename));
        }
    }

    // Send response
    res.send('');
    server.close(() => {});
}));

// Open server on a port which is not already in use
let server,
    port = DEFAULT_PORT;
for (let i = 0; i < LISTEN_ATTEMPTS; i++) {
    console.log(`Starting server on port ${port}`);
    try {
        await new Promise((resolve, reject) => {
            server = app.listen(port, resolve);
            server.on('error', reject);
        });
        break;
    } catch (err) {
        if (err?.code !== 'EADDRINUSE') throw err;
        console.log(`Port ${port} in use. Trying again.`);
        port = DEFAULT_PORT + Math.round(Math.random() * 5000);
    }
}
console.log(`Server listening on port ${port}`);

// Output data dir path + port to env vars
await fs.appendFile(process.env.GITHUB_ENV, `DATA_DIR=${dataDir}\nINTERCEPT_PORT=${port}\n`);
