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 <firmware> <file> <comments>'
exports.desc = 'Upload a new version of your firmware.'
exports.builder = function (yargs) {

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

  return yargs
    .usage('Usage:\ndarwincli firmware upload /Documents/Firmware/build.hex "Fresh Buttons"')
    .positional('firmware', {
      describe: 'The id of the firmware to upload',
      type: 'string'
    })
    .positional('file', {
      describe: 'The firmware file to upload',
      type: 'string'
    })
    .positional('comments', {
      describe: 'Comments for this firmware version',
      type: 'string'
    })
    .help()
    .epilog(epilog)
}

exports.handler = function (argv) {

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

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

  //1.2) Determine the size of the file.
  let stats = fs.statSync(uploadFile)
  let fileSizeInBytes = stats.size;
  const maxSize = 1024 * 1024 * 1024 //1GB
  if (fileSizeInBytes > maxSize) {
    console.log(chalk.red.bold("The file you are trying to upload is too large. The maximum file size is 1GB. You provided a file of size: " + (fileSizeInBytes / (1024 * 1024)).toFixed(2) + "MB"))
    return
  }

  let comments = argv.comments

  //2.2) If name not set and no git commit, then error out.
  if (comments == undefined || comments.length == 0) {
    console.log(chalk.green.bold("Please provide some comments for this version of your firmware."))
    return
  }

  console.log(chalk.blue("Uploading new version for firmware: " + argv.firmware))
  console.log(chalk.yellow("Comments: " + chalk.bold(comments) + "\n\n"))

  //4) Create the deployment and get back upload variables.
  API.clientCallDarwinAPI("POST", "firmwareUpload", {
    "firmwareID": argv.firmware,
    "comments": comments,
  }, [], (result, data) => {
    if (!result) {
      console.log(chalk.bold.red(data.error || data))
      return
    }
    console.log(chalk.blue.bold("Uploading Firmware File...\n"))

    //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(1, 0, {
      key: ""
    });

    //5.2) Upload the file
    let key = data.startKey + path.basename(uploadFile)
    API.uploadFileToS3(data.formAttributes, data.formInputs, key, uploadFile, (success, error) => {
      if (success) {
        bar.setTotal(1)
        bar.update(1, {
          key: key
        })
        bar.stop()
        console.log(chalk.green("The firmware file has been uploaded."))

        //6) Switch over the firmware to this new deployment.
        let formInputs2 = {
          "firmwareID": argv.firmware,
          "versionID": data.versionID,
          "url": key,
        }
        API.clientCallDarwinAPI("PUT", "firmwareUpload", formInputs2, [], (result2, data2) => {
          if (!result2) {
            console.log("inputs:", formInputs2)
            console.log(chalk.bold.red(data2.error || data2))
            return
          }
          console.log("\n\n✨✨✨✨✨✨✨✨✨✨✨✨\n" +
            chalk.cyan.bold("Finished Uploading Firmware") +
            "\n✨✨✨✨✨✨✨✨✨✨✨✨\n")
        }, process.env.DARWIN_CLIENT_ID, process.env.DARWIN_CLIENT_SECRET)
      } else {
        //we should error out.
        bar.stop()
        console.log(chalk.bold.red("Error uploading firmware file: " + error))
      }
    }, false, 0)
  }, 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
}
