const url = require("url");
const { Message, User, GuildMember, Role, Guild, Channel } = require("discord.js");

const regex = {
  userOrMember: new RegExp("^(?:<@!?)?(\\d{17,21})>?$"),
  channel: new RegExp("^(?:<#)?(\\d{17,21})>?$"),
  role: new RegExp("^(?:<@&)?(\\d{17,21})>?$"),
  snowflake: new RegExp("^(\\d{17,21})$"),
};

/* eslint-disable class-methods-use-this */

/**
 * The base resolver class
 */
class Resolver {

  /**
   * @param {KomadaClient} client The Komada Client
   */
  constructor(client) {
    Object.defineProperty(this, "client", { value: client });
  }

  /**
   * Fetch a Message object by its Snowflake or instanceof Message.
   * @param {Snowflake} message The message snowflake to validate.
   * @param {Channel} channel The Channel object in which the message can be found.
   * @returns {Promise<?Message>}
   */
  async msg(message, channel) {
    if (message instanceof Message) return message;
    return regex.snowflake.test(message) ? channel.messages.fetch(message).catch(() => null) : undefined;
  }

  /**
   * Fetch messages by a snowflake or instanceof Message
   * @param {Snowflake}  message The message snowflake to validate.
   * @param {Channel}  channel The Channel object in which the message can be found.
   * @param {number}  [limit=100] The number of messages to fetch and send back.
   * @return {Promise<?Collection<Message>>}
   */
  async msgs(message, channel, limit = 100) {
    if (message instanceof Message) message = message.id;
    return regex.snowflake.test(message) ? channel.messages.fetch(message, { limit, around: message }).catch(() => null) : undefined;
  }

  /**
   * Resolve a User object by its instance of User, GuildMember, or by its Snowflake.
   * @param {User} user The user to validate.
   * @returns {Promise<?User>}
   */
  async user(user) {
    if (user instanceof User) return user;
    if (user instanceof GuildMember) return user.user;
    if (typeof user === "string" && regex.userOrMember.test(user)) return this.client.user.bot ? this.client.users.fetch(regex.userOrMember.exec(user)[1]).catch(() => null) : this.client.users.get(regex.userOrMember.exec(user)[1]);
    return null;
  }

  /**
   * Resolve a GuildMember object by its instance of GuildMember, User, or by its Snowflake.
   * @param {(GuildMember|User|Snowflake)} member The number to validate.
   * @param {Guild} guild The Guild object in which the member can be found.
   * @returns {Promise<?GuildMember>}
   */
  async member(member, guild) {
    if (member instanceof GuildMember) return member;
    if (member instanceof User) return guild.members.fetch(member);
    if (typeof member === "string" && regex.userOrMember.test(member)) {
      const user = this.client.user.bot ? await this.client.users.fetch(regex.userOrMember.exec(member)[1]).catch(() => null) : this.client.users.get(regex.userOrMember.exec(member)[1]);
      if (user) return guild.members.fetch(user).catch(() => null);
    }
    return null;
  }

  /**
   * Resolve a Channel object by its instance of Channel, or by its Snowflake.
   * @param {Channel} channel The channel to validate.
   * @returns {Promise<?Channel>}
   */
  async channel(channel) {
    if (channel instanceof Channel) return channel;
    if (typeof channel === "string" && regex.channel.test(channel)) return this.client.channels.get(regex.channel.exec(channel)[1]);
    return null;
  }

  /**
   * Resolve a Guild object by its instance of Guild, or by its Snowflake.
   * @param {Guild} guild The guild to validate/find.
   * @returns {Promise<?Guild>}
   */
  async guild(guild) {
    if (guild instanceof Guild) return guild;
    if (typeof guild === "string" && regex.snowflake.test(guild)) return this.client.guilds.get(guild);
    return null;
  }

  /**
   * Resolve a Role object by its instance of Role, or by its Snowflake.
   * @param {Role} role The role to validate/find.
   * @param {Guild} guild The Guild object in which the role can be found.
   * @returns {Promise<?Role>}
   */
  async role(role, guild) {
    if (role instanceof Role) return role;
    if (typeof role === "string" && regex.role.test(role)) return guild.roles.get(regex.role.exec(role)[1]);
    return null;
  }

  /**
   * Resolve a Boolean instance.
   * @param {(boolean|string)} bool The boolean to validate.
   * @returns {Promise<?boolean>}
   */
  async boolean(bool) {
    if (bool instanceof Boolean) return bool;
    if (["1", "true", "+", "t", "yes", "y"].includes(String(bool).toLowerCase())) return true;
    if (["0", "false", "-", "f", "no", "n"].includes(String(bool).toLowerCase())) return false;
    return null;
  }

  /**
   * Resolve a String instance.
   * @param {string} string The string to validate.
   * @returns {Promise<?string>}
   */
  async string(string) {
    return String(string);
  }

  /**
   * Resolve an Integer.
   * @param {(string|number)} integer The integer to validate.
   * @returns {Promise<?number>}
   */
  async integer(integer) {
    integer = parseInt(integer);
    if (Number.isInteger(integer)) return integer;
    return null;
  }

  /**
   * Resolve a Float.
   * @param {(string|number)} number The float to validate.
   * @returns {Promise<?number>}
   */
  async float(number) {
    number = parseFloat(number);
    if (!Number.isNaN(number)) return number;
    return null;
  }

  /**
   * Resolve a hyperlink.
   * @param {string} hyperlink The hyperlink to validate.
   * @returns {Promise<?string>}
   */
  async url(hyperlink) {
    const res = url.parse(hyperlink);
    if (res.protocol && res.hostname) return hyperlink;
    return null;
  }

}

module.exports = Resolver;