'use strict';

const winston = require('winston');
const path = require('path');
const fs = require('fs');
const nconf = require('nconf');
const os = require('os');
const cproc = require('child_process');
const util = require('util');
const request = require('request-promise-native');

const db = require('../database');
const meta = require('../meta');
const pubsub = require('../pubsub');
const { paths } = require('../constants');

const supportedPackageManagerList = require('../cli/package-install').supportedPackageManager; // load config from src/cli/package-install.js
const packageManager = supportedPackageManagerList.indexOf(nconf.get('package_manager')) >= 0 ? nconf.get('package_manager') : 'npm';
let packageManagerExecutable = packageManager;
const packageManagerCommands = {
	yarn: {
		install: 'add',
		uninstall: 'remove',
	},
	npm: {
		install: 'install',
		uninstall: 'uninstall',
	},
	cnpm: {
		install: 'install',
		uninstall: 'uninstall',
	},
	pnpm: {
		install: 'install',
		uninstall: 'uninstall',
	},
};

if (process.platform === 'win32') {
	packageManagerExecutable += '.cmd';
}

module.exports = function (Plugins) {
	if (nconf.get('isPrimary')) {
		pubsub.on('plugins:toggleInstall', function (data) {
			if (data.hostname !== os.hostname()) {
				toggleInstall(data.id, data.version);
			}
		});

		pubsub.on('plugins:upgrade', function (data) {
			if (data.hostname !== os.hostname()) {
				upgrade(data.id, data.version);
			}
		});
	}

	Plugins.toggleActive = async function (id) {
		const isActive = await Plugins.isActive(id);
		if (isActive) {
			await db.sortedSetRemove('plugins:active', id);
		} else {
			const count = await db.sortedSetCard('plugins:active');
			await db.sortedSetAdd('plugins:active', count, id);
		}
		meta.reloadRequired = true;
		Plugins.hooks.fire(isActive ? 'action:plugin.deactivate' : 'action:plugin.activate', { id: id });
		return { id: id, active: !isActive };
	};

	Plugins.checkWhitelist = async function (id, version) {
		const body = await request({
			method: 'GET',
			url: `https://packages.nodebb.org/api/v1/plugins/${encodeURIComponent(id)}`,
			json: true,
		});

		if (body && body.code === 'ok' && (version === 'latest' || body.payload.valid.includes(version))) {
			return;
		}

		throw new Error('[[error:plugin-not-whitelisted]]');
	};

	Plugins.toggleInstall = async function (id, version) {
		pubsub.publish('plugins:toggleInstall', { hostname: os.hostname(), id: id, version: version });
		return await toggleInstall(id, version);
	};

	const runPackageManagerCommandAsync = util.promisify(runPackageManagerCommand);

	async function toggleInstall(id, version) {
		const [installed, active] = await Promise.all([
			Plugins.isInstalled(id),
			Plugins.isActive(id),
		]);
		const type = installed ? 'uninstall' : 'install';
		if (active) {
			await Plugins.toggleActive(id);
		}
		await runPackageManagerCommandAsync(type, id, version || 'latest');
		const pluginData = await Plugins.get(id);
		Plugins.hooks.fire('action:plugin.' + type, { id: id, version: version });
		return pluginData;
	}

	function runPackageManagerCommand(command, pkgName, version, callback) {
		cproc.execFile(packageManagerExecutable, [
			packageManagerCommands[packageManager][command],
			pkgName + (command === 'install' ? '@' + version : ''),
			'--save',
		], function (err, stdout) {
			if (err) {
				return callback(err);
			}

			winston.verbose('[plugins/' + command + '] ' + stdout);
			callback();
		});
	}


	Plugins.upgrade = async function (id, version) {
		pubsub.publish('plugins:upgrade', { hostname: os.hostname(), id: id, version: version });
		return await upgrade(id, version);
	};

	async function upgrade(id, version) {
		await runPackageManagerCommandAsync('install', id, version || 'latest');
		const isActive = await Plugins.isActive(id);
		meta.reloadRequired = isActive;
		return isActive;
	}

	Plugins.isInstalled = async function (id) {
		const pluginDir = path.join(paths.nodeModules, id);
		try {
			const stats = await fs.promises.stat(pluginDir);
			return stats.isDirectory();
		} catch (err) {
			return false;
		}
	};

	Plugins.isActive = async function (id) {
		return await db.isSortedSetMember('plugins:active', id);
	};

	Plugins.getActive = async function () {
		return await db.getSortedSetRange('plugins:active', 0, -1);
	};
};
