const fs = require('fs-extra')
const rp = require('@cypress/request-promise')
const util = require('util')
const exec = require('child_process').exec
const minimist = require('minimist')
const chalk = require('chalk')

const execPromise = util.promisify(exec)

const getArtifactJobName = (platformKey) => {
  if (platformKey === 'linux-x64') {
    return 'linux-amd-publish-binary'
  }

  if (platformKey === 'linux-arm64') {
    return 'linux-arm64-publish-binary'
  }

  return 'publish-binary'
}

const urlPaths = [
  '~/cypress/binary-url.json',
  '~/cypress/npm-package-url.json',
]

const archivePaths = [
  '~/cypress/cypress.zip',
  '~/cypress/cypress.tgz',
]

function getRequestOptions (url) {
  return {
    method: 'GET',
    url,
    headers: { 'Circle-Token': process.env.CIRCLE_TOKEN },
  }
}

function getPipelineId (pipelineInfoFilePath) {
  const data = fs.readFileSync(pipelineInfoFilePath)

  const parsedPipelineId = JSON.parse(data).id

  if (!parsedPipelineId) {
    throw new Error(`error retrieving pipeline id from ${pipelineInfoFilePath}`)
  }

  return parsedPipelineId
}

async function getWorkflows (pipelineId) {
  const response = await rp(getRequestOptions(`https://circleci.com/api/v2/pipeline/${pipelineId}/workflow`))

  const parsed = JSON.parse(response)

  if (parsed.items.length === 0) {
    throw new Error(`did not find any workflows in pipeline ${pipelineId}`)
  }

  if (parsed.items.length > 1) {
    console.log(parsed.items)
    throw new Error(`expected pipeline ${pipelineId} to only have one workflow, but it had many`)
  }

  return parsed.items
}

async function getWorkflowJobs (workflowId) {
  const response = await rp(getRequestOptions(`https://circleci.com/api/v2/workflow/${workflowId}/job`))

  const parsed = JSON.parse(response)

  if (parsed.items.length === 0) {
    throw new Error(`did not find any jobs in workflow ${workflowId}`)
  }

  return parsed.items
}

async function getJobArtifacts (jobNumber) {
  const response = await rp(getRequestOptions(`https://circleci.com/api/v2/project/github/cypress-io/cypress-publish-binary/${jobNumber}/artifacts`))

  const parsed = JSON.parse(response)

  if (parsed.items.length === 0) {
    throw new Error(`did not find any artifacts for job ${jobNumber}`)
  }

  return parsed.items
}

async function downloadArtifact (url, path) {
  try {
    console.log(`Downloading artifact from ${chalk.cyan(url)} \n to path ${chalk.cyan(path)}...`)
    await execPromise(`curl -L --url ${url} --header 'Circle-Token: ${process.env.CIRCLE_TOKEN}' --header 'content-type: application/json' -o ${path}`)
  } catch (error) {
    throw new Error(`failed to fetch artifact from URL ${url}: ${error}`)
  }
}

async function run (args) {
  const options = minimist(args)

  const artifactJobName = getArtifactJobName(options.platformKey)

  const pipelineInfoFilePath = options.pipelineInfo

  if (!pipelineInfoFilePath) {
    throw new Error('--pipelineInfo must be provided as a parameter')
  }

  console.log(`Parsing pipeline info from ${chalk.cyan(pipelineInfoFilePath)}...`)

  const pipelineId = module.exports.getPipelineId(pipelineInfoFilePath)

  console.log(`Getting workflows from pipeline ${chalk.cyan(pipelineId)}...`)
  const workflows = await module.exports.getWorkflows(pipelineId)

  const workflow = workflows[0]

  if (workflow.status !== 'success') {
    console.error(chalk.red(`\nThe ${chalk.cyan(workflow.name)} workflow that we triggered in the ${chalk.cyan('cypress-publish-binary')} project did not succeed.\n
Status: ${chalk.red(workflow.status)} \n
Check the workflow logs to see why it failed

${chalk.cyan.underline(`https://app.circleci.com/pipelines/workflows/${workflow.id}`)}
    `))

    process.exitCode = 1

    return
  }

  console.log(`Getting jobs from workflow ${chalk.cyan(workflow.name)}...`)
  const jobs = await module.exports.getWorkflowJobs(workflow.id)

  const job = jobs.find((job) => job.name === artifactJobName)

  if (!job) {
    throw new Error(`unable to find job in workflow ${workflow.name} named ${artifactJobName}`)
  }

  const artifacts = await module.exports.getJobArtifacts(job.job_number)

  let artifactPaths

  if (process.env.SHOULD_PERSIST_ARTIFACTS) {
    artifactPaths = [...urlPaths, ...archivePaths]
  } else {
    // If we didn't persist the artifacts to the registry, then we only want the build artifacts, no URLs.
    artifactPaths = [...archivePaths]
  }

  const filteredArtifacts = artifacts.filter((artifact) => artifactPaths.includes(artifact.path))

  await Promise.all(filteredArtifacts.map(({ url, path }) => {
    return module.exports.downloadArtifact(url, path)
  }))

  console.log('Artifacts successfully downloaded ✅')
}

module.exports = {
  getPipelineId,
  getWorkflows,
  getWorkflowJobs,
  getJobArtifacts,
  downloadArtifact,
  run,
}

if (!module.parent) {
  run(process.argv)
}
