const http = require('http'),
      https = require('https'),
      fs = require('fs'),
      rimraf = require('rimraf'),
      crypto = require('crypto'),
      { exec } = require('child_process');

/**
 * Returns an appropriate requester depending on the protocol of the url.
 * Supported requester are: http and https
 * @param {String} url
 */
function getRequesterForUrl(url) {
  if (typeof url !== 'string' || url === '') {
    return null;
  }
  // If it is a https request, use the https module.
  else if (url.startsWith('https://')) {
    return https;
  }
  // If it is a http request, use the http module.
  else if (url.startsWith('http://')) {
    return http;
  }
  return null;
}

/**
 * Returns sha-1 for a given file
 * @param {String} filePath
 */
function getChecksumForFile(filePath, callback, options = {}) {
  const { algorithm = 'sha1' } = options;

  if (!callback) {
    throw new Error('InvalidArgumentException: Hermes~util~checksum required parameter \'callback\'');
  }

  if (typeof callback !== 'function') {
    throw new Error('InvalidArgumentException: Hermes~util~checksum required parameter \'callback\' should be of type function');
  }

  if (typeof filePath !== 'string') {
    callback(new Error('InvalidArgumentException: Hermes~util~checksum required parameter \'filePath\' must be a string'));
  }
  if (filePath === '') {
    callback(new Error('InvalidArgumentException: Hermes~util~checksum \'filePath\' cannot be empty'));
  }

  try {
    const stats = fs.statSync(filePath);
    if (!stats.isFile()) {
      return callback(new Error(`InvalidArgumentException: Hermes~util~checksum '${filePath}' is not a file`));
    }
  }
  catch (e) {
    return callback(new Error(`InvalidArgumentException: Hermes~util~checksum File not found '${filePath}'`));
  }

  // Algorithm depends on availability of OpenSSL on platform
  // Another algorithms: 'sha1', 'md5', 'sha256', 'sha512' ...
  const shasum = crypto.createHash(algorithm);
  const s = fs.ReadStream(filePath);
  s.on('data', (data) => {
    shasum.update(data);
  });
  // making digest
  s.on('end', () => {
    const hash = shasum.digest('hex');
    callback(null, hash);
  });
  s.on('error', (error) => {
    callback(error);
  });
}

/**
 * Download a resource from given URL to a destination
 * @param {String} url URL from where to download the resource
 * @param {String} destination Destination where the resource should be downloaded
 * @param {Function} callback Callback that will be called after download is finished
 */
function downloadFile(url, destination, callback) {
  if (typeof callback !== 'function') {
    throw new Error('InvalidArgumentException: Hermes~util~download required parameter \'callback\' must be a function');
  }
  if (typeof url !== 'string') {
    callback(new Error('InvalidArgumentException: Hermes~util~download required parameter \'url\' must be a string'));
    return;
  }
  if (url === '') {
    callback(new Error('InvalidArgumentException: Hermes~util~download \'url\' cannot be empty'));
    return;
  }
  if (typeof destination !== 'string') {
    callback(new Error('InvalidArgumentException: Hermes~util~download required parameter \'destination\' must be a string'));
    return;
  }
  if (destination === '') {
    callback(new Error('InvalidArgumentException: Hermes~util~download \'destination\' cannot be empty'));
    return;
  }

  const requester = getRequesterForUrl(url);
  if (!requester) {
    callback(new Error('InvalidArgumentException: Hermes~util~download Invalid protocol information in URL, only https and http are supported'));
    return;
  }

  requester.get(url, (response) => {
    const statusCode = response && response.statusCode;
    let file = null;

    if (statusCode !== 200) {
      const error = new Error(`InvalidResponseException: Hermes~util~download Expected to get status 200 but received ${statusCode}`);
      callback(error);
      return;
    }

    file = fs.createWriteStream(destination);
    file.on('error', (err) => {
      file.end();
      callback(err);
    });
    response.pipe(file);

    file.on('finish', () => {
      file.close(callback);
    });
  }).on('error', (err) => {
    callback(err);
  });
}

/**
 * Extracts a compressed file
 * @param {String} filePath
 * @param {String} destinationDir
 */
function extract(filePath, destinationDir) {
  return new Promise((resolve, reject) => {
    exec(`tar -xzf ${filePath} -C ${destinationDir}`, { maxBuffer: Infinity }, (err) => {
      if (err) {
        return reject(err);
      }
      return resolve();
    });
  });
}

/**
 * Only delete the files and folders which are specified in pattern
 * @param {String} rootDirectory
 * @param {Array,<String>} patterns
 * @param {Funcrion} callback
 */
function removeOnly(options = {}, callback) {
  const { rootDirectory, patterns, fileSystemModule } = options;
  if (typeof callback !== 'function') {
    throw new Error('InvalidArgumentException: Hermes~util~removeOnly required parameter \'callback\' must be a function');
  }
  if (typeof rootDirectory !== 'string') {
    return callback(new Error('InvalidArgumentException: Hermes~util~removeOnly required parameter \'rootDirectory\' must be a string'));
  }

  if (!patterns) {
    return callback(new Error('InvalidArgumentException: Hermes~util~removeOnly required parameter \'patterns\''));
  }

  if (typeof fileSystemModule !== 'object') {
    return callback(new Error('InvalidArgumentException: Hermes~util~removeOnly required parameter \'fileSystemModule\' must be an object'));
  }

  rimraf(`${rootDirectory}/${patterns}`, fileSystemModule, callback);
}

module.exports = {
  getRequesterForUrl,
  getChecksumForFile,
  downloadFile,
  extract,
  removeOnly,
};
