/* eslint-disable max-classes-per-file */
import get from 'lodash/get';
import i18n from '../../i18n';
import {
  NotAuthorized,
  NotImplementedError,
  BadRequest,
} from '../../libs/errors';
import updateUserBalance from '../updateUserBalance';
import updateUserHourglasses from '../updateUserHourglasses';

export class AbstractBuyOperation {
  /**
   * @param {User} user - the User-Object
   * @param {Request} req - the Request-Object
   */
  constructor (user, req) {
    this.user = user;
    this.req = req || {};

    const quantity = get(req, 'quantity');

    this.quantity = quantity ? Number(quantity) : 1;
    if (this.quantity < 1 || !Number.isInteger(this.quantity)) throw new BadRequest(this.i18n('invalidQuantity'));
  }

  /**
   * Returns the item value
   * @param item
   * @returns {number}
   */
  getItemValue (item) { // eslint-disable-line class-methods-use-this
    return item.value;
  }

  /**
   * Returns the item key
   * @param item
   * @returns {String}
   */
  getItemKey (item) { // eslint-disable-line class-methods-use-this
    return item.key;
  }

  /**
   * Returns the item type
   * @param item
   * @returns {String}
   */
  getItemType (item) { // eslint-disable-line class-methods-use-this
    if (!item.type) throw new NotImplementedError('item doesn\'t have a type property');

    return item.type;
  }

  /**
   * Shortcut to get the translated string without passing `req.language`
   * @param {String} key - translation key
   * @param {*=} params
   * @returns {*|string}
   */
  // eslint-disable-next-line no-unused-vars
  i18n (key, params = {}) {
    return i18n.t.apply(null, [...arguments, this.req.language]); // eslint-disable-line prefer-rest-params, max-len
  }

  /**
   * If the Operation allows purchasing items by quantity
   * @returns Boolean
   */
  multiplePurchaseAllowed () { // eslint-disable-line class-methods-use-this
    throw new NotImplementedError('multiplePurchaseAllowed');
  }

  /**
   * Method is called to save the params as class-fields in order to access them
   */
  extractAndValidateParams () { // eslint-disable-line class-methods-use-this
    throw new NotImplementedError('extractAndValidateParams');
  }

  async executeChanges () { // eslint-disable-line class-methods-use-this
    throw new NotImplementedError('executeChanges');
  }

  async purchase () {
    if (!this.multiplePurchaseAllowed() && this.quantity > 1) {
      throw new NotAuthorized(this.i18n('messageNotAbleToBuyInBulk'));
    }

    this.extractAndValidateParams(this.user, this.req);

    const resultObj = await this.executeChanges(this.user, this.item, this.req);

    return resultObj;
  }
}

export class AbstractGoldItemOperation extends AbstractBuyOperation {
  canUserPurchase (user, item) {
    this.item = item;
    const itemValue = this.getItemValue(item);

    const userGold = user.stats.gp;

    if (userGold < itemValue * this.quantity) {
      throw new NotAuthorized(this.i18n('messageNotEnoughGold'));
    }

    if (item && item.canOwn && !item.canOwn(user)) {
      throw new NotAuthorized(this.i18n('cannotBuyItem'));
    }
  }

  async subtractCurrency (user, item) {
    const itemValue = this.getItemValue(item);

    user.stats.gp -= itemValue * this.quantity;
  }
}

export class AbstractGemItemOperation extends AbstractBuyOperation {
  canUserPurchase (user, item) {
    this.item = item;
    const itemValue = this.getItemValue(item);

    if (!item.canBuy(user)) {
      throw new NotAuthorized(this.i18n('messageNotAvailable'));
    }

    if (!user.balance || user.balance < itemValue * this.quantity) {
      throw new NotAuthorized(this.i18n('notEnoughGems'));
    }
  }

  async subtractCurrency (user, item) {
    const itemValue = this.getItemValue(item);

    await updateUserBalance(user, -(itemValue * this.quantity), 'spend', item.key, item.text());
  }
}

export class AbstractHourglassItemOperation extends AbstractBuyOperation {
  canUserPurchase (user, item) {
    this.item = item;

    if (user.purchased.plan.consecutive.trinkets <= 0) {
      throw new NotAuthorized(this.i18n('notEnoughHourglasses'));
    }
  }

  async subtractCurrency (user, item) { // eslint-disable-line class-methods-use-this
    await updateUserHourglasses(user, -1, 'spend', item.key);
  }
}
