/**
 * Contains functions/data relating to users.
 * Users in Discord are generally considered the base entity. Users can spawn across the
 * entire platform, be members of guilds, participate in text and voice chat, and much
 * more. Users are separated by a distinction of "bot" vs "normal." Although they are
 * similar, bot users are automated users that are "owned" by another user. Unlike normal
 * users, bot users do not have a limitation on the number of guilds they can be a part of.
 * Reference: @see {@link https://discord.com/developers/docs/resources/user|Discord}
 * @module User
 * @memberof Discord
 * @namespace Discord.User
 */

/* eslint-disable no-unused-vars */
import { isUrl, assertString } from '@vizality/util/string';
import { log, warn, error } from '@vizality/util/logger';
import { getModule } from '@vizality/webpack';

import constants from './constants';
import snowflake from './snowflakes';

/**
 * @private
 */
const _labels = [ 'Discord', 'User' ];
const _log = (labels, ...message) => log({ labels, message });
const _warn = (labels, ...message) => warn({ labels, message });
const _error = (labels, ...message) => error({ labels, message });

/**
 * Export the user action functions.
 */
export const actions = {
  pie: () => {
    try {
      const CurrentUserId = getModule('getId').getId();
      if (!CurrentUserId) {
        return null;
      }
      return this.getUser(CurrentUserId);
    } catch (err) {
      return _error(_labels.concat('actions', 'getCurrentUser'), err);
    }
  }
};

/**
 * Gets the current user object.
 * @returns {User|null}
 */
export function getCurrentUser () {
  try {
    const currentUserId = getModule('getId').getId();
    if (!currentUserId) {
      return null;
    }
    return this.getUser(currentUserId);
  } catch (err) {
    return _error(_labels.concat('getCurrentUser'), err);
  }
}

/**
 * Gets the user object from a user ID.
 * @param {snowflake} userId User ID
 * @returns {User|void}
 */
export function getUser (userId) {
  try {
    assertString(userId);
    return getModule('getUser', 'getUsers').getUser(userId);
  } catch (err) {
    return _error(_labels.concat('getUser'), err);
  }
}

/**
 * Fetches the user object from a user ID. Unlike getUser, this will use an API call to
 * lookup the user if they are not in cache.
 * @param {snowflake} userId User ID
 * @returns {Promise<User|void>}
 */
export async function fetchUser (userId) {
  try {
    assertString(userId);
    return getModule('getUser', 'fetchCurrentUser').getUser(userId);
  } catch (err) {
    return _error(_labels.concat('fetchUser'), err);
  }
}

/**
 * Gets the user object from a user tag.
 * @param {string} userTag User tag
 * @returns {User|void}
 */
export function getUserByTag (userTag) {
  try {
    assertString(userTag);
    const username = userTag.slice(0, -5);
    const discriminator = userTag.slice(-4);
    return getModule('getUser', 'getUsers').findByTag(username, discriminator);
  } catch (err) {
    return _error(_labels.concat('getUserByTag'), err);
  }
}

/**
 * Gets all of the currently cached user objects.
 * @returns {Collection<snowflake, User>} All cached user objects
 */
export function getUsers () {
  try {
    return getModule('getUser', 'getUsers').getUsers();
  } catch (err) {
    return _error(_labels.concat('getUsers'), err);
  }
}

/**
 * Gets all of the currently cached user IDs.
 * @returns {Array<snowflake>|void} All cached user IDs
 */
export function getUserIds () {
  try {
    return getModule('getStatus', 'getUserIds').getUserIds();
  } catch (err) {
    return _error(_labels.concat('getUserIds'), err);
  }
}

/**
 * Gets the user's avatar hash. @see {@link https://discord.com/developers/docs/reference#image-formatting-image-base-url|Discord}
 * If no user ID is specified, tries to get the avatar string of the current user.
 * @param {snowflake} userId User ID
 * @returns {string|void}
 */
export function getAvatar (userId) {
  try {
    assertString(userId);
    return this.getUser(userId).avatar;
  } catch (err) {
    return _error(_labels.concat('getAvatar'), err);
  }
}

/**
 * Gets the user avatar URL.
 * @param {snowflake} [userId] User ID
 * @returns {string|void}
 */
export function getAvatarUrl (userId) {
  try {
    assertString(userId);
    const AvatarURL = this.getUser(userId).getAvatarURL();
    /**
     * Check if the avatar URL exists. If it's already a valid URL, skip directly to the
     * return below. If it's not a valid URL already, make sure it starts with /
     */
    if (AvatarURL && !isUrl(AvatarURL) && AvatarURL.startsWith('/')) {
      return window.location.origin + AvatarURL;
    }
    return AvatarURL;
  } catch (err) {
    return _error(_labels.concat('getAvatarUrl'), err);
  }
}

/**
 * Gets the user account creation date and time in local string format.
 * @param {snowflake} userId User ID
 * @returns {string|void}
 */
export function getCreatedAt (userId) {
  try {
    assertString(userId);
    return snowflake.getTimestamp(userId).toLocaleString();
  } catch (err) {
    return _error(_labels.concat('getCreatedAt'), err);
  }
}

/**
 * Gets the user account creation timestamp.
 * @param {snowflake} userId User ID
 * @returns {string|void}
 */
export function getCreatedTimestamp (userId) {
  try {
    assertString(userId);
    return snowflake.getTimestamp(userId);
  } catch (err) {
    return _error(_labels.concat('getCreatedTimestamp'), err);
  }
}

/**
 * Gets the user discriminator (4 digit indentifier).
 * If no user ID is specified, tries to get the avatar string of the current user.
 * @param {snowflake} userId User ID
 * @returns {string|void}
 */
export function getDiscriminator (userId) {
  try {
    assertString(userId);
    return this.getUser(userId).discriminator;
  } catch (err) {
    return _error(_labels.concat('getDiscriminator'), err);
  }
}

/**
 * Gets the last message the user sent (that's visible to your client).
 * @param {snowflake} userId User ID
 * @returns {Message|void}
 */
export function getLastMessage (userId) {
  // @todo: Not sure how to check for this yet.
  return void 0;
}

/**
 * Gets the user note contents.
 * @param {snowflake} userId User ID
 * @returns {string|void}
 */
export function getNote (userId) {
  try {
    assertString(userId);
    return getModule('getNote').getNote(userId).note;
  } catch (err) {
    return _error(_labels.concat('getNote'), err);
  }
}

/**
 * Gets the user tag, which is a combination of the username and discriminator.
 * @param {snowflake} userId User ID
 * @returns {string|void}
 */
export function getTag (userId) {
  try {
    assertString(userId);
    return this.getUser(userId).tag;
  } catch (err) {
    return _error(_labels.concat('getTag'), err);
  }
}

/**
 * Gets the user username.
 * @param {snowflake} userId User ID
 * @returns {string|void}
 */
export function getUsername (userId) {
  try {
    assertString(userId);
    return this.getUser(userId).username;
  } catch (err) {
    return _error(_labels.concat('getUsername'), err);
  }
}

/**
 * Checks if the user has an animated avatar.
 * @param {snowflake} userId User ID
 * @returns {boolean} Whether the user has an animated avatar
 */
export function hasAnimatedAvatar (userId) {
  try {
    assertString(userId);
    const User = this.getUser(userId);
    const ImageResolver = getModule('getUserAvatarURL', 'getGuildIconURL', 'hasAnimatedGuildIcon');
    return ImageResolver.hasAnimatedAvatar(User);
  } catch (err) {
    return _error(_labels.concat('hasAnimatedAvatar'), err);
  }
}

/**
 * Checks if the user has a non-default avatar.
 * @param {snowflake} userId User ID
 * @returns {boolean} Whether the user has a non-default avatar
 */
export function hasAvatar (userId) {
  try {
    assertString(userId);
    return Boolean(this.getUser(userId).avatar);
  } catch (err) {
    return _error(_labels.concat('hasAvatar'), err);
  }
}

/**
 * Checks if the user has a Nitro (premium) subscription.
 * @param {snowflake} userId User ID
 * @returns {Promise<boolean>} Whether the user has an active Nitro subscription
 */
export function hasNitro (userId) {
  try {
    return new Promise(async resolve => {
      const result = (await getModule('getAPIBaseURL')).get({
        url: `${constants.Endpoints.USER_PROFILE(userId)}`
      });
      resolve(!!result.body.premium_since);
    });
  } catch (err) {
    return _error(_labels.concat('hasNitro'), err);
  }
}

/**
 * Checks if the user is a bot.
 * @param {snowflake} userId User ID
 * @returns {boolean} Whether the user is a bot
 */
export function isBot (userId) {
  try {
    assertString(userId);
    return this.getUser(userId).bot;
  } catch (err) {
    return _error(_labels.concat('isBot'), err);
  }
}

/**
 * Checks if the user is a bug hunter.
 * @param {snowflake} userId User ID
 * @returns {boolean} Whether the user is a bug hunter
 */
export function isBugHunter (userId) {
  try {
    assertString(userId);
    return Boolean(
      this.getUser(userId).hasFlag(constants.UserFlags.BUG_HUNTER_LEVEL_1) ||
      this.getUser(userId).hasFlag(constants.UserFlags.BUG_HUNTER_LEVEL_2)
    );
  } catch (err) {
    return _error(_labels.concat('isBugHunter'), err);
  }
}

/**
 * Checks if the user is a Discord partner.
 * @param {snowflake} userId User ID
 * @returns {boolean} Whether the user is a Discord partner
 */
export function isPartner (userId) {
  try {
    assertString(userId);
    return this.getUser(userId).hasFlag(constants.UserFlags.PARTNER);
  } catch (err) {
    return _error(_labels.concat('isPartner'), err);
  }
}

/**
 * Checks if the user is a Discord staff member.
 * @param {snowflake} userId User ID
 * @returns {boolean} Whether the user is a Discord staff member
 */
export function isStaff (userId) {
  try {
    assertString(userId);
    return this.getUser(userId).hasFlag(constants.UserFlags.STAFF);
  } catch (err) {
    return _error(_labels.concat('isStaff'), err);
  }
}

/**
 * Checks if the user is a system user.
 * @param {snowflake} userId User ID
 * @returns {boolean} Whether the user is a system user
 */
export function isSystemUser (userId) {
  try {
    assertString(userId);
    return this.getUser(userId).system;
  } catch (err) {
    return _error(_labels.concat('isSystemUser'), err);
  }
}

/**
 * Checks if the user is a verified bot.
 * @param {snowflake} userId User ID
 * @returns {boolean} Whether the user is a verified bot
 */
export function isVerifiedBot (userId) {
  try {
    assertString(userId);
    return this.getUser(userId).hasFlag(constants.UserFlags.VERIFIED_BOT);
  } catch (err) {
    return _error(_labels.concat('isVerifiedBot'), err);
  }
}

/**
 * Checks if the user is a verified bot developer.
 * @param {snowflake} userId User ID
 * @returns {boolean} Whether the user is a verified bot developer
 */
export function isVerifiedBotDev (userId) {
  try {
    assertString(userId);
    return this.getUser(userId).hasFlag(constants.UserFlags.VERIFIED_DEVELOPER);
  } catch (err) {
    return _error(_labels.concat('isVerifiedBotDev'), err);
  }
}

/**
 * Export the current user object as a property, as means of providing an alternate method
 * of accessing it.
 */
export const current = this.getCurrentUser();
