const fs = require('fs-extra');
const path = require('path');
const UserError = require('vn-loopback/util/user-error');

module.exports = Self => {
    Self.remoteMethod('scrub', {
        description: 'Deletes images without database reference',
        accessType: 'WRITE',
        accepts: [
            {
                arg: 'collection',
                type: 'string',
                description: 'The collection name',
                required: true
            }, {
                arg: 'remove',
                type: 'boolean',
                description: 'Delete instead of move images to trash'
            }, {
                arg: 'limit',
                type: 'integer',
                description: 'Maximum number of images to clean'
            }, {
                arg: 'dryRun',
                type: 'boolean',
                description: 'Simulate actions'
            }, {
                arg: 'skipLock',
                type: 'boolean',
                description: 'Wether to skip exclusive lock'
            }
        ],
        returns: {
            type: 'integer',
            root: true
        },
        http: {
            path: `/scrub`,
            verb: 'POST'
        }
    });

    Self.scrub = async function(collection, remove, limit, dryRun, skipLock) {
        const $ = Self.app.models;

        const env = process.env.NODE_ENV;
        dryRun = dryRun || (env && env !== 'production');

        const instance = await $.ImageCollection.findOne({
            fields: ['id'],
            where: {name: collection}
        });
        if (!instance)
            throw new UserError('Collection does not exist');

        const container = await $.ImageContainer.container(collection);
        const rootPath = container.client.root;

        let tx;
        let opts;
        const lockName = 'salix.Image.scrub';

        if (!skipLock) {
            tx = await Self.beginTransaction({timeout: null});
            opts = {transaction: tx};

            const [row] = await Self.rawSql(
                `SELECT GET_LOCK(?, 10) hasLock`, [lockName], opts);
            if (!row.hasLock)
                throw new UserError('Cannot obtain exclusive lock');
        }

        try {
            const now = Date.vnNew().toJSON();
            const scrubDir = path.join(rootPath, '.scrub', now);

            const collectionDir = path.join(rootPath, collection);
            const sizes = await fs.readdir(collectionDir);
            let cleanCount = 0;

            mainLoop: for (const size of sizes) {
                const sizeDir = path.join(collectionDir, size);
                const scrubSizeDir = path.join(scrubDir, collection, size);
                const images = await fs.readdir(sizeDir);
                for (const image of images) {
                    const imageName = path.parse(image).name;
                    const count = await Self.count({
                        collectionFk: collection,
                        name: imageName
                    }, opts);
                    const exists = count > 0;
                    let scrubDirCreated = false;
                    if (!exists) {
                        const srcFile = path.join(sizeDir, image);
                        if (remove !== true) {
                            if (!scrubDirCreated) {
                                if (!dryRun)
                                    await fs.mkdir(scrubSizeDir, {recursive: true});
                                scrubDirCreated = true;
                            }
                            const dstFile = path.join(scrubSizeDir, image);
                            if (!dryRun) await fs.rename(srcFile, dstFile);
                        } else {
                            try {
                                if (!dryRun) await fs.unlink(srcFile);
                            } catch (err) {
                                console.error(err.message);
                            }
                        }

                        cleanCount++;
                        if (limit && cleanCount == limit)
                            break mainLoop;
                    }
                }
            }

            return cleanCount;
        } finally {
            if (!skipLock) {
                try {
                    await Self.rawSql(`DO RELEASE_LOCK(?)`, [lockName], opts);
                    await tx.rollback();
                } catch (err) {
                    console.error(err.message);
                }
            }
        }
    };
};