// store the cwd
const cwd = process.cwd()

const path = require('path')
const _ = require('lodash')
const os = require('os')
const gift = require('gift')
const chalk = require('chalk')
const Promise = require('bluebird')
const minimist = require('minimist')
const la = require('lazy-ass')
const check = require('check-more-types')
const debug = require('debug')('cypress:binary')
const questionsRemain = require('@cypress/questions-remain')
const R = require('ramda')

const zip = require('./zip')
const ask = require('./ask')
const bump = require('./bump')
const meta = require('./meta')
const build = require('./build')
const upload = require('./upload')
const uploadUtils = require('./util/upload')
const { uploadNpmPackage } = require('./upload-npm-package')
const { uploadUniqueBinary } = require('./upload-unique-binary')
const { moveBinaries } = require('./move-binaries')

// initialize on existing repo
const repo = Promise.promisifyAll(gift(cwd))

const success = (str) => {
  return console.log(chalk.bgGreen(` ${chalk.black(str)} `))
}

const fail = (str) => {
  return console.log(chalk.bgRed(` ${chalk.black(str)} `))
}

const zippedFilename = R.always(upload.zipName)

// goes through the list of properties and asks relevant question
// resolves with all relevant options set
// if the property already exists, skips the question
const askMissingOptions = function (properties = []) {
  const questions = {
    platform: ask.whichPlatform,
    version: ask.deployNewVersion,
    // note: zip file might not be absolute
    zip: ask.whichZipFile,
    commit: ask.toCommit,
  }
  const pickedQuestions = _.pick(questions, properties)

  return questionsRemain(pickedQuestions)
}

// hack for @packages/server modifying cwd
process.chdir(cwd)

const commitVersion = function (version) {
  const msg = `release ${version} [skip ci]`

  return repo.commitAsync(msg, {
    'allow-empty': true,
  })
}

const deploy = {
  meta,

  parseOptions (argv) {
    const opts = minimist(argv, {
      boolean: ['skip-clean'],
      default: {
        'skip-clean': false,
      },
      alias: {
        skipClean: 'skip-clean',
        zip: ['zipFile', 'zip-file', 'filename'],
      },
    })

    if (opts['skip-tests']) {
      opts.runTests = false
    }

    if (!opts.platform && (os.platform() === meta.platforms.linux)) {
      // only can build Linux on Linux
      opts.platform = meta.platforms.linux
    }

    // windows aliases
    if ((opts.platform === 'win32') || (opts.platform === 'win') || (opts.platform === 'windows')) {
      opts.platform = meta.platforms.windows
    }

    if (!opts.platform && (os.platform() === meta.platforms.windows)) {
      // only can build Windows binary on Windows platform
      opts.platform = meta.platforms.windows
    }

    // be a little bit user-friendly and allow aliased values
    if (opts.platform === 'mac') {
      opts.platform = meta.platforms.darwin
    }

    debug('parsed command line options')
    debug(opts)

    return opts
  },

  bump () {
    return ask.whichBumpTask()
    .then((task) => {
      switch (task) {
        case 'run':
          return bump.runTestProjects()
        case 'version':
          return ask.whichVersion(meta.distDir(''))
          .then((v) => {
            return bump.version(v)
          })
        default:
          throw new Error('unknown task')
      }
    })
  },

  release () {
    // read off the argv
    const options = this.parseOptions(process.argv)

    const release = ({ version, commit }) => {
      return upload.s3Manifest(version)
      .then(() => {
        if (commit) {
          return commitVersion(version)
        }
      }).then(() => {
        return success('Release Complete')
      }).catch((err) => {
        fail('Release Failed')
        throw err
      })
    }

    return askMissingOptions(['version'])(options)
    .then(release)
  },

  build (options) {
    console.log('#build')
    if (options == null) {
      options = this.parseOptions(process.argv)
    }

    debug('parsed build options %o', options)

    return askMissingOptions(['version', 'platform'])(options)
    .then(() => {
      debug('building binary: platform %s version %s', options.platform, options.version)

      return build(options.platform, options.version, options)
    })
  },

  zip (options) {
    console.log('#zip')
    if (!options) {
      options = this.parseOptions(process.argv)
    }

    return askMissingOptions(['platform'])(options)
    .then((options) => {
      const zipDir = meta.zipDir(options.platform)

      console.log('directory to zip %s', zipDir)
      options.zip = path.resolve(zippedFilename(options.platform))

      return zip.ditto(zipDir, options.zip)
    })
  },

  // upload Cypress NPM package file
  'upload-npm-package' (args = process.argv) {
    console.log('#packageUpload')

    return uploadNpmPackage(args)
  },

  // upload Cypress binary zip file under unique hash
  'upload-unique-binary' (args = process.argv) {
    console.log('#uniqueBinaryUpload')

    return uploadUniqueBinary(args)
  },

  // uploads a single built Cypress binary ZIP file
  // usually a binary is built on CI and is uploaded
  upload (options) {
    console.log('#upload')

    if (!options) {
      options = this.parseOptions(process.argv)
    }

    return askMissingOptions(['version', 'platform', 'zip'])(options)
    .then((options) => {
      la(check.unemptyString(options.zip),
        'missing zipped filename', options)

      options.zip = path.resolve(options.zip)

      return options
    }).then((options) => {
      console.log('Need to upload file %s', options.zip)
      console.log('for platform %s version %s',
        options.platform, options.version)

      return upload.toS3({
        zipFile: options.zip,
        version: options.version,
        platform: options.platform,
      })
    })
  },

  'move-binaries' (args = process.argv) {
    console.log('#moveBinaries')

    return moveBinaries(args)
  },

  // purge all platforms of a desktop app for specific version
  'purge-version' (args = process.argv) {
    console.log('#purge-version')
    const options = minimist(args, {
      string: 'version',
      alias: {
        version: 'v',
      },
    })

    la(check.unemptyString(options.version), 'missing app version to purge', options)

    return uploadUtils.purgeDesktopAppAllPlatforms(options.version, upload.zipName)
  },

  // goes through the entire pipeline:
  //   - build
  //   - zip
  //   - upload
  deploy () {
    const options = this.parseOptions(process.argv)

    return askMissingOptions(['version', 'platform'])(options)
    .then((options) => {
      return this.build(options)
      .then(() => {
        return this.zip(options)
      })
      // assumes options.zip contains the zipped filename
      .then(() => {
        return this.upload(options)
      })
    })
  },
}

module.exports = _.bindAll(deploy, _.functions(deploy))
