'use strict';

const config = require('config');
const fs = require('fs').promises;
const logger = require('simple-node-logger').createSimpleLogger();
const { argv } = require('yargs');
// const timers = require('timers/promises');

const style = require('./utils/style');

logger.setLevel(argv.level || 'info');

const points = {};

async function initPoints(identifier) {
	try {
		const pointsFile = await fs.readFile(`./points-${identifier}.json`, 'utf-8');

		Object.assign(points, JSON.parse(pointsFile));
	} catch (error) {
		if (error.code === 'ENOENT') {
			logger.info('Creating new points file');

			await fs.writeFile(`./points-${identifier}.json`, '{}');
			await initPoints(identifier);
		}
	}
}

async function setPoints(identifier, defaultKey, user, value, { mode = 'add', key } = {}) {
	const gameKey = key || defaultKey;

	if (!user) {
		logger.warn(`Failed to set ${gameKey} points for missing user`);
		return;
	}

	const userKey = `${user.id}:${user.username}`;

	if (!points[gameKey]) {
		points[gameKey] = {};
	}

	if (mode === 'add') {
		points[gameKey][userKey] = (points[gameKey][userKey] || 0) + value;
	}

	if (mode === 'set') {
		points[gameKey][userKey] = value;
	}

	await fs.writeFile(`./points-${identifier}.json`, JSON.stringify(points, null, 4));
}

function getPoints(game, rawUsername, { user, room, command }) {
	const username = rawUsername?.replace(new RegExp(`^${config.usernamePrefix}`), '');
	const gamePoints = points[command] || points[game.key];

	const userPoints = username
		? Object.entries(gamePoints || {}).find(([identifier]) => identifier.split(':')[1] === username)?.[1]
		: gamePoints?.[`${user?.id}:${user?.username}`];

	game.sendMessage(`${username ? `${style.bold(username)} has` : 'You have'} scored ${style.bold(userPoints || 0)} points in ${game.name}, ${config.usernamePrefix}${user.username}`, room.id);
}

function getLeaderboard(game, { user, room, command }) {
	const leaderboard = points[command] || points[game.key];

	if (!leaderboard || Object.keys(leaderboard).length === 0) {
		game.sendMessage(`No points scored in ${game.name} yet!`, room.id);

		return;
	}

	const curatedLeaderboard = Object.entries(leaderboard)
		.sort(([, scoreA], [, scoreB]) => scoreB - scoreA)
		.map(([userKey, score]) => {
			const username = userKey.split(':')[1];
			return `${style.bold(`${username === user.username ? config.usernamePrefix : ''}${username}`)} at ${style.bold(score)} points`;
		})
		.slice(0, 10)
		.join(', ');

	game.sendMessage(`The top ${Math.min(Object.keys(leaderboard).length, 10)} ${style.italic(game.name)} players are: ${curatedLeaderboard}`, room.id);
}

/* eslint-disable no-irregular-whitespace */
function styleCommands(rawBody) {
	const styledCommands = rawBody.replaceAll(new RegExp(`${config.prefix}\\w+`, 'g'), (match) => (match.includes('game') ? `${style.cyan(config.prefix)}${style.italic(style.grey(match.slice(1)))}` : style.cyan(match)));
	const styledSubcommands = styledCommands.replaceAll(/:\w+/g, (match) => style.green(match));

	return styledSubcommands.replaceAll(/(?<!^)\[[\w\d|-]+\]/g, (match) => {
		// using a zero-width space so numerical values don't mess up the color code
		const value = match.slice(1, -1).replace(/[|-]/, (delimiter) => `​${style.grey(delimiter)}​`);

		return `${style.grey('[')}​${value}​${style.grey(']')}`;
	});
}
/* eslint-enable no-irregular-whitespace */

function curateMessageBody(rawBody, game, key, options) {
	const body = options?.styleCommands ? styleCommands(rawBody) : rawBody;

	if (options?.label === false || config.labels === false) {
		return body;
	}

	return `${style.grey(`[${game.name || key}]`)} ${body}`;
}

async function getGames(bot, identifier) {
	await initPoints(identifier);

	const games = config.games.reduce((acc, key) => {
		const game = require(`./games/${key.game || key}`); // eslint-disable-line global-require, import/no-dynamic-require

		const sendMessage = (body, roomId, options, recipient) => {
			const curatedBody = curateMessageBody(body, game, key, options);

			if (config.platform === 'irc') {
				bot.client.say(roomId || recipient, curatedBody);
			}

			if (config.platform === 'schat') {
				bot.socket.transmit('message', {
					roomId,
					recipient,
					type: recipient && options.type !== 'message' ? 'whisper' : 'message',
					body: curatedBody,
					style: config.style,
				});
			}
		};

		const setGamePoints = (userId, score, options) => setPoints(identifier, key, userId, score, options);

		const curatedGame = {
			...game,
			...(key.game && key),
			name: game.name || key,
			key,
			sendMessage,
			setPoints: setGamePoints,
		};

		if (game.onStart) {
			game.onStart({ ...curatedGame, bot });
		}

		return {
			...acc,
			[key]: curatedGame,
			...game.commands?.reduce((commandAcc, command) => ({ ...commandAcc, [command]: curatedGame }), {}),
		};
	}, {});

	return games;
}

function getMessageUser(message, bot) {
	if (config.platform === 'irc') {
		return {
			username: message.from,
			id: message.from,
			prefixedUsername: `${config.usernamePrefix}${message.from}`,
		};
	}

	if (config.platform === 'schat') {
		const user = bot.users[message.userId] || message.user;

		if (user) {
			return {
				...user,
				prefixedUsername: `${config.usernamePrefix}${user.username}`,
			};
		}
	}

	return null;
}

function getMessageRoom(message, bot) {
	if (config.platform === 'irc') {
		return {
			id: message.to,
			name: message.to,
		};
	}

	if (config.platform === 'schat') {
		return bot.rooms[message.roomId];
	}

	return null;
}

function onMessage(message, bot, games) {
	const body = message.originalBody || message.body;
	const [, command, subcommand] = body?.match(new RegExp(`^${config.prefix}(\\w+)(?:\\:(\\w+))?`)) || [];
	const user = getMessageUser(message, bot);
	const room = getMessageRoom(message, bot);

	if (command) {
		const args = body.split(/\s+/).slice(1);
		const game = games[command];

		if (['leaderboard', 'lead', 'leader', 'leaders', 'scoreboard', 'best'].includes(subcommand) && games[command]) {
			getLeaderboard(games[command], { user, room, command });
			return;
		}

		if (['points', 'score'].includes(subcommand) && games[command]) {
			getPoints(games[command], args[0], { user, room, command });
			return;
		}

		if (game && game.help && ['help', 'commands'].includes(subcommand)) {
			game.sendMessage(game.help, room.id, { styleCommands: true });

			if (game.blockHelp !== false) {
				return;
			}
		}

		if (game && game.onCommand) {
			if (user) {
				user.points = points[game.key]?.[`${user.id}:${user.username}`] || 0;
			}

			game.onCommand(args, {
				...game,
				command,
				subcommand,
				bot,
				message,
				user,
				room,
				points: points[game.key] || {},
				games,
				logger,
			});
		}
	}

	Object.values(games).forEach((game) => game.onMessage?.(message, {
		...game,
		bot,
		message,
		user: user && {
			...user,
			points: points[game.key]?.[`${user.id}:${user.username}`] || 0,
		},
		room,
		logger,
	}));
}

module.exports = {
	onMessage,
	getGames,
	getLeaderboard,
	getPoints,
	setPoints,
	initPoints,
};
