const Command = require('../../structures/Command');
const { Collection } = require('discord.js');
const { stripIndents } = require('common-tags');
const { shuffle, wait, awaitPlayers } = require('../../util/Util');
const { questions, stories } = require('../../assets/json/mafia');

module.exports = class MafiaCommand extends Command {
	constructor(client) {
		super(client, {
			name: 'mafia',
			aliases: ['town-of-salem', 'werewolf'],
			group: 'games',
			memberName: 'mafia',
			description: 'Who is the Mafia? Who is the doctor? Who is the detective? Will the Mafia kill them all?',
			guildOnly: true
		});

		this.playing = new Set();
	}

	async run(msg) { // eslint-disable-line complexity
		if (this.playing.has(msg.channel.id)) return msg.reply('Only one game may be occurring per channel.');
		this.playing.add(msg.channel.id);
		try {
			await msg.say('You will need at least 2 more players, at maximum 10. To join, type `join game`.');
			const awaitedPlayers = await awaitPlayers(msg, 10, 3, { dmCheck: true });
			if (!awaitedPlayers) {
				this.playing.delete(msg.channel.id);
				return msg.say('Game could not be started...');
			}
			const players = await this.generatePlayers(awaitedPlayers);
			let turn = 1;
			while (players.size > 2 && players.some(p => p.role === 'mafia')) {
				let killed = null;
				let saved = null;
				await msg.say(`Night ${turn}, sending DMs...`);
				for (const player of players.values()) {
					if (player.role.includes('pleb')) continue;
					await msg.say(`The ${player.role} is making their decision...`);
					const valid = players.filterArray(p => p.role !== player.role);
					await player.user.send(stripIndents`
						${questions[player.role]} Please type the number.
						${valid.map((p, i) => `**${i + 1}.** ${p.user.tag}`).join('\n')}
					`);
					const filter = res => valid[Number.parseInt(res.content, 10) - 1];
					const decision = await player.user.dmChannel.awaitMessages(filter, {
						max: 1,
						time: 120000
					});
					if (!decision.size) {
						await player.user.send('Sorry, time is up!');
						continue;
					}
					const choice = Number.parseInt(decision.first().content, 10);
					if (player.role === 'mafia') {
						const chosen = players.get(choice);
						killed = chosen.id;
						await player.user.send(`${chosen.user.tag} will be killed...`);
					} else if (player.role === 'doctor') {
						const chosen = players.get(choice);
						saved = chosen.id;
						await player.user.send(`${chosen.user.tag} will be saved...`);
					} else if (player.role === 'detective') {
						await player.user.send(players.find(p => p.role === 'mafia').id === choice ? 'Yes.' : 'No.');
					}
				}
				const display = killed ? players.get(killed).user : null;
				const story = stories[Math.floor(Math.random() * stories.length)];
				if (killed && killed !== saved) players.delete(killed);
				if (killed && killed === saved) {
					await msg.say(stripIndents`
						Late last night, a Mafia member emerged from the dark and tried to kill ${display}${story}
						Thankfully, a doctor stepped in just in time to save the day.
						Who is this mysterious Mafia member? You have one minute to decide.
					`);
				} else if (killed && players.size < 3) {
					await msg.say(stripIndents`
						Late last night, a Mafia member emerged from the dark and killed poor ${display}${story}
						Sadly, after the event, the final citizen left the town in fear, leaving the Mafia to rule forever.
					`);
					break;
				} else if (killed && killed !== saved) {
					await msg.say(stripIndents`
						Late last night, a Mafia member emerged from the dark and killed poor ${display}${story}
						Who is this mysterious Mafia member? You have one minute to decide.
					`);
				} else {
					await msg.say(stripIndents`
						Late last night, a Mafia member emerged from the dark. Thankfully, however, they didn't try to kill anyone.
						Who is this mysterious Mafia member? You have one minute to decide.
					`);
				}
				await wait(60000);
				const playersArr = Array.from(players.values());
				await msg.say(stripIndents`
					Who do you think is the Mafia member? Please type the number.
					${playersArr.map((p, i) => `**${i + 1}.** ${p.user.tag}`).join('\n')}
				`);
				const voted = [];
				const filter = res => {
					if (!players.exists(p => p.user.id === res.author.id)) return false;
					if (voted.includes(res.author.id)) return false;
					if (!playersArr[Number.parseInt(res.content, 10) - 1]) return false;
					voted.push(res.author.id);
					return true;
				};
				const votes = await msg.channel.awaitMessages(filter, {
					max: players.size,
					time: 120000
				});
				if (!votes.size) {
					await msg.say('No one will be hanged.');
					continue;
				}
				const hanged = this.getHanged(votes, players, playersArr);
				await msg.say(`${hanged.user} will be hanged.`);
				players.delete(hanged.id);
				++turn;
			}
			this.playing.delete(msg.channel.id);
			const mafia = players.find(p => p.role === 'mafia');
			if (!mafia) return msg.say('The Mafia has been hanged! Thanks for playing!');
			return msg.say(`Oh no, the Mafia wasn't caught in time... Nice job, ${mafia.user}!`);
		} catch (err) {
			this.playing.delete(msg.channel.id);
			return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`);
		}
	}

	async generatePlayers(list) {
		let roles = ['mafia', 'doctor', 'detective'];
		for (let i = 0; i < (list.length - 2); i++) roles.push(`pleb ${i + 1}`);
		roles = shuffle(roles);
		const players = new Collection();
		let i = 0;
		for (const user of list) {
			players.set(user.id, {
				id: user.id,
				user,
				role: roles[i]
			});
			await user.send(`Your role will be: ${roles[i]}!`);
			i++;
		}
	}

	getHanged(votes, players, playersArr) {
		const counts = new Collection();
		for (const vote of votes.values()) {
			const player = players.get(playersArr[Number.parseInt(vote.content, 10) - 1].id);
			if (counts.has(player.id)) {
				++counts.get(player.id).votes;
			} else {
				counts.set(player.id, {
					id: player.id,
					votes: 1,
					user: player.user
				});
			}
		}
		return counts.sort((a, b) => b.votes - a.votes).first();
	}
};
