#!/usr/bin/env node

const program = require("commander");
const inquirer = require("inquirer");

const path = require("path");
const fs = require("fs");
const os = require("os");
const { spawn, execSync } = require("child_process");

const slash = require("slash");
const YAML = require("yaml");
const Lokka = require("lokka").Lokka;
const Transport = require("lokka-transport-http").Transport;

/**
 * Execute a command with nice console output.
 *
 * @param {string} command to execute
 */
const exec = (command, opt) =>
  new Promise((resolve) => {
    const [c, ...args] = command.split(" ");
    const task = spawn(c, args, { stdio: "inherit", ...opt });
    task.on("close", resolve);
  });

const dockerComposeFile = slash(
  path.relative(process.cwd(), path.join(__dirname, "/docker-compose.yml"))
);

const dockerCompose = YAML.parse(fs.readFileSync(dockerComposeFile, "utf-8"));
const services = Object.keys(dockerCompose.services);

// Program
const { version } = fs.readFileSync(
  path.join(__dirname, "./package.json"),
  "utf-8"
);

program.version(version);

program
  .command("clean")
  .description("Stop the environment and remove volumes")
  .action(() => exec(`docker-compose -f ${dockerComposeFile} down -v`));

program
  .command("docker-auth")
  .description("Setup docker authentification with gcloud")
  .action(() => {
    // Doesn't work with `exec` ¯\_(ツ)_/¯
    console.log("Please execute the following commands:");
    console.log("- gcloud auth login");
    console.log("- gcloud auth configure-docker");
  });

program
  .command("pull")
  .description("Pull the latest docker images")
  .action(() => {
    exec(
      `docker-compose -f ${dockerComposeFile} pull datastore ui auth pantheon profiler hub graphql`
    );
  });

program
  .command("start")
  .description("Run the local dev enviromnent on http://localhost:9898")
  .action(() => {
    exec(
      `docker-compose -f ${dockerComposeFile} up -d --force-recreate --remove-orphans`
    ).then(() => {
      console.log("-----");
      console.log("Tracing ui: http://localhost:16686/search");
      console.log("Dev ui: http://localhost:9898");
      console.log("SMTP ui: http://localhost:8025");
      console.log("Email: lemon@example.com");
      console.log("Password: localdev");
    });
  });

program
  .command("logs [SERVICE...]")
  .option(
    "--tail [n]",
    "Number of lines to show from the end of the logs for each container."
  )
  .option("-f, --follow", "Follow log output.")
  .description("Get logs of a service.")
  .action((_, { parent: { rawArgs }, tail, follow }) => {
    let args = [];
    if (typeof tail === "string") args.push(`--tail ${tail}`);
    if (follow) args.push(`-f`);
    args.push(...rawArgs.filter((i) => services.includes(i)));
    exec(`docker-compose -f ${dockerComposeFile} logs ${args.join(" ")}`);
  });

program
  .command("restart [SERVICE...]")
  .description("Restart running containers.")
  .action((_, { parent: { rawArgs } }) => {
    let args = rawArgs.filter((i) => services.includes(i));
    exec(`docker-compose -f ${dockerComposeFile} restart ${args.join(" ")}`);
  });

program
  .command("stop")
  .description("Stop the running docker container")
  .action(() => {
    exec(`docker-compose -f ${dockerComposeFile} down`);
  });

program
  .command("repro")
  .description(
    "Compute the exact image digests that are used in the current setup"
  )
  .action(() => {
    const configYaml = execSync(
      `docker-compose -f ${dockerComposeFile} config --resolve-image-digests`
    );
    const config = YAML.parse(configYaml.toString());
    const output = {
      version: config.version,
      services: Object.entries(config.services).reduce(
        (mem, [key, value]) => ({ ...mem, [key]: { image: value.image } }),
        {}
      ),
    };
    console.log(YAML.stringify(output));
  });

program
  .command("pr-preview [pullRequests...]")
  .description("Testing PR images (ex: `pr-preview hub:123 contiamo-ui:456`)")
  .action(async (pullRequests) => {
    const prPreviewableServices = Object.values(dockerCompose.services).reduce(
      (mem, s) => {
        if (!s.image.startsWith("$")) return mem;
        const detail = /\$\{(\w+):-eu.gcr.io\/dev-and-test-env\/([\w-]+)/.exec(
          s.image
        );
        if (mem.map((m) => m.repo).includes(detail[2])) return mem;
        return [...mem, { repo: detail[2], envKey: detail[1] }];
      },
      []
    );

    if (pullRequests.length === 0) {
      const { repositories } = await inquirer.prompt([
        {
          type: "checkbox",
          name: "repositories",
          choices: prPreviewableServices.map((i) => ({
            name: i.repo,
            value: i.repo,
          })),
          message: "Please select a repository",
        },
      ]);

      for (const repo of repositories) {
        // Retrieve the 10 last github PR
        let accessToken;
        const githubTokenPath = path.join(os.homedir(), ".restful-react");

        if (fs.existsSync(githubTokenPath)) {
          accessToken = fs.readFileSync(githubTokenPath, "utf-8");
        } else {
          const answers = await inquirer.prompt([
            {
              type: "input",
              name: "githubToken",
              message:
                "Please provide a GitHub token with `repo` rules checked ( https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line )",
            },
          ]);
          fs.writeFileSync(githubTokenPath, answers.githubToken);
          accessToken = answers.githubToken;
        }

        const client = new Lokka({
          transport: new Transport("https://api.github.com/graphql", {
            headers: {
              "user-agent": "contiamo-dev",
              authorization: `bearer ${accessToken}`,
            },
          }),
        });

        const last10prReq = await client
          .query(
            `{
          repository(name:"${repo}", owner:"contiamo") {
            pullRequests(last: 10){
              nodes {
                number
                title
              }
            }
          }
        }`
          )
          .then((res) => res.repository.pullRequests.nodes);

        const { pr } = await inquirer.prompt({
          type: "list",
          name: "pr",
          message: `Please select the PR you want to pull for ${repo}`,
          choices: last10prReq.reverse().map((i) => ({
            name: `#${i.number} - ${i.title}`,
            value: i.number,
          })),
        });

        pullRequests.push(`${repo}:${pr}`)
      };
    }

    if (pullRequests.length === 0) {
      console.log("You need to select at least one pull-request");
      return;
    }

    await Promise.all(pullRequests.map(async (pullRequest) => {
      const [repo, pr] = pullRequest.split(":")
      console.log(`Pull image for ${repo} PR #${pr}`);
      await exec(
        `docker pull eu.gcr.io/dev-and-test-env/${repo}-preview:pr-${pr}`
      );
    }))

    const env = process.env;
    pullRequests.forEach(pullRequest => {
      const [repo, pr] = pullRequest.split(":")
      env[
        prPreviewableServices.find((i) => i.repo === repo).envKey
      ] = `eu.gcr.io/dev-and-test-env/${repo}-preview:pr-${pr}`;
    })

    exec(`docker-compose -f ${dockerComposeFile} up -d --force-recreate`, {
      env,
    });
  });

program.parse(process.argv);
