/* eslint-disable no-await-in-loop */
/* eslint-disable no-eval */
import Bull from 'bull';
import cron from 'cron';
import { TextableChannel, TextChannel } from 'eris';
import { Client, RichEmbed } from '.';
import { ScoreInterface, InqType as InquiryType } from '../models';

import { apply as Apply } from '../commands';

export default class Queue {
  public client: Client;

  public queues: { score: Bull.Queue };

  constructor(client: Client) {
    this.client = client;
    this.queues = {
      score: new Bull('score', { prefix: 'queue::score' }),
    };
    this.setProcessors();
    this.setCronJobs();
  }

  protected setCronJobs() {
    const historialCommunityReportJob = new cron.CronJob('0 20 * * *', async () => {
      try {
        const reports = await this.client.db.Score.find().lean().exec();
        const startDate = new Date();

        for (const report of reports) {
          const inqs = await this.client.db.Inquiry.find({ userID: report.userID });
          const data = new this.client.db.ScoreHistorical({
            userID: report.userID,
            report,
            inquiries: inqs.map((inq) => inq._id),
            date: startDate,
          });
          await data.save();
        }
      } catch (err) {
        this.client.util.handleError(err);
      }
    });

    const clearOldHistoricalReportsJob = new cron.CronJob('0 20 * * *', async () => {
      this.client.db.ScoreHistorical.remove({ date: { $lt: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) } });
    });

    historialCommunityReportJob.start();
    clearOldHistoricalReportsJob.start();
  }

  public async jobCounts() {
    const data = {
      waiting: 0,
      active: 0,
      completed: 0,
      failed: 0,
      delayed: 0,
    };
    for (const entry of Object.entries(this.queues)) {
      // eslint-disable-next-line no-await-in-loop
      const counts = await entry[1].getJobCounts();
      data.waiting += counts.waiting;
      data.active += counts.active;
      data.completed += counts.completed;
      data.failed += counts.failed;
      data.delayed += counts.delayed;
    }
    return data;
  }

  protected listeners() {
    this.queues.score.on('active', (job) => {
      this.client.util.signale.pending(`${job.id} has become active.`);
    });
    this.queues.score.on('completed', (job) => {
      this.client.util.signale.success(`Job with id ${job.id} has been completed`);
    });
    this.queues.score.on('error', async (err) => {
      this.client.util.handleError(err);
    });
  }

  protected setProcessors() {
    this.queues.score.process('score::inquiry', async (job: Bull.Job<{ inqID: string, userID: string, name: string, type: InquiryType, reason?: string }>) => {
      const member = this.client.util.resolveMember(job.data.userID, this.client.guilds.get(this.client.config.guildID));
      const report = await this.client.db.Score.findOne({ userID: job.data.userID }).lean().exec();
      const embed = new RichEmbed();
      if (job.data.type === InquiryType.HARD) {
        embed.setTitle('Inquiry Notification');
        embed.setDescription(job.data.inqID);
        embed.setColor('#800080');
        embed.addField('Member', `${member.user.username}#${member.user.discriminator} | <@${job.data.userID}>`, true);
        embed.addField('Type', 'HARD', true);
        embed.addField('Department/Service', job.data.name.toUpperCase(), true);
        embed.addField('Reason', job.data.reason ?? 'N/A', true);
        embed.setTimestamp();
        embed.setFooter(this.client.user.username, this.client.user.avatarURL);
        if (report.notify === true) {
          await this.client.getDMChannel(job.data.userID).then((chan) => {
            chan.createMessage(`__**Community Score - Hard Pull Notification**__\n*You have signed up to be notified whenever your hard score has been pulled. See \`?score\` for more information.*\n\n**Department/Service:** ${job.data.name.toUpperCase()}`);
          }).catch(() => {});
        }
      } else {
        embed.setTitle('Inquiry Notification');
        embed.setColor('#00FFFF');
        embed.addField('Member', `${member.user.username}#${member.user.discriminator} | <@${job.data.userID}>`, true);
        embed.addField('Type', 'SOFT', true);
        embed.addField('Department/Service', job.data.name.toUpperCase(), true);
        embed.setTimestamp();
        embed.setFooter(this.client.user.username, this.client.user.avatarURL);
      }
      const log = <TextChannel> this.client.guilds.get(this.client.config.guildID).channels.get('611584771356622849');
      log.createMessage({ embed }).catch(() => {});
    });
    this.queues.score.process('score::update', async (job: Bull.Job<{ score: ScoreInterface, total: number, activity: number, roles: number, moderation: number, cloudServices: number, other: number, staff: number }>) => {
      await this.client.db.Score.updateOne({ userID: job.data.score.userID }, { $set: { total: job.data.total, activity: job.data.activity, roles: job.data.roles, moderation: job.data.moderation, cloudServices: job.data.cloudServices, other: job.data.other, staff: job.data.staff, lastUpdate: new Date() } });
      if (!job.data.score.pin || job.data.score.pin?.length < 1) {
        await this.client.db.Score.updateOne({ userID: job.data.score.userID }, { $set: { pin: [this.client.util.randomNumber(100, 999), this.client.util.randomNumber(10, 99), this.client.util.randomNumber(1000, 9999)] } });
      }
    });
    this.queues.score.process('score::apply', async (job: Bull.Job<{ channelInformation: { messageID: string, guildID: string, channelID: string }, url: string, userID: string, func?: string }>) => {
      const application = await Apply.apply(this.client, job.data.url, job.data.userID);
      const guild = this.client.guilds.get(job.data.channelInformation.guildID);
      const channel = <TextableChannel> guild.channels.get(job.data.channelInformation.channelID);
      const message = await channel.getMessage(job.data.channelInformation.messageID);
      const member = guild.members.get(job.data.userID);
      await message.delete();
      const embed = new RichEmbed();
      embed.setTitle('Application Decision');
      if (member) {
        embed.setAuthor(member.username, member.avatarURL);
      }
      if (application.decision === 'APPROVED') {
        embed.setColor('#50c878');
      } else if (application.decision === 'DECLINED') {
        embed.setColor('#fe0000');
      } else if (application.decision === 'PRE-DECLINE') {
        embed.setColor('#ffa500');
      } else {
        embed.setColor('#eeeeee');
      }
      const chan = await this.client.getDMChannel(job.data.userID);
      if (chan) {
        let description = `This application was processed by __${application.processedBy}__ on behalf of the vendor, department, or service who operates this application. Please contact the vendor for further information about your application if needed.`;
        if (application.token) description += `\n\n*This document provides detailed information about the decision given to you by EDS.*: https://eds.libraryofcode.org/dec/${application.token}`;
        embed.setDescription(description);
        embed.addField('Status', application.decision, true);
        embed.addField('User ID', job.data.userID, true);
        embed.addField('Application ID', application.id, true);
        embed.addField('Job ID', job.id.toString(), true);
        embed.setFooter(`${this.client.user.username} via Electronic Decision Service [EDS]`, this.client.user.avatarURL);
        embed.setTimestamp();
        try {
          await chan.createMessage({ embed });
        } catch {
          await channel.createMessage('**We were not able to send the decision of your application to your DMs. Please contact the Staff Team for further information.**');
        }

        await channel.createMessage({ embed: new RichEmbed().setTitle('Application Decision').setDescription('The decision of your application has been sent to your DMs.\n\n*Please view the PDF document for further information regarding the decision. For reconsiderations or questions, please contact us.*').setFooter(`${this.client.user.username} via Electronic Decision Service [EDS]`, this.client.user.avatarURL)
          .setTimestamp() });
      } else {
        await channel.createMessage('**We were not able to send the decision of your application to your DMs. Please contact the Staff Team for further information.**');
      }
      if (job.data.func) {
        const func = eval(job.data.func);
        if (application.status === 'SUCCESS' && application.decision === 'APPROVED') await func(this.client, job.data.userID);
      }
    });
  }

  public addInquiry(inqID: string, userID: string, name: string, type: InquiryType, reason?: string) {
    return this.queues.score.add('score::inquiry', { inqID, userID, name, type, reason });
  }

  public updateScore(score: ScoreInterface, total: number, activity: number, roles: number, moderation: number, cloudServices: number, other: number, staff: number) {
    return this.queues.score.add('score::update', { score, total, activity, roles, moderation, cloudServices, other, staff });
  }

  public processApplication(channelInformation: { messageID: string, guildID: string, channelID: string }, url: string, userID: string, func?: string) {
    return this.queues.score.add('score::apply', { channelInformation, url, userID, func });
  }
}
