process.env.NODE_ENV = 'test';

const chai = require('chai');
chai.use(require('chai-http'));

const ModelPath = '../app/models/';
const User = require(ModelPath + 'User.js');
const Upload = require(ModelPath + 'Upload.js');
const Key = require(ModelPath + 'Key.js');
const Invite = require(ModelPath + 'Invite.js');
const View = require(ModelPath + 'View.js');

const Buffer = require('buffer').Buffer;
const crypto = require('crypto');
const fs = require('fs');
const fsPromises = fs.promises;
const path = require('path');

//---------------- RESPONSE VERIFICATION ----------------//

exports.verifyResponse = (res, status, message) => {
    res.should.have.status(status);
    res.body.should.be.a('object');
    res.body.should.have.property('message').equal(message);
};

exports.verifyResponseObj = (res, status, obj) => {
    res.should.have.status(status);
    res.body.should.deep.equal(obj);
};

//---------------- DATABASE UTIL ----------------//

exports.clearDatabase = () =>
    Promise.all([
        User.deleteMany(),
        Invite.deleteMany(),
        Key.deleteMany(),
        Upload.deleteMany(),
        View.deleteMany()
    ]);

exports.insertInvite = invite =>
    Invite.create(invite);

exports.insertKey = key =>
    Key.create(key);

exports.insertUpload = upload =>
    Upload.create(upload);

exports.insertView = view =>
    View.create(view);

exports.setBanned = (username, banned) =>
    User.updateOne({username: username}, {banned: banned});

//---------------- API ROUTES ----------------//

exports.login = (credentials, agent) =>
    agent.post('/api/auth/login')
        .send(credentials);

exports.logout = agent =>
    agent.post('/api/auth/logout');

exports.registerUser = (user, agent) =>
    agent.post('/api/auth/register')
        .send(user);

exports.whoami = (agent, key) =>
    agent.get('/api/auth/whoami')
        .send({key: key});

//---------------- TEST ENTRY CREATION ----------------//

exports.createTestInvite = () =>
    exports.insertInvite({code: 'code', scope: ['file.upload'], issuer: 'Mocha'});

exports.createTestInvites = (n) =>
    Promise.all(
        Array.from(new Array(n), (val, index) => 'code' + index)
            .map(code => exports.insertInvite({code: code, scope: ['file.upload'], issuer: 'Mocha'}))
    );

exports.createTestUser = async agent => {
    await exports.createTestInvite();
    return exports.registerUser({displayname: 'user', password: 'pass', invite: 'code'}, agent);
};

exports.createTestSession = async agent => {
    await exports.createTestUser(agent);
    return exports.login({displayname: 'user', password: 'pass'}, agent);
};

exports.createSession = async (agent, scope, displayname) => {
    await exports.insertInvite({code: 'code', scope: scope, issuer: 'Mocha'});
    await exports.registerUser({displayname: displayname ? displayname : 'user', password: 'pass', invite: 'code'}, agent);
    await exports.login({displayname: displayname ? displayname : 'user', password: 'pass'}, agent);
    await Invite.deleteOne({code: 'code'});
};

exports.createTestFile = (size, name) =>
    fsPromises.writeFile(name, Buffer.allocUnsafe(size));

exports.createTestKey = scope =>
    exports.insertKey({key: 'key', identifier: 'test', scope: scope, issuer: 'Mocha'});

//---------------- FILESYSTEM ----------------//

exports.deleteFile = file =>
    fsPromises.unlink(file);

exports.fileExists = file =>
    fsPromises.access(file, fs.constants.R_OK);

exports.fileSize = async file =>
    (await fsPromises.stat(file)).size;

exports.fileHash = file =>
    new Promise((resolve, reject) => {
        const hash = crypto.createHash('MD5');
        fs.createReadStream(file)
            .on('error', reject)
            .on('data', chunk => hash.update(chunk))
            .on('end', () => resolve(hash.digest('hex')));
    });

exports.bufferHash = buffer =>
    crypto.createHash('MD5')
        .update(buffer)
        .digest('hex');

exports.directoryFileCount = async dir =>
    (await fsPromises.readdir(dir)).length;

exports.clearDirectory = async dir => {
    const files = await fsPromises.readdir(dir);
    const promises = files.map(file => fsPromises.unlink(path.join(dir, file)));
    return Promise.all(promises);
};

//---------------- UPLOADS ----------------//

exports.upload = (file, agent, key) => {
    const request = agent.post('/api/upload');

    if (key)
        request.field('key', key);

    return request.attach('file', file);
};

//---------------- Invites ----------------//

exports.createInvite = (invite, agent) =>
    agent.post('/api/invites/create')
        .send(invite);

exports.deleteInvite = (code, agent) =>
    agent.post('/api/invites/delete')
        .send({code: code});

exports.getInvites = (query, agent) =>
    agent.get('/api/invites/get')
        .send(query);

//---------------- Keys ----------------//

exports.createKey = (key, agent) =>
    agent.post('/api/keys/create')
        .send(key);

exports.deleteKey = (key, agent) =>
    agent.post('/api/keys/delete')
        .send({key: key});

exports.getKeys = (query, agent) =>
    agent.get('/api/keys/get')
        .send(query);

//---------------- Viewing ----------------//

exports.binaryFileParser = (res, cb) => {
    res.setEncoding('binary');
    res.data = '';
    res.on("data", function (chunk) {
        res.data += chunk;
    });
    res.on('end', function () {
        cb(null, Buffer.from(res.data, 'binary'));
    });
};

exports.view = (id, agent) =>
    agent.get('/v/' + id)
        .buffer();

//---------------- Users ----------------//

exports.getUsers = (query, agent) =>
    agent.get('/api/users/get')
        .send(query);

exports.ban = (username, agent) =>
    agent.post('/api/users/ban')
        .send({username: username});

exports.unban = (username, agent) =>
    agent.post('/api/users/unban')
        .send({username: username});

//---------------- Stats ----------------//

exports.getStatsWeek = agent =>
    agent.get('/api/stats/week');

exports.getStatsAll = agent =>
    agent.get('/api/stats/all');

