const { Command } = require("commander");
const core = require("@actions/core");
const fetch = require("node-fetch");
const { Octokit } = require("@octokit/core");

const program = new Command();

program
  .name("Approve PR")
  .description("Approve and merge PR if patch release")
  .option("--version <version>", "version of a project")
  .option("--token <token>", "token fo login to cloud")
  .option("--repo_token <repo_token>", "github token")
  .action(async options => {
    const isOldVersion = await checkIfOldVersion(
      options.version,
      options.repo_token,
    );
    core.setOutput("IS_OLD_VERSION", isOldVersion);

    if (!isPatchRelease(options.version)) {
      //If it's minor version, check services, to get one before current
      //Then create environment with this service, and big db
      const snapshotWithBigDB = "nqw0Qmbr";
      const versionBefore = await getServiceWithVersionOneBeforeVersion(
        options.version,
        options.token,
      );
      const environmentKey = await createEnvironment(
        options.version,
        options.token,
        versionBefore,
        snapshotWithBigDB,
      );

      //Update environment to current version
      await updateEnvironment(environmentKey, options.version, options.token);
      const domain = await getDomainForUpdatedEnvironment(
        environmentKey,
        options.token,
      );
      core.setOutput("url", `https://${domain}/`);
    } else {
      core.setOutput(
        "url",
        `https://v${getFormattedVersion(
          options.version,
        )}.staging.saleor.cloud/`,
      );
    }
  })
  .parse();

async function createEnvironment(version, token, versionBefore, snapshot) {
  const response = await fetch(
    `https://staging-cloud.saleor.io/api/organizations/saleor/environments/`,
    {
      method: "POST",
      body: `{ 
        "service": "${versionBefore}",
        "project": "staging",
        "name": "minor-release-test-${version}",
        "domain_label": "minor-release-test-${getFormattedVersion(version)}",
        "restore_from": "${snapshot}"
      }`,
      headers: {
        Authorization: `Token ${token}`,
        Accept: "application/json",
        "Content-Type": "application/json;charset=UTF-8",
      },
    },
  );
  const responseInJson = await response.json();

  if (!responseInJson.task_id)
    throw new Error(
      `Can't create environment- ${JSON.stringify(responseInJson)}`,
    );
  const throwErrorAfterTimeoutWhenCreatingEnv = setTimeout(function () {
    throw new Error("Environment didn't crated after 20 minutes");
  }, 1200000);
  return await waitUntilTaskInProgress(
    responseInJson.task_id,
    token,
    throwErrorAfterTimeoutWhenCreatingEnv,
  ).then(() => {
    clearTimeout(throwErrorAfterTimeoutWhenCreatingEnv);
    return responseInJson.key;
  });
}

async function updateEnvironment(environmentId, version, token) {
  console.log(
    `{ "service": "saleor-staging-v${getFormattedVersion(version)}" }`,
  );
  const response = await fetch(
    `https://staging-cloud.saleor.io/api/organizations/saleor/environments/${environmentId}/upgrade/`,
    {
      method: "PUT",
      body: `{ "service": "saleor-staging-v${getFormattedVersion(version)}" }`,
      headers: {
        Authorization: `Token ${token}`,
        Accept: "application/json",
        "Content-Type": "application/json;charset=UTF-8",
      },
    },
  );
  const responseInJson = await response.json();
  if (!responseInJson.task_id)
    throw new Error(
      `Can't update environment- ${JSON.stringify(responseInJson)}`,
    );
  const throwErrorAfterTimeout = setTimeout(function () {
    throw new Error("Environment didn't upgraded after 20 minutes");
  }, 1200000);
  await waitUntilTaskInProgress(
    responseInJson.task_id,
    token,
    throwErrorAfterTimeout,
  );
  clearTimeout(throwErrorAfterTimeout);
}

async function waitUntilTaskInProgress(taskId, token) {
  const response = await fetch(
    `https://staging-cloud.saleor.io/api/service/task-status/${taskId}/`,
    {
      method: "GET",
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json;charset=UTF-8",
      },
    },
  );
  const responseInJson = await response.json();
  console.log(`Job status - ${responseInJson.status}`);
  if (
    responseInJson.status == "PENDING" ||
    responseInJson.status == "IN_PROGRESS"
  ) {
    await new Promise(resolve =>
      setTimeout(function () {
        resolve(waitUntilTaskInProgress(taskId, token));
      }, 10000),
    );
  } else if (responseInJson.status == "SUCCEEDED") {
    return responseInJson.status;
  } else if (responseInJson.status !== "SUCCEEDED") {
    console.log("error");

    throw new Error(
      `Job ended with status - ${responseInJson.status} - ${responseInJson.job_name}`,
    );
  }
}

function isPatchRelease(version) {
  const regex = /\d+\.\d+\.[1-9]/;
  return version.match(regex) ? true : false;
}

function getFormattedVersion(version) {
  const regex = /^\d+\.\d+\./;
  return version.match(regex)[0].replace(/\./g, "");
}

async function getDomainForUpdatedEnvironment(environmentId, token) {
  const response = await fetch(
    `https://staging-cloud.saleor.io/api/organizations/saleor/environments/${environmentId}`,
    {
      method: "GET",
      json: true,
      responseType: "json",
      headers: {
        Authorization: `Token ${token}`,
      },
    },
  );
  const responseInJson = await response.json();
  return responseInJson.domain;
}

async function checkIfOldVersion(version, token) {
  const newestVersion = await getTheNewestVersion(token);
  const howManyVersionsBehind =
    getFormattedVersion(newestVersion) - getFormattedVersion(version);
  //All versions besides last three are old versions
  return howManyVersionsBehind > 2 ? "true" : "false";
}

async function getTheNewestVersion(token) {
  const octokit = new Octokit({
    auth: token,
  });

  const response = await octokit.request(
    "GET /repos/{owner}/{repo}/releases/latest",
    {
      owner: "saleor",
      repo: "saleor-dashboard",
    },
  );
  return response.data["tag_name"];
}

async function getServices(token) {
  // us-east-1
  const response = await fetch(
    `https://staging-cloud.saleor.io/api/regions/us-east-1/services`,
    {
      method: "GET",
      headers: {
        Authorization: `Token ${token}`,
        Accept: "application/json",
        "Content-Type": "application/json;charset=UTF-8",
      },
    },
  );
  const responseInJson = await response.json();
  return responseInJson;
}

async function getServiceWithVersionOneBeforeVersion(version, token) {
  const services = await getServices(token);
  const sandboxServices = services.filter(
    element => element.service_type === "SANDBOX",
  );
  const sortedServices = sortServicesByVersion(sandboxServices);
  const currentService = sortedServices.find(service => {
    return service.name === `saleor-staging-v${getFormattedVersion(version)}`;
  });
  const serviceBeforeCurrent =
    sortedServices[sortedServices.indexOf(currentService) + 1];
  console.log(`Selected ${serviceBeforeCurrent.name} service`);
  return serviceBeforeCurrent.name;
}

function sortServicesByVersion(services) {
  // This function is used to sort environments by their version
  // It returns sorted list of services in descending order

  return services.sort(function (a, b) {
    //Convert version from string to array eg. from "3.5.7" to [3, 5, 7]
    // Where 3 is main version, 5 is major version and 7 is patch version

    const versionASplittedToArray = a.version.split(/\D/);
    const versionBSplittedToArray = b.version.split(/\D/);
    const mainVersionNumberA = versionASplittedToArray[0];
    const mainVersionNumberB = versionBSplittedToArray[0];
    const majorVersionNumberA = versionASplittedToArray[1];
    const majorVersionNumberB = versionBSplittedToArray[1];
    const patchVersionNumberA = versionASplittedToArray[2];
    const patchVersionNumberB = versionBSplittedToArray[2];

    //Compare two versions
    if (mainVersionNumberA !== mainVersionNumberB) {
      return mainVersionNumberB - mainVersionNumberA;
    } else if (majorVersionNumberA !== majorVersionNumberB) {
      return majorVersionNumberB - majorVersionNumberA;
    } else if (patchVersionNumberA !== patchVersionNumberB) {
      return patchVersionNumberB - patchVersionNumberA;
    } else return 0;
  });
}
