import { client } from "../client";
import { getRepository } from "typeorm";
import { KnownUser } from "@shared/db/entity/KnownUser";
import { ReactionType, ReactionEmote } from "@shared/db/entity/ReactionEmote";
import { Message, User, PartialUser, MessageReaction as DiscordMessageReaction } from "discord.js";
import { logger } from "src/logging";
import { Command, ICommandData, Event, BotEventData, Plugin } from "src/model/plugin";
import { MessageReaction } from "@shared/db/entity/MessageReaction";

const pattern = /^react to\s+"([^"]+)"\s+with\s+<:[^:]+:([^>]+)>$/i;

async function getRandomEmotes(allowedTypes: ReactionType[], limit: number) {
    const reactionEmotesRepo = getRepository(ReactionEmote);

    const a = await reactionEmotesRepo.query(`
        select distinct on (type) type, "reactionId"
        from
                (   select *
                    from reaction_emote
                    where type in (${allowedTypes.map((s, i) => `$${i + 1}`).join(",")})
                    order by random(), random() 
                    limit ${limit}
        ) as sub`, allowedTypes) as ReactionEmote[];

    return a;
}

@Plugin
export class ReactCommands {

    private REACT_PROBABILITY = 0.3;

    @Event("messageReactionAdd")
    async randomReact(data: BotEventData, reaction: DiscordMessageReaction, user: User | PartialUser): Promise<void> {
        if (Math.random() <= this.REACT_PROBABILITY && !user.bot) {
            logger.verbose(`Reacting to message ${reaction.message.id} because user ${user.tag} reacted to it`);
            reaction.message.react(reaction.emoji);
        }
    }

    @Command({
        type: "mention",
        pattern: "react to",
        auth: true,
        documentation: {
            description: "React to <message> with <emote>.",
            example: "react to \"<message>\" with <emote>"
        }
    })
    async addReaction({ message, contents }: ICommandData): Promise<void> {
        const reactContents = pattern.exec(contents as string);

        if (reactContents != null) {
            const reactable = reactContents[1].trim().toLowerCase();
            const reactionEmoji = reactContents[2];

            if (!client.bot.emojis.cache.has(reactionEmoji)) {
                message.reply("I cannot react with this emoji :(");
                return;
            }

            const repo = getRepository(MessageReaction);

            const msgReaction = repo.create({
                message: reactable,
                reactionEmoteId: reactionEmoji
            });
            await repo.save(msgReaction);

            message.reply("added reaction!");
        }
    }

    @Command({
        type: "mention",
        pattern: "remove reaction to", 
        auth: true, 
        documentation: {
            description: "Stops reacting to <message>.", 
            example: "remove reaction to <message>"
        } 
    })
    async removeReaction({message, contents}: ICommandData): Promise<void> {
        const content = (contents as string).substring("remove reaction to ".length).trim().toLowerCase();
        const repo = getRepository(MessageReaction);
        const result = await repo.delete({ message: content });

        if (result.affected == 0) {
            message.reply("no such reaction available!");
            return;
        }
        message.reply("removed reaction!");
    }

    @Command({
        type: "mention",
        pattern: "reactions", 
        documentation: {
            description: "Lists all known messages this bot can react to.", 
            example: "reactions"
        } 
    })
    async listReactions({ message }: ICommandData): Promise<void> {
        const reactionsRepo = getRepository(MessageReaction);

        const messages = await reactionsRepo.find({
            select: ["message"]
        });

        const reactions = messages.reduce((p, c) => `${p}\n${c.message}`, "");
        message.reply(`I'll react to the following messages:\n\`\`\`${reactions}\n\`\`\``);
    }

    @Event("message")
    async reactToMentions(data: BotEventData, msg: Message): Promise<void> {
        if (data.actionsDone)
            return;
        
        const content = msg.cleanContent.trim();
        const lowerContent = content.toLowerCase();

        const reactionRepo = getRepository(MessageReaction);
        const usersRepo = getRepository(KnownUser);

        const message = await reactionRepo.findOne({ message: lowerContent });

        if (message) {
            const emoji = client.bot.emojis.resolve(message.reactionEmoteId);
            if (emoji)
                msg.react(emoji);
            data.actionsDone = true;
            return;
        }

        if (msg.mentions.users.size == 0)
            return;

        const knownUsers = await usersRepo.find({
            select: ["mentionReactionType"],
            where: [...msg.mentions.users.map(u => ({ userID: u.id }))]
        });

        if (knownUsers.length == 0)
            return;

        const reactionEmoteTypes = new Set<ReactionType>();

        for (const user of knownUsers) {
            if (user.mentionReactionType == ReactionType.NONE)
                continue;
            reactionEmoteTypes.add(user.mentionReactionType);
        }

        if(reactionEmoteTypes.size == 0)
            return;

        const randomEmotes = await getRandomEmotes([...reactionEmoteTypes], 5);

        if (randomEmotes.length == 0)
            return;

        for (const emote of randomEmotes) {
            const emoji = client.bot.emojis.resolve(emote.reactionId);
            if(emoji)
                await msg.react(emoji);
        }

        data.actionsDone = true;
    }

    @Event("indirectMention")
    async reactToPing(data: BotEventData, msg: Message): Promise<void> {
        if (data.actionsDone)
            return;
        let emoteType = ReactionType.ANGERY;

        const repo = getRepository(KnownUser);

        const knownUser = await repo.findOne({
            select: ["replyReactionType"],
            where: [{
                userID: msg.author.id
            }]
        });

        if (knownUser) {
            if (knownUser.replyReactionType == ReactionType.NONE)
                return;
            emoteType = knownUser.replyReactionType;
        }

        const emotes = await getRandomEmotes([emoteType], 1);

        if (emotes.length != 1)
            return;

        const emote = client.bot.emojis.resolve(emotes[0].reactionId);

        if (!emote) {
            logger.warn(`Emote ${emotes[0]} no longer is valid. Deleting invalid emojis from the list...`);

            const emotesRepo = getRepository(ReactionEmote);
            await emotesRepo.delete({ reactionId: emotes[0].reactionId });
            return;
        }

        msg.channel.send(emote.toString());
        data.actionsDone = true;
    }
}