import Stripe from 'stripe';
import eris from 'eris';
import pluris from 'pluris';
import mongoose from 'mongoose';
import Redis from 'ioredis';
import { promises as fs } from 'fs';
import { Collection, Command, LocalStorage, Queue, Util, ServerManagement, Event } from '.';
import {
  Customer, CustomerInterface,
  CustomerPortal, CustomerPortalInterface,
  ExecutiveOrder, ExecutiveOrderInterface,
  File, FileInterface,
  Inquiry, InquiryInterface,
  Judgement, JudgementInterface,
  Member, MemberInterface,
  Merchant, MerchantInterface,
  Moderation, ModerationInterface,
  Motion, MotionInterface,
  Note, NoteInterface,
  PagerNumber, PagerNumberInterface,
  Proclamation, ProclamationInterface,
  Promo, PromoInterface,
  Rank, RankInterface,
  Redirect, RedirectInterface,
  Resolution, ResolutionInterface,
  SAA, SAAInterface,
  Score, ScoreInterface,
  ScoreHistorical, ScoreHistoricalInterface,
  Staff, StaffInterface,
  Stat, StatInterface,
} from '../models';
import { Config } from '../../types'; // eslint-disable-line

pluris(eris);

export default class Client extends eris.Client {
  public config: Config;

  public commands: Collection<Command>;

  public events: Collection<Event>;

  public intervals: Collection<NodeJS.Timeout>;

  public util: Util;

  public serverManagement: ServerManagement;

  public queue: Queue;

  public stripe: Stripe;



  public db: {
    Customer: mongoose.Model<CustomerInterface>,
    CustomerPortal: mongoose.Model<CustomerPortalInterface>,
    ExecutiveOrder: mongoose.Model<ExecutiveOrderInterface>,
    File: mongoose.Model<FileInterface>,
    Inquiry: mongoose.Model<InquiryInterface>,
    Judgement: mongoose.Model<JudgementInterface>,
    Member: mongoose.Model<MemberInterface>,
    Merchant: mongoose.Model<MerchantInterface>,
    Moderation: mongoose.Model<ModerationInterface>,
    Motion: mongoose.Model<MotionInterface>,
    Note: mongoose.Model<NoteInterface>,
    PagerNumber: mongoose.Model<PagerNumberInterface>,
    Proclamation: mongoose.Model<ProclamationInterface>,
    Promo: mongoose.Model<PromoInterface>,
    Rank: mongoose.Model<RankInterface>,
    Redirect: mongoose.Model<RedirectInterface>,
    Resolution: mongoose.Model<ResolutionInterface>,
    SAA: mongoose.Model<SAAInterface>,
    Score: mongoose.Model<ScoreInterface>,
    ScoreHistorical: mongoose.Model<ScoreHistoricalInterface>,
    Staff: mongoose.Model<StaffInterface>,
    Stat: mongoose.Model<StatInterface>,
    local: { muted: LocalStorage }
  };

  constructor(token: string, options?: eris.ClientOptions) {
    super(token, options);
    this.commands = new Collection<Command>();
    this.events = new Collection<Event>();
    this.intervals = new Collection<NodeJS.Timeout>();
    this.queue = new Queue(this);
    this.db = {
      Customer,
      CustomerPortal,
      ExecutiveOrder,
      File,
      Inquiry,
      Judgement,
      Member,
      Merchant,
      Moderation,
      Motion,
      Note,
      PagerNumber,
      Promo,
      Proclamation,
      Rank,
      Redirect,
      Resolution,
      SAA,
      Score,
      ScoreHistorical,
      Staff,
      Stat,
      local: { muted: new LocalStorage('muted') },
    };
  }

  get report() {
    return this.util.report;
  }

  get pbx() {
    return this.util.pbx;
  }


  public async loadDatabase() {
    await mongoose.connect(this.config.mongoDB, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
      poolSize: 50,
    });

    const statMessages = await this.db.Stat.findOne({ name: 'messages' });
    const statCommands = await this.db.Stat.findOne({ name: 'commands' });
    const statPages = await this.db.Stat.findOne({ name: 'pages' });
    const statRequests = await this.db.Stat.findOne({ name: 'requests' });

    if (!statMessages) {
      await (new this.db.Stat({ name: 'messages', value: 0 }).save());
    }
    if (!statCommands) {
      await (new this.db.Stat({ name: 'commands', value: 0 }).save());
    }
    if (!statPages) {
      await (new this.db.Stat({ name: 'pages', value: 0 }).save());
    }
    if (!statRequests) {
      await (new this.db.Stat({ name: 'requests', value: 0 }).save());
    }
  }

  public loadPlugins() {
    this.util = new Util(this);
    this.serverManagement = new ServerManagement(this);
    this.stripe = new Stripe(this.config.stripeKey, { apiVersion: null, typescript: true });
  }

  public async loadIntervals() {
    const intervalFiles = await fs.readdir(`${__dirname}/../intervals`);
    intervalFiles.forEach((file) => {
      const intervalName = file.split('.')[0];
      if (file === 'index.js') return;
      const interval: NodeJS.Timeout = (require(`${__dirname}/../intervals/${file}`).default)(this);
      this.intervals.add(intervalName, interval);
      this.util.signale.success(`Successfully loaded interval: ${intervalName}`);
    });
  }

  public async loadEvents(eventFiles: { [s: string]: typeof Event; } | ArrayLike<typeof Event>) {
    const evtFiles = Object.entries<typeof Event>(eventFiles);
    for (const [name, Ev] of evtFiles) {
      const event = new Ev(this);
      this.events.add(event.event, event);
      this.on(event.event, event.run);
      this.util.signale.success(`Successfully loaded event: ${name}`);
      delete require.cache[require.resolve(`${__dirname}/../events/${name}`)];
    }
  }

  public async loadCommands(commandFiles: { [s: string]: typeof Command; } | ArrayLike<typeof Command>) {
    const cmdFiles = Object.values<typeof Command>(commandFiles);
    for (const Cmd of cmdFiles) {
      const command = new Cmd(this);
      if (command.subcmds.length) {
        command.subcmds.forEach((C) => {
          const cmd: Command = new C(this);
          command.subcommands.add(cmd.name, cmd);
          this.util.signale.success(`Successfully loaded subcommand ${cmd.name} under ${command.name}`);
        });
      }
      delete command.subcmds;
      this.commands.add(command.name, command);
      this.util.signale.success(`Successfully loaded command: ${command.name}`);
    }
  }
}
