import 'reflect-metadata';

import commandLineArgs, { CommandLineOptions } from 'command-line-args';
import * as Discord from 'discord.js';
import _ from 'lodash';
import moment from 'moment';
import { BotPlugin } from 'psyduck-contracts';
import { PluginQuoteUser, UserQuote, GuildUser, Guild } from 'psyduck-models';
import { ConnectionOptions, createConnection, Repository } from 'typeorm';

export const commandOptions = [
    { name: 'pluck', alias: 'p', type: Number },
    { name: 'help', alias: 'h', type: Boolean },
    { name: 'user', alias: 'u', type: String, defaultOption: true }
];

export const userFormatRegex = new RegExp(/^(<@.+>)|(<!.+>)$/);

export enum CommandType { Quote, Random }

export default class PsyduckQuotes implements BotPlugin {
    public users!: Repository<PluginQuoteUser>;
    public quotes!: Repository<UserQuote>;
    public connectionOptions: ConnectionOptions;
    constructor(options: any) {
        this.connectionOptions = options;
    }
    connect() {
        createConnection({
            ...this.connectionOptions,
            synchronize: true,
            logging: false,
            entities: [
                PluginQuoteUser, UserQuote
            ]
        }).then(async connection => {
            this.users = connection.getRepository(PluginQuoteUser);
            this.quotes = connection.getRepository(UserQuote);
        }).catch(error => console.log(error));
    }
    onMessage(message: Discord.Message) {
        if (message.author.bot) {
            return;
        }
        if (message.content.length <= 1) {
            return;
        }
        const dupe = RegExp(/^(.)\1*$/gm);
        if (dupe.test(message.content)) {
            return;
        }
        if (message.content.startsWith('!')) {
            const commandCode = message.content.slice(1, message.content.indexOf(' '));
            if (_.includes(['quote', 'q', 'grab', 'g'], commandCode)) {
                this.prepareHandler(message, CommandType.Quote);
            } else if (_.includes(['random', 'r'], commandCode)) {
                this.prepareHandler(message, CommandType.Random);
            }
        }
    }
    parseParameters(messageContent: string): string[] {
        const params = messageContent.match(/ (?=\S)[^'\s]*(?:'[^\\']*(?:\\[\s\S][^\\']*)*'[^'\s]*)*/g) || [];
        return params.map(p => p.trim());
    }
    hydrateOptions(parameters: string[]): CommandLineOptions {
        return commandLineArgs(commandOptions, { argv: parameters, partial: true });
    }
    extractUserId(userId: string): string | undefined {
        const matches = userId.match(/(?<=@|!)(.*)(?=>)/gm);
        if (matches && matches.length === 1) {
            return matches[0].replace('!', '');
        }
        return undefined;
    }
    prepareHandler(message: Discord.Message, commandType: CommandType) {
        const parameters = this.parseParameters(message.content);
        if (parameters.length > 0) {
            const options = this.hydrateOptions(parameters);
            if (options.help === true) {
                this.sendHelp(message);
            } else {
                if (!options.user || !userFormatRegex.test(options.user)) {
                    if (commandType === CommandType.Quote) {
                        message.channel.send(`Unable to quote. User missing or wrong format.`);
                    } else if (commandType === CommandType.Random) {
                        message.channel.send(`Unable to recall. User missing or wrong format.`);
                    }
                } else {
                    const userId = this.extractUserId(options.user);
                    if (!userId) {
                        message.channel.send('Unable to parse user id from supplied parameters.');
                    }
                    if (userId === message.author.id) {
                        if (commandType === CommandType.Quote) {
                            message.channel.send('It looks like you are trying to quote yourself! Don\'t take all the fun out of it!');
                        } else if (commandType === CommandType.Random) {
                            message.channel.send('It looks like you are trying to recall yourself! Don\'t take all the fun out of it!');
                        }
                    } else {
                        if (commandType === CommandType.Quote) {
                            this.saveQuote(message, userId!, options.pluck);
                        } else if (commandType === CommandType.Random) {
                            this.recallQuote(message, userId!);
                        }
                    }
                }
            }
        }
    }
    sendHelp(message: Discord.Message) {
        const embed = {
            'title': 'Psyduck Quote Plugin Help',
            'description': 'Use this plugin to quote users and recall random quotes from the past.',
            'url': 'https://github.com/galactic0wl/psyduck-plugin-quotes',
            'color': 3201505,
            'author': {
                'name': 'Marky Mark',
                'url': 'https://github.com/galactic0wl/psyduck-plugin-quotes',
                'icon_url': 'https://cdn.discordapp.com/avatars/341668692133806080/a_252e33ff6363f93849e75a5decf9f10a.gif'
            },
            'fields': [
                {
                    'name': 'Commands',
                    'value': 'The following commands are available to standard users.\n\n'
                },
                {
                    'name': '[q]uote & [g]rab',
                    'value': 'To save a quote from a user run `!q @MentionUser` to grab the last message. If you would like to target a specific message within reason you can run `!quote @MentionUser -p 3` to save the second to the last message of the user, or, 3 messages up.\n\n**Parameters** ```--pluck <count> or -p <count>```'
                },
                {
                    'name': '[r]andom',
                    'value': 'To recall a quote from a user run `!r @MentionUser` to grab a random message from the past.'
                }
            ]
        };
        message.channel.send({ embed });
    }
    async getUser(userId: string) {
        return await this.users.findOne({
            user: {
                id: userId
            }
        }, { relations: ['quotes'] });
    }
    createUser(guildId: string, serverName: string, guildAvatarUrl: string, userId: string, userName: string, avatarUrl: string) {
        const user = new PluginQuoteUser();

        const guildUser = new GuildUser();
        guildUser.id = userId;
        guildUser.userName = userName;
        guildUser.avatarUrl = avatarUrl;

        const guild = new Guild();
        guild.id = guildId;
        guild.serverName = serverName;
        guild.avatarUrl = guildAvatarUrl;

        guildUser.guild = guild;

        user.user = guildUser;
        user.quotes = [];
        return user;
    }
    async recallQuote(message: Discord.Message, userId: string) {
        const user = await this.getUser(userId);

        if (!user || user.quotes.length === 0) {
            message.channel.send(`Our records indicated that the user has zero quotes. While this is unfortunate, it may be resolved.`);
        } else {
            const quote = _.sample(user.quotes)!;
            message.channel.send(`Remember this one <@${userId}>? '${quote.content}' - Snagged by <@${quote.capturerId}> at ${quote.captureTime}`);
        }
    }
    buildQuote(capturerId: string, content: string): UserQuote {
        const quote = new UserQuote();
        quote.capturerId = capturerId;
        quote.content = content;
        quote.captureTime = moment().format('ddd MMM DD YYYY h:mm A');
        return quote;
    }
    async saveQuote(message: Discord.Message, userId: string, pluck = 1) {
        const messageMap = await message.channel.fetchMessages({ limit: 100 });
        const messages = [...messageMap.values()].filter(m => m.author.id === userId);
        if (messages.length > 0) {
            if (messages.length < pluck) {
                message.channel.send('You attempted to pluck a message out of my range. Please try again.');
            } else {
                let user = await this.getUser(userId);
                if (!user) {
                    user = this.createUser(message.guild.id, message.guild.name, message.guild.iconURL, message.author.id, message.author.username, message.author.avatarURL);
                    user.quotes.push(this.buildQuote(message.author.id, messages[pluck - 1].content));
                } else {
                    if (user.quotes.filter(u => u.content === messages[pluck - 1].content).length <= 0) {
                        user.quotes.push(this.buildQuote(message.author.id, messages[pluck - 1].content));
                    } else {
                        message.channel.send('It looks like I already have a record of this quote.');
                        return;
                    }
                }
                await this.users.save(user);
                message.channel.send(`Plucked: ${messages[pluck - 1]}`);
            }
        }
    }
}