// GCSAdapter
// Store Parse Files in Google Cloud Storage: https://cloud.google.com/storage
import { storage } from 'gcloud';
import { FilesAdapter } from './FilesAdapter';
import requiredParameter from '../../requiredParameter';

function requiredOrFromEnvironment(env, name) {
  let environmentVariable = process.env[env];
  if (!environmentVariable) {
    requiredParameter(`GCSAdapter requires an ${name}`);
  }
  return environmentVariable;
}

function fromEnvironmentOrDefault(env, defaultValue) {
  let environmentVariable = process.env[env];
  if (environmentVariable) {
    return environmentVariable;
  }
  return defaultValue;
}

export class GCSAdapter extends FilesAdapter {
  // GCS Project ID and the name of a corresponding Keyfile are required.
  // Unlike the S3 adapter, you must create a new Cloud Storage bucket, as this is not created automatically.
  // See https://googlecloudplatform.github.io/gcloud-node/#/docs/master/guides/authentication
  // for more details.
  constructor(
    projectId = requiredOrFromEnvironment('GCP_PROJECT_ID', 'projectId'),
    keyFilename = requiredOrFromEnvironment('GCP_KEYFILE_PATH', 'keyfile path'),
    bucket = requiredOrFromEnvironment('GCS_BUCKET', 'bucket name'),
    { bucketPrefix = fromEnvironmentOrDefault('GCS_BUCKET_PREFIX', ''),
      directAccess = fromEnvironmentOrDefault('GCS_DIRECT_ACCESS', false) } = {}) {
    super();

    this._bucket = bucket;
    this._bucketPrefix = bucketPrefix;
    this._directAccess = directAccess;

    let options = {
      projectId: projectId,
      keyFilename: keyFilename
    };

    this._gcsClient = new storage(options);
  }

  // For a given config object, filename, and data, store a file in GCS.
  // Resolves the promise or fails with an error.
  createFile(config, filename, data, contentType) {
    let params = {
      contentType: contentType || 'application/octet-stream'
    };

    return new Promise((resolve, reject) => {
      let file = this._gcsClient.bucket(this._bucket).file(this._bucketPrefix + filename);
      // gcloud supports upload(file) not upload(bytes), so we need to stream.
      var uploadStream = file.createWriteStream(params);
      uploadStream.on('error', (err) => {
        return reject(err);
      }).on('finish', () => {
        // Second call to set public read ACL after object is uploaded.
        if (this._directAccess) {
          file.makePublic((err, res) => {
            if (err !== null) {
              return reject(err);
            }
            resolve();
          });
        } else {
          resolve();
        }
      });
      uploadStream.write(data);
      uploadStream.end();
    });
  }

  // Deletes a file with the given file name.
  // Returns a promise that succeeds with the delete response, or fails with an error.
  deleteFile(config, filename) {
    return new Promise((resolve, reject) => {
      let file = this._gcsClient.bucket(this._bucket).file(this._bucketPrefix + filename);
      file.delete((err, res) => {
        if(err !== null) {
          return reject(err);
        }
        resolve(res);
      });
    });
  }

  // Search for and return a file if found by filename.
  // Returns a promise that succeeds with the buffer result from GCS, or fails with an error.
  getFileData(config, filename) {
    return new Promise((resolve, reject) => {
      let file = this._gcsClient.bucket(this._bucket).file(this._bucketPrefix + filename);
      // Check for existence, since gcloud-node seemed to be caching the result
      file.exists((err, exists) => {
        if (exists) {
          file.download((err, data) => {
            if (err !== null) {
              return reject(err);
            }
            return resolve(data);
          });
        } else {
          reject(err);
        }
      });
    });
  }

  // Generates and returns the location of a file stored in GCS for the given request and filename.
  // The location is the direct GCS link if the option is set,
  // otherwise we serve the file through parse-server.
  getFileLocation(config, filename) {
    if (this._directAccess) {
      return `https://${this._bucket}.storage.googleapis.com/${this._bucketPrefix + filename}`;
    }
    return (config.mount + '/files/' + config.applicationId + '/' + encodeURIComponent(filename));
  }
}

export default GCSAdapter;
