// crowi-fileupload-gridFS

module.exports = function(crowi) {
  'use strict';

  const debug = require('debug')('growi:service:fileUploaderGridfs');
  const mongoose = require('mongoose');
  const path = require('path');
  const fs = require('fs');
  const lib = {};

  // instantiate mongoose-gridfs
  const gridfs = require('mongoose-gridfs')({
    collection: 'attachmentFiles',
    model: 'AttachmentFile',
    mongooseConnection: mongoose.connection
  });

  // obtain a model
  const AttachmentFile = gridfs.model;
  const Chunks = mongoose.model('Chunks', gridfs.schema, 'attachmentFiles.chunks');

  // delete a file
  lib.deleteFile = async function(fileId, filePath) {
    debug('File deletion: ' + fileId);
    const file = await getFile(filePath);
    const id = file.id;
    AttachmentFile.unlinkById(id, function(error, unlinkedAttachment) {
      if (error) {
        throw new Error(error);
      }
    });
    clearCache(fileId);
  };

  const clearCache = (fileId) => {
    const cacheFile = createCacheFileName(fileId);
    const stats = fs.statSync(crowi.cacheDir);
    if (stats.isFile(`attachment-${fileId}`)) {
      fs.unlink(cacheFile, (err) => {
        if (err) {
          throw new Error('fail to delete cache file', err);
        }
      });
    }
  };

  /**
   * get size of data uploaded files using (Promise wrapper)
   */
  const getCollectionSize = () => {
    return new Promise((resolve, reject) => {
      Chunks.collection.stats((err, data) => {
        if (err) {
          reject(err);
        }
        resolve(data.size);
      });
    });
  };

  /**
   * chech storage for fileUpload reaches MONGO_GRIDFS_TOTAL_LIMIT (for gridfs)
   */
  lib.checkCapacity = async(uploadFileSize) => {
    // skip checking if env var is undefined
    if (process.env.MONGO_GRIDFS_TOTAL_LIMIT == null) {
      return true;
    }

    const usingFilesSize = await getCollectionSize();
    return (+process.env.MONGO_GRIDFS_TOTAL_LIMIT > usingFilesSize + +uploadFileSize);
  };

  lib.uploadFile = async function(filePath, contentType, fileStream, options) {
    debug('File uploading: ' + filePath);
    await writeFile(filePath, contentType, fileStream);
  };

  /**
   * write file to MongoDB with GridFS (Promise wrapper)
   */
  const writeFile = (filePath, contentType, fileStream) => {
    return new Promise((resolve, reject) => {
      AttachmentFile.write({
        filename: filePath,
        contentType: contentType
      }, fileStream,
      function(error, createdFile) {
        if (error) {
          reject(error);
        }
        resolve();
      });
    });
  };

  lib.getFileData = async function(filePath) {
    const file = await getFile(filePath);
    const id = file.id;
    const contentType = file.contentType;
    const data = await readFileData(id);
    return {
      data,
      contentType
    };
  };

  /**
   * get file from MongoDB (Promise wrapper)
   */
  const getFile = (filePath) => {
    return new Promise((resolve, reject) => {
      AttachmentFile.findOne({
        filename: filePath
      }, function(err, file) {
        if (err) {
          reject(err);
        }
        resolve(file);
      });
    });
  };

  /**
   * read File in MongoDB (Promise wrapper)
   */
  const readFileData = (id) => {
    return new Promise((resolve, reject) => {
      let buf;
      const stream = AttachmentFile.readById(id);
      stream.on('error', function(error) {
        reject(error);
      });
      stream.on('data', function(data) {
        if (buf) {
          buf = Buffer.concat([buf, data]);
        }
        else {
          buf = data;
        }
      });
      stream.on('close', function() {
        debug('GridFS readstream closed');
        resolve(buf);
      });
    });
  };

  lib.findDeliveryFile = async function(fileId, filePath) {
    const cacheFile = createCacheFileName(fileId);
    debug('Load attachement file into local cache file', cacheFile);
    const fileStream = fs.createWriteStream(cacheFile);
    const file = await getFile(filePath);
    const id = file.id;
    const buf = await readFileData(id);
    await writeCacheFile(fileStream, buf);
    return cacheFile;
  };

  const createCacheFileName = (fileId) => {
    return path.join(crowi.cacheDir, `attachment-${fileId}`);
  };

  /**
   * write cache file (Promise wrapper)
   */
  const writeCacheFile = (fileStream, data) => {
    return new Promise((resolve, reject) => {
      fileStream.write(data);
      resolve();
    });
  };

  lib.generateUrl = function(filePath) {
    return `/${filePath}`;
  };

  return lib;
};
