'use strict';

const path = require('path');
const nconf = require('nconf');
const winston = require('winston');
const crypto = require('crypto');

const db = require('../database');
const posts = require('../posts');
const file = require('../file');
const batch = require('../batch');

const md5 = filename => crypto.createHash('md5').update(filename).digest('hex');

const pathPrefix = path.join(nconf.get('upload_path'));

const _getFullPath = relativePath => path.join(pathPrefix, relativePath);

const _validatePath = async (relativePaths) => {
	if (typeof relativePaths === 'string') {
		relativePaths = [relativePaths];
	} else if (!Array.isArray(relativePaths)) {
		throw new Error(`[[error:wrong-parameter-type, relativePaths, ${typeof relativePaths}, array]]`);
	}

	const fullPaths = relativePaths.map(path => _getFullPath(path));
	const exists = await Promise.all(fullPaths.map(async fullPath => file.exists(fullPath)));

	if (!fullPaths.every(fullPath => fullPath.startsWith(nconf.get('upload_path'))) || !exists.every(Boolean)) {
		throw new Error('[[error:invalid-path]]');
	}
};

module.exports = function (User) {
	User.associateUpload = async (uid, relativePath) => {
		await _validatePath(relativePath);
		await Promise.all([
			db.sortedSetAdd(`uid:${uid}:uploads`, Date.now(), relativePath),
			db.setObjectField(`upload:${md5(relativePath)}`, 'uid', uid),
		]);
	};

	User.deleteUpload = async function (callerUid, uid, uploadNames) {
		if (typeof uploadNames === 'string') {
			uploadNames = [uploadNames];
		} else if (!Array.isArray(uploadNames)) {
			throw new Error(`[[error:wrong-parameter-type, uploadNames, ${typeof uploadNames}, array]]`);
		}

		await _validatePath(uploadNames);

		const [isUsersUpload, isAdminOrGlobalMod] = await Promise.all([
			db.isSortedSetMembers(`uid:${callerUid}:uploads`, uploadNames),
			User.isAdminOrGlobalMod(callerUid),
		]);
		if (!isAdminOrGlobalMod && !isUsersUpload.every(Boolean)) {
			throw new Error('[[error:no-privileges]]');
		}

		await batch.processArray(uploadNames, async (uploadNames) => {
			const fullPaths = uploadNames.map(path => _getFullPath(path));

			await Promise.all(fullPaths.map(async (fullPath, idx) => {
				winston.verbose(`[user/deleteUpload] Deleting ${uploadNames[idx]}`);
				await Promise.all([
					file.delete(fullPath),
					file.delete(file.appendToFileName(fullPath, '-resized')),
				]);
				await Promise.all([
					db.sortedSetRemove(`uid:${uid}:uploads`, uploadNames[idx]),
					db.delete(`upload:${md5(uploadNames[idx])}`),
				]);
			}));

			// Dissociate the upload from pids, if any
			const pids = await db.getSortedSetsMembers(uploadNames.map(relativePath => `upload:${md5(relativePath)}:pids`));
			await Promise.all(pids.map(async (pids, idx) => Promise.all(
				pids.map(async pid => posts.uploads.dissociate(pid, uploadNames[idx]))
			)));
		}, { batch: 50 });
	};

	User.collateUploads = async function (uid, archive) {
		await batch.processSortedSet(`uid:${uid}:uploads`, (files, next) => {
			files.forEach((file) => {
				archive.file(_getFullPath(file), {
					name: path.basename(file),
				});
			});

			setImmediate(next);
		}, { batch: 100 });
	};
};
