"use strict";

function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}

var axios = require("axios");
var crypto = require("crypto");

var _require = require("stream"),
  Readable = _require.Readable,
  Writable = _require.Writable;

var crc32 = require("fast-crc32c");
var path = require("path");
var fs = require("fs");

function getB2(options) {
  return new Promise(function(resolve, reject) {
    var encodedBase64 = new Buffer(
      options.accountId + ":" + options.applicationKey
    ).toString("base64");
    axios
      .post(
        "https://api.backblazeb2.com/b2api/v1/b2_authorize_account",
        {},
        {
          headers: { Authorization: "Basic " + encodedBase64 }
        }
      )
      .then(function(response) {
        var data = response.data;
        var credentials = {
          accountId: options.accountId,
          applicationKey: options.applicationKey,
          apiUrl: data.apiUrl,
          authorizationToken: data.authorizationToken,
          downloadUrl: data.downloadUrl,
          recommendedPartSize: data.recommendedPartSize
        };
        resolve(new B2Wrapper(credentials));
      })
      .catch(function(err) {
        throw new Error({
          status: 400,
          message: err.message
        });
      });
  });
}

var B2Wrapper = function B2Wrapper(credentials) {
  _classCallCheck(this, B2Wrapper);

  this.credentials = credentials;
  var self = this;

  var getBucketByName = function getBucketByName(bucketName) {
    return new Promise(function(resolve, reject) {
      axios
        .post(
          self.credentials.apiUrl + "/b2api/v1/b2_list_buckets",
          {
            accountId: self.credentials.accountId,
            bucketTypes: ["allPrivate", "allPublic"]
          },
          { headers: { Authorization: self.credentials.authorizationToken } }
        )
        .then(function(response) {
          var buckets = response.data.buckets;
          var bucket = null;
          buckets.forEach(function(element) {
            if (element.bucketName === bucketName) {
              bucket = element;
            }
          });

          if (!bucket) {
            reject({
              status: 400,
              message: "Bucket not found"
            });
          } else {
            resolve({
              status: 200,
              data: bucket
            });
          }
        })
        .catch(function(err) {
          reject({
            status: 400,
            message: err.message
          });
        });
    });
  };

  var getFileByName = function getFileByName(bucketName, filename) {
    return new Promise(function(resolve, reject) {
      getBucketByName(bucketName)
        .then(function(response) {
          if (response.status !== 200) {
            reject({
              status: 400,
              message: response.message
            });
          }
          var bucket = response.data;
          axios
            .post(
              self.credentials.apiUrl + "/b2api/v1/b2_list_file_names",
              {
                bucketId: bucket.bucketId
              },
              {
                headers: {
                  Authorization: self.credentials.authorizationToken
                }
              }
            )
            .then(function(response) {
              var files = response.data.files;
              var file = null;
              files.forEach(function(element) {
                if (element.fileName === filename) {
                  file = element;
                }
              });
              if (!file) {
                reject({
                  status: 400,
                  message: "File not found"
                });
              } else {
                resolve({
                  status: 200,
                  data: file
                });
              }
            })
            .catch(function(err) {
              reject({
                status: 400,
                message: err.message
              });
            });
        })
        .catch(function(err) {
          reject({
            status: 400,
            message: err.message
          });
        });
    });
  };

  var getUploadUrl = function getUploadUrl(bucketId) {
    return new Promise(function(resolve, reject) {
      axios
        .post(
          self.credentials.apiUrl + "/b2api/v1/b2_get_upload_url",
          {
            bucketId: bucketId
          },
          { headers: { Authorization: self.credentials.authorizationToken } }
        )
        .then(function(response) {
          resolve({
            status: 200,
            data: response.data,
            message: "UploadURL was received successfully"
          });
        })
        .catch(function(err) {
          reject({
            status: 400,
            message: err.message
          });
        });
    });
  };

  var getUploadPartUrl = function getUploadPartUrl(fileId) {
    return new Promise(function(resolve, reject) {
      axios
        .post(
          self.credentials.apiUrl + "/b2api/v1/b2_get_upload_part_url",
          {
            fileId: fileId
          },
          { headers: { Authorization: self.credentials.authorizationToken } }
        )
        .then(function(response) {
          resolve({
            status: 200,
            data: response.data,
            message: "UploadPartURL was received successfully"
          });
        })
        .catch(function(err) {
          reject({
            status: 400,
            message: err.message
          });
        });
    });
  };

  var startLargeFileUpload = function startLargeFileUpload(filename, bucketId) {
    return new Promise(function(resolve, reject) {
      var data = {
        fileName: filename,
        bucketId: bucketId,
        contentType: "b2/x-auto"
      };

      axios
        .post(self.credentials.apiUrl + "/b2api/v1/b2_start_large_file", data, {
          headers: { Authorization: self.credentials.authorizationToken }
        })
        .then(function(response) {
          resolve({
            status: 200,
            data: response.data,
            message:
              'Upload for large File "' +
              filename +
              '" was successfully started'
          });
        })
        .catch(function(err) {
          reject({
            status: 400,
            message: err.message
          });
        });
    });
  };

  var finishLargeFileUpload = function finishLargeFileUpload(
    fileId,
    filePath,
    bucketName,
    listOfSha1
  ) {
    return new Promise(function(resolve, reject) {
      var data = {
        fileId: fileId,
        partSha1Array: listOfSha1
      };
      axios
        .post(
          self.credentials.apiUrl + "/b2api/v1/b2_finish_large_file",
          data,
          { headers: { Authorization: self.credentials.authorizationToken } }
        )
        .then(function(response) {
          resolve({
            status: 200,
            message:
              'File "' +
              filePath +
              '" was uploaded successfully to bucket "' +
              bucketName +
              '"'
          });
        })
        .catch(function(err) {
          reject({
            status: 400,
            message: err.message
          });
        });
    });
  };

  var cancelLargeFileUpload = function cancelLargeFileUpload(fileId, filePath) {
    return new Promise(function(resolve, reject) {
      var data = {
        fileId: fileId
      };
      axios
        .post(
          self.credentials.apiUrl + "/b2api/v1/b2_cancel_large_file",
          data,
          { headers: { Authorization: self.credentials.authorizationToken } }
        )
        .then(function(response) {
          resolve({
            status: 200,
            message: 'Large File "' + filePath + '" upload was unsuccessful'
          });
        })
        .catch(function(err) {
          reject({
            status: 400,
            message: err.message
          });
        });
    });
  };

  var uploadSmallFile = function uploadSmallFile(
    source,
    fileName,
    filePath,
    fileSize,
    sha1,
    uploadUrl,
    uploadAuthorizationToken,
    bucketName
  ) {
    return new Promise(function(resolve, reject) {
      return axios
        .post(uploadUrl, source, {
          headers: {
            Authorization: uploadAuthorizationToken,
            "X-Bz-File-Name": fileName,
            "Content-Type": "b2/x-auto",
            "Content-Length": fileSize,
            "X-Bz-Content-Sha1": sha1,
            "X-Bz-Info-Author": "unknown"
          }
        })
        .then(function(response) {
          resolve({
            status: 200,
            message:
              'File "' +
              filePath +
              '" was uploaded successfully to bucket "' +
              bucketName +
              '"'
          });
        })
        .catch(function(err) {
          reject({
            status: 400,
            message: err.message
          });
        });
    });
  };

  var uploadBigFile = function uploadBigFile(
    source,
    fileName,
    filePath,
    fileSize,
    eachPartSize,
    bucketId,
    bucketName
  ) {
    var shouldSimulateBigFileUploadError =
      arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : false;

    return new Promise(function(resolve, reject) {
      startLargeFileUpload(fileName, bucketId).then(function(response) {
        var fileId = response.data.fileId;

        getUploadPartUrl(fileId).then(function(response) {
          var fileId = response.data.fileId;
          var uploadUrl = response.data.uploadUrl;
          var authorizationToken = response.data.authorizationToken;
          var numOfParts =
            eachPartSize > fileSize ? 1 : Math.ceil(fileSize / eachPartSize);
          var lastPartSize =
            eachPartSize > fileSize
              ? fileSize
              : fileSize - Math.floor(fileSize / eachPartSize) * eachPartSize;
          var partCounter = 0;
          var listOfSha1 = [];
          var destination = new Writable({
            write: function write(chunk, encoding, callback) {
              partCounter += 1;

              var hash = crypto.createHash("sha1");

              hash.setEncoding("hex");
              hash.write(chunk);
              hash.end();
              var sha1 = hash.read();
              listOfSha1.push(sha1);

              axios
                .post(uploadUrl, chunk, {
                  headers: {
                    Authorization: authorizationToken,
                    "X-Bz-Part-Number": partCounter,
                    "Content-Length":
                      partCounter !== numOfParts ? eachPartSize : lastPartSize,
                    "X-Bz-Content-Sha1": sha1
                  }
                })
                .then(function(response) {
                  callback();
                })
                .catch(function(err) {
                  cancelLargeFileUpload(fileId, filePath)
                    .then(function(response) {
                      reject(err);
                    })
                    .catch(function(err) {
                      reject({
                        status: 400,
                        message:
                          'Large File "' +
                          filePath +
                          '" upload was unsuccessful'
                      });
                    });
                });
            }
          });

          source.on("data", function(data) {
            if (partCounter === 2 && shouldSimulateBigFileUploadError) {
              cancelLargeFileUpload(fileId, filePath).then(function(response) {
                reject({
                  status: 400,
                  message:
                    'Large File "' + filePath + '" upload was unsuccessful'
                });
              });
            }
          });

          source.on("error", function(err) {
            cancelLargeFileUpload(fileId, filePath).then(function(response) {
              reject(err);
            });
          });

          destination.on("error", function(err) {
            cancelLargeFileUpload(fileId, filePath).then(function(response) {
              reject(err);
            });
          });

          destination.on("finish", function(value) {
            finishLargeFileUpload(fileId, filePath, bucketName, listOfSha1)
              .then(function(response) {
                resolve(response);
              })
              .catch(function(err) {
                cancelLargeFileUpload(fileId, filePath).then(function(
                  response
                ) {
                  reject(err);
                });
              });
          });
          source.pipe(destination);
        });
      });
    });
  };

  self.createBucket = function(name) {
    if (!name) {
      return new Promise(function(resolve, reject) {
        reject({
          status: 400,
          message: "Invalid parameters"
        });
      });
    }

    return new Promise(function(resolve, reject) {
      axios
        .get(
          self.credentials.apiUrl +
            "/b2api/v1/b2_create_bucket?accountId=" +
            self.credentials.accountId +
            "&bucketName=" +
            name +
            "&bucketType=allPrivate",
          { headers: { Authorization: self.credentials.authorizationToken } }
        )
        .then(function(response) {
          resolve({
            status: 201,
            message: 'Bucket "' + name + '" was created successfully'
          });
        })
        .catch(function(err) {
          reject({
            status: 400,
            message: err.message
          });
        });
    });
  };

  self.deleteBucket = function(name) {
    if (!name) {
      return new Promise(function(resolve, reject) {
        reject({
          status: 400,
          message: "Invalid parameters"
        });
      });
    }

    return new Promise(function(resolve, reject) {
      getBucketByName(name)
        .then(function(response) {
          if (response.status !== 200) {
            reject({
              status: 400,
              message: response.message
            });
          }

          var bucket = response.data;

          axios
            .post(
              self.credentials.apiUrl + "/b2api/v1/b2_delete_bucket",
              {
                accountId: self.credentials.accountId,
                bucketId: bucket.bucketId
              },
              {
                headers: {
                  Authorization: self.credentials.authorizationToken
                }
              }
            )
            .then(function(response) {
              resolve({
                status: 200,
                message: 'Bucket "' + name + '" was deleted successfully'
              });
            })
            .catch(function(err) {
              reject({
                status: 400,
                message: err.message
              });
            });
        })
        .catch(function(err) {
          reject({
            status: 400,
            message: err.message
          });
        });
    });
  };

  self.uploadFile = function(bucketName, filePath) {
    var shouldSimulateBigFileUploadError =
      arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;

    return new Promise(function(resolve, reject) {
      if (!bucketName || !filePath) {
        reject({
          status: 400,
          message: "Invalid parameters"
        });
      }

      getBucketByName(bucketName)
        .then(function(response) {
          if (response.status !== 200) {
            reject({
              status: 400,
              message: response.message
            });
          }

          var bucket = response.data;
          getUploadUrl(bucket.bucketId)
            .then(function(response) {
              if (response.status !== 200) {
                reject({
                  status: 400,
                  message: response.message
                });
              }

              var uploadUrl = response.data.uploadUrl;
              var uploadAuthorizationToken = response.data.authorizationToken;

              var fileName = path.basename(filePath);
              var fileSize = fs.statSync(filePath).size;
              if (fileSize <= self.credentials.recommendedPartSize) {
                var source = fs.readFileSync(filePath);
                var sha1 = crypto
                  .createHash("sha1")
                  .update(source)
                  .digest("hex");

                uploadSmallFile(
                  source,
                  fileName,
                  filePath,
                  fileSize,
                  sha1,
                  uploadUrl,
                  uploadAuthorizationToken,
                  bucketName
                )
                  .then(function(response) {
                    resolve(response);
                  })
                  .catch(function(err) {
                    reject(err);
                  });
              } else {
                var eachPartSize = Math.floor(
                  self.credentials.recommendedPartSize / 10
                );
                var _source = fs.createReadStream(filePath, {
                  highWaterMark: eachPartSize
                });
                uploadBigFile(
                  _source,
                  fileName,
                  filePath,
                  fileSize,
                  eachPartSize,
                  bucket.bucketId,
                  bucketName,
                  shouldSimulateBigFileUploadError
                )
                  .then(function(response) {
                    resolve(response);
                  })
                  .catch(function(err) {
                    reject(err);
                  });
              }
            })
            .catch(function(err) {
              reject({
                status: 400,
                message: err.message
              });
            });
        })
        .catch(function(err) {
          reject({
            status: 400,
            message: err.message
          });
        });
    });
  };

  self.downloadFile = function(bucketName, filename, saveToPath) {
    if (!bucketName || !filename) {
      return new Promise(function(resolve, reject) {
        reject({
          status: 400,
          message: "Invalid parameters"
        });
      });
    }

    return new Promise(function(resolve, reject) {
      axios
        .get(
          self.credentials.downloadUrl + "/file/" + bucketName + "/" + filename,
          { headers: { Authorization: self.credentials.authorizationToken } }
        )
        .then(function(response) {
          var source = new Readable();
          source._read = function noop() {}; // redundant? see update below
          source.push(response.data);
          source.push(null);

          var destination = fs.createWriteStream(saveToPath);

          source.on("end", function() {
            resolve({
              status: 200,
              message:
                'File "' +
                filename +
                '" was downloaded successfully from bucket "' +
                bucketName +
                '"'
            });
          });
          source.pipe(destination);
        })
        .catch(function(err) {
          reject({
            status: 400,
            message: err.message
          });
        });
    });
  };

  self.createFileFromText = function(bucketName, filename, object) {
    var shouldSimulateFileCorruption =
      arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;

    if (!bucketName || !filename || !object) {
      return new Promise(function(resolve, reject) {
        reject({
          status: 400,
          message: "Invalid parameters"
        });
      });
    }

    return new Promise(function(resolve, reject) {
      getBucketByName(bucketName)
        .then(function(response) {
          if (response.status !== 200) {
            reject({
              status: 400,
              message: response.message
            });
          }

          var bucket = response.data;
          getUploadUrl(bucket.bucketId)
            .then(function(response) {
              if (response.status !== 200) {
                reject({
                  status: 400,
                  message: response.message
                });
              }

              var stringifiedObject = JSON.stringify(object);

              var source = new Readable();
              source._read = function noop() {}; // redundant? see update below
              source.push(stringifiedObject);
              source.push(null);

              var uploadUrl = response.data.uploadUrl;
              var uploadAuthorizationToken = response.data.authorizationToken;
              var fileSize = Buffer.byteLength(stringifiedObject, "utf8");
              var hash = crypto.createHash("sha1");

              hash.setEncoding("hex");
              hash.write(stringifiedObject);
              hash.end();

              uploadSmallFile(
                source,
                filename,
                filename,
                fileSize,
                hash.read(),
                uploadUrl,
                uploadAuthorizationToken,
                bucketName
              )
                .then(function(response) {
                  // Check whether file was properly uploaded
                  var localFileCrc32 = crc32.calculate(stringifiedObject);
                  self.readFile(bucketName, filename).then(function(response) {
                    var remoteFileCrc32 = void 0;
                    var data = JSON.stringify(response.data);
                    if (shouldSimulateFileCorruption) {
                      data += "corrupted-sufix";
                    }
                    remoteFileCrc32 = crc32.calculate(data);

                    if (localFileCrc32 === remoteFileCrc32) {
                      resolve({
                        status: 200,
                        message:
                          'Object "' +
                          filename +
                          '" was saved successfully in bucket "' +
                          bucketName +
                          '"'
                      });
                    } else {
                      reject({
                        status: 400,
                        message:
                          'CRC32 validation error. Object "' +
                          filename +
                          '" could not be uploaded successfully'
                      });
                    }
                  });
                })
                .catch(function(err) {
                  reject({
                    status: 400,
                    message: err.message
                  });
                });
            })
            .catch(function(err) {
              reject({
                status: 400,
                message: err.message
              });
            });
        })
        .catch(function(err) {
          reject({
            status: 400,
            message: err.message
          });
        });
    });
  };

  self.getListOfFiles = function(bucketName) {
    if (!bucketName) {
      return new Promise(function(resolve, reject) {
        reject({
          status: 400,
          message: "Invalid parameters"
        });
      });
    }

    return new Promise(function(resolve, reject) {
      getBucketByName(bucketName)
        .then(function(response) {
          if (response.status !== 200) {
            reject({
              status: 400,
              message: response.message
            });
          }

          var bucket = response.data;
          axios
            .post(
              self.credentials.apiUrl + "/b2api/v1/b2_list_file_names",
              {
                bucketId: bucket.bucketId
              },
              {
                headers: {
                  Authorization: self.credentials.authorizationToken
                }
              }
            )
            .then(function(response) {
              var files = response.data.files;
              var returnedFiles = [];
              files.forEach(function(element) {
                returnedFiles.push({
                  filename: element.fileName
                });
              });
              resolve({
                status: 200,
                data: returnedFiles,
                message:
                  'The list of objects was fetched successfully from bucket "' +
                  bucketName +
                  '"'
              });
            })
            .catch(function(err) {
              reject({
                status: 400,
                message: err.message
              });
            });
        })
        .catch(function(err) {
          reject({
            status: 400,
            message: err.message
          });
        });
    });
  };

  self.deleteFile = function(bucketName, filename) {
    if (!bucketName || !filename) {
      return new Promise(function(resolve, reject) {
        reject({
          status: 400,
          message: "Invalid parameters"
        });
      });
    }

    return new Promise(function(resolve, reject) {
      getFileByName(bucketName, filename)
        .then(function(response) {
          if (response.status !== 200) {
            reject({
              status: 400,
              message: response.message
            });
          }
          var file = response.data;
          axios
            .post(
              self.credentials.apiUrl + "/b2api/v1/b2_delete_file_version",
              {
                fileName: filename,
                fileId: file.fileId
              },
              {
                headers: {
                  Authorization: self.credentials.authorizationToken
                }
              }
            )
            .then(function(response) {
              var files = response.data.files;
              resolve({
                status: 200,
                message:
                  'Object "' +
                  filename +
                  '" was deleted successfully from bucket "' +
                  bucketName +
                  '"'
              });
            })
            .catch(function(err) {
              reject({
                status: 400,
                message: err.message
              });
            });
        })
        .catch(function(err) {
          reject({
            status: 400,
            message: err.message
          });
        });
    });
  };

  self.readFile = function(bucketName, filename) {
    if (!bucketName || !filename) {
      return new Promise(function(resolve, reject) {
        reject({
          status: 400,
          message: "Invalid parameters"
        });
      });
    }

    return new Promise(function(resolve, reject) {
      axios
        .get(
          self.credentials.downloadUrl + "/file/" + bucketName + "/" + filename,
          { headers: { Authorization: self.credentials.authorizationToken } }
        )
        .then(function(response) {
          resolve({
            status: 200,
            data: response.data,
            message:
              'Object "' +
              filename +
              '" was fetched successfully from bucket "' +
              bucketName +
              '"'
          });
        })
        .catch(function(err) {
          reject({
            status: 400,
            message: err.message
          });
        });
    });
  };
};

module.exports = {
  getB2: getB2,
  B2Wrapper: B2Wrapper
};
