'use strict';

const GridFSBucket = require('mongodb').GridFSBucket;
const libbase64 = require('libbase64');
const libmime = require('libmime');

class StorageHandler {
    constructor(options) {
        this.database = options.database;
        this.gridfs = options.gridfs || options.database;
        this.users = options.users || options.database;
        this.bucketName = 'storage';

        this.gridstore = new GridFSBucket(this.gridfs, {
            bucketName: this.bucketName,
            chunkSizeBytes: 255 * 1024
        });
    }

    async add(user, options) {
        let filename = options.filename;
        let contentType = options.contentType;

        let filebase = 'upload-' + new Date().toISOString().substr(0, 10);
        if (!contentType && !filename) {
            filename = filebase + '.bin';
            contentType = 'application/octet-stream';
        } else if (!contentType) {
            contentType = libmime.detectMimeType(filename) || 'application/octet-stream';
        } else if (!filename) {
            filename = filebase + '.' + libmime.detectExtension(contentType);
        }

        return new Promise((resolve, reject) => {
            let store = this.gridstore.openUploadStream(filename, {
                contentType,
                metadata: {
                    user
                }
            });

            store.on('error', err => {
                reject(err);
            });

            store.once('finish', () => {
                resolve(store.id);
            });

            if (!options.encoding) {
                // content is not encoded, pass on as is
                try {
                    store.end(options.content);
                } catch (err) {
                    reject(err);
                }
                return;
            }

            let decoder = new libbase64.Decoder();
            decoder.pipe(store);

            decoder.once('error', err => {
                // pass error forward
                store.emit('error', err);
            });

            try {
                decoder.end(options.content);
            } catch (err) {
                return reject(err);
            }
        });
    }

    async get(user, file) {
        let fileData = await this.gridfs.collection('storage.files').findOne({
            _id: file,
            'metadata.user': user
        });

        if (!fileData) {
            let err = new Error('This file does not exist');
            err.code = 'FileNotFound';
            throw err;
        }

        return new Promise((resolve, reject) => {
            let stream = this.gridstore.openDownloadStream(file);
            let chunks = [];
            let chunklen = 0;

            stream.once('error', err => {
                reject(err);
            });

            stream.on('readable', () => {
                let chunk;
                while ((chunk = stream.read()) !== null) {
                    chunks.push(chunk);
                    chunklen += chunk.length;
                }
            });

            stream.once('end', () => {
                resolve({
                    id: fileData._id.toString(),
                    filename: fileData.filename,
                    contentType: fileData.contentType,
                    size: fileData.length,
                    content: Buffer.concat(chunks, chunklen)
                });
            });
        });
    }

    async delete(user, file) {
        let fileData = await this.gridfs.collection('storage.files').findOne({
            _id: file,
            'metadata.user': user
        });

        if (!fileData) {
            let err = new Error('This file does not exist');
            err.code = 'FileNotFound';
            throw err;
        }

        return this.gridstore.delete(file);
    }
}

module.exports = StorageHandler;
