import API from '../../API.js';
const chalk = require("chalk");
const cliProgress = require('cli-progress');
const path = require('path')
const fs = require('fs')
const {process: childProcess} = require('child_process')
const execSync = require('child_process').execSync;


exports.command = 'upload <site> <dir> [name] [description]'
exports.desc = 'Upload a new version of your site.'
exports.builder = function (yargs) {

  const epilog = chalk.dim("Darwin Command Line Interface\nCopyright 2019 Darwin Inc. All Rights Reserved.\n")

  return yargs
    .usage('Usage:\ndarwincli site upload 312 /Documents/Sites/MyDomain/build "Fresh Buttons" "Testing out the new button look."')
    .positional('site', {
      describe: 'The id of the site to upload',
      type: 'string'
    })
    .positional('dir', {
      describe: 'The directory of files to upload recursively',
      type: 'string'
    })
    .positional('name', {
      describe: 'The name of this version for reference',
      type: 'string'
    })
    .positional('description', {
      describe: 'A list of changes',
      type: 'string'
    })
    .help()
    .epilog(epilog)
}

exports.handler = function (argv) {

  //1) Determine the directory
  //console.log("Determining the directory")
  let uploadFolder = path.resolve(argv.dir)
  //console.log("Upload folder: ", uploadFolder)

  //1.1) Make sure the directory is valid.
  if (!fs.existsSync(uploadFolder) || !fs.lstatSync(uploadFolder).isDirectory()) {
    console.log(chalk.red.bold("This directory does not exist. Directories are relative. You provided:\n" + uploadFolder))
    return
  }
  //console.log("Directory is valid")

  //1.2) Make sure the directory isn't above 1 GB for uploading.
  let folderSizeLimit = 1073741824
  let results = directoryStats(uploadFolder, folderSizeLimit)
  if (results.size > folderSizeLimit) {
    console.log(chalk.red.bold("This directory is too big! It contains over 1 GB of data. Darwin Sites are restricted to 1 GB. Contact support for more information."))
    return
  }
  //console.log("Directory is less than 100MB.")

  //1.3) Determine the number of files to upload
  let fileCount = results.fileCount
  //console.log("File Count:", fileCount)

  let name = argv.name
  let description = argv.description
  //console.log("name & description", name, description)

  //2) If the name and description are unset, then try to get them from the last git commit if possible.
  if (name == undefined) {
    //2.1) Try using the provided folder path, then use the parent if that fails, then the current directory.
    let git = null
    //2.2) Try the upload folder for .git
    git = getLastCommit({dst: uploadFolder})
    if (git.subject === null) {
      //2.3) Try the parent folder for .git
      //console.log("trying parent")
      git = getLastCommit({dst: path.resolve(uploadFolder, '..')})
      if (git.subject === null) {
        //2.4) Try the current folder for .git
        //console.log("trying current")
        git = getLastCommit()
      }
    }
    console.log("Last Git Commit", git)
    if (git.subject !== null) {
      console.log(chalk.green("Taking name and description from the last .git commit."))
      if (git.subject.length > 50) {
        name = git.subject.substring(0, 47) + "..."
      } else {
        name = git.subject
      }
      description = (git.body || git.subject).substring(0, 800)
    } else {
      console.log("Couldn't find a .git directory. We could not pull a commit tag.")
    }
  }

  //2.2) If name not set and no git commit, then error out.
  if (name == undefined || name.length == 0) {
    console.log(chalk.green.bold("Please provide a name for this version of your site."))
    return
  }
  if (description === undefined || description === null) {
    //2.3) If the description is unset, default to the name.
    description = name
  }

  console.log(chalk.blue("Uploading new version for site: " + argv.site))
  console.log(uploadFolder)
  console.log(chalk.yellow("With name: " + chalk.bold(name)))
  console.log(chalk.yellow("Description: " + chalk.bold(description) + "\n\n"))

  //4) Create the deployment and get back upload variables.
  API.clientCallDarwinAPI("POST", "siteUpload", {
    "siteID": argv.site,
    "name": name,
    "description": description,
  }, [], (result, data) => {
    if (!result) {
      console.log(chalk.bold.red(data.error || data))
      return
    }
    console.log(chalk.blue.bold("Uploading Files"))

    //5) Upload all of the files in this directory.
    //5.1) Create new progress bar
    const bar = new cliProgress.SingleBar({
        format: chalk.bold('Progress') + ' |' + chalk.white('{bar}') + '| {percentage}% || {value}/{total} Files | {key}',
        barCompleteChar: '\u2588',
        barIncompleteChar: '\u2591',
        hideCursor: true
    });
    bar.start(fileCount, 0, {
      key: ""
    });

    //5.2) Upload the files.
    uploadFiles(data, uploadFolder, (success, error) => {
      if (success) {
        //we have successfully uploaded the folder.
        bar.stop()
        console.log(chalk.green("All of the files have been uploaded."))

        //6) Switch over the website to this new deployment.
        API.clientCallDarwinAPI("PUT", "siteUpload", {
          "siteID": argv.site,
          "deploymentID": data.deploymentID,
        }, [], (result2, data2) => {
          if (!result2) {
            console.log(chalk.bold.red(data2.error || data2))
            return
          }
          console.log("\n\n✨✨✨✨✨✨✨✨✨✨✨✨\n" +
            chalk.cyan.bold("Finished Uploading Site") +
            "\n✨✨✨✨✨✨✨✨✨✨✨✨\n")
        }, process.env.DARWIN_CLIENT_ID, process.env.DARWIN_CLIENT_SECRET)
      } else {
        console.log(chalk.bold.red("Error, we couldn't upload all of the files: " + error))
      }
    }, (progressPercent, progressIndex, progressTotal, key) => {
      //5.3) Update the progress bar
      bar.setTotal(progressTotal)
      bar.update(progressIndex, {
        key: key
      })
    })
  }, process.env.DARWIN_CLIENT_ID, process.env.DARWIN_CLIENT_SECRET)
}

/*
Returns the size of this folder along with the number of files:
{size: 1024000, fileCount:14}
*/
function directoryStats(dir, limit, size = 0, count = 0) {
  let ourSize = size
  let ourCount = count
  //get all the files/directories in the current directory.
  let res;
  try {
    res = fs.readdirSync(dir)
  } catch (e) {
    //issue with this directory so just return
    return {
      size: ourSize,
      fileCount: ourCount
    }
  }
  let resLength = res.length
  for (let i = 0; i < resLength; i = i + 1) {
    let cp = path.join(dir, res[i])
    if (fs.lstatSync(cp).isDirectory()) {
      //recurse down the directory
      const stats = directoryStats(cp)
      ourCount += stats.fileCount
      ourSize += stats.size
    } else {
      //this is a file, get the size of the file and increment our count
      let stats;
      try {
        stats = fs.statSync(cp);
      } catch (e) {
        //can't read this file, go to the next one.
        continue;
      }
      const fileSizeInBytes = stats.size;
      ourCount += 1
      ourSize += fileSizeInBytes
    }
    if (ourSize > limit) {
      return {
        size: ourSize,
        fileCount: ourCount
      }
    }
  }
  return {
    size: ourSize,
    fileCount: ourCount
  }
}

const executeCommand = (command, options, callback) => {
  let dst = __dirname

  if(!!options && options.dst) {
    dst = options.dst
  }

  try {
    //console.log("cmd", dst)
    return execSync(command, {cwd: dst})
    //return childProcess.execSync(command, {cwd: dst})
  } catch (e) {
    console.log(e)
    return ''
  }
}

const getCommandStringSubject = 'git log --pretty=format:"%s" -1'
const getCommandStringBody = 'git log --pretty=format:"%b" -1'

const getLastCommit = (options) => {

  let subject = executeCommand(getCommandStringSubject, options).toString()
  if (subject == '') {
    subject = null
  }

  let body = executeCommand(getCommandStringBody, options).toString()
  if (body == '') {
    body = null
  }

  return {
    subject: subject,
    body: body,
  }
}

/*
 * Uploads the directory to S3 with a presigned url.
 * data: pass in results from the presigned url request.
 * folder: the folder of files to upload.
 * callback: the callback when we are done uploading.
 * progress: the progress callback once a file has been uploaded.
 */
function uploadFiles(data, folder, callback, progress) {
  //upload the files
  let files = getFileList(folder, folder)
  // Remove all of the files that aren't supposed to be uploaded by the
  // site.darwinignore file if it exists
  if (fs.existsSync(folder + "/" + "site.darwinignore")) {
    //A darwin ignore file exists so we should ignore certain files for upload
    try {
      let darwinIgnore = fs.readFileSync(folder + "/" + "site.darwinignore", "utf8");
      let rules = darwinIgnore.split("\n")
      let validFiles = [];
      for (let fi = 0; fi < files.length; fi += 1) {
        let fpath = files[fi].relative
        //now test each rule
        let valid = true
        for (let ri = 0; ri < rules.length; ri += 1) {
          //test the rule using regex
          if (rules[ri] == "") {
            //blank line
            continue;
          }
          //create the regex
          let regexString = "^" + rules[ri] + "$"
          let regex = new RegExp(regexString)
          if (regex.test(fpath)) {
            valid = false;
            //console.log("not valid: ", fpath)
            break;
          }
        }
        if (valid) {
          validFiles.push(files[fi])
        }
      }
      files = validFiles
      //console.log("Resolved Files:", files)
    } catch (e) {
      console.log(chalk.red.bold("Issue reading site.darwinignore file."))
    }
  }
  let pCount = 0
  uploadFileList(data.formAttributes, data.formInputs, data.startKey, files, 0, (res, error) => {
    if (res) {
      callback(true, null)
    } else {
      callback(false, error)
    }
  }, (key) => {
    pCount += 1
    let percent = Math.round((pCount / files.length) * 100)
    //this is the progress callback
    progress(percent, pCount, files.length, key)
  })
}

/*
 * Uploads a list of files one after the other and calls the callback when finished uploading everything
 */
function uploadFileList(attributes, inputs, startKey, fileList, index, callback, progressCallback, attempt = 0) {
  let key = startKey + fileList[index]['relative']
  API.uploadFileToS3(attributes, inputs, key, fileList[index]['absolute'], (success, error) => {
    if (success) {
      progressCallback(key)
      index += 1
      if (index == fileList.length) {
        callback(true, null)
      } else {
        //upload the next file
        uploadFileList(attributes, inputs, startKey, fileList, index, callback, progressCallback)
      }
    } else if (attempt >= 10) {
      //we have failed 10 times, we should error out.
      callback(false, "Tried and failed 10 times to upload file: " + key)
    } else {
      //we should try to upload this file again
      uploadFileList(attributes, inputs, startKey, fileList, index, callback, progressCallback, attempt + 1)
    }
  }, false, 0)
}

/*
 * Returns the list of all of the files in the folder
 */
function getFileList(dir, startDir) {
  let results = []
  let list = fs.readdirSync(dir)
  list.forEach(function(file) {
      file = dir + '/' + file
      let fileName = file.substr(startDir.length + 1);
      let baseName = path.basename(file)
      if (baseName.charAt(0) != ".") {
        let stat = fs.statSync(file)
        if (stat && stat.isDirectory()) {
          results = results.concat(getFileList(file, startDir))
        } else {
          results.push({"absolute":file,"relative":fileName})
        }
      }
  })
  return results
}
