import { MaxQuantityHelper, getDiscount } from "./helpers";

export type PriceOptions = {
  ico: number;
  iva: number;
  base: number;
  subtotal: number;
  total: number;
  measurementTotal: number;
  externalId?: number;
  maxQuantity?: number;
  discountedExternalId?: number;
  expireDate?: Date | null;
  quantity?: number;
  discount?: number | null;
};

export class Price {
  public readonly ico: number;
  public readonly iva: number;
  public readonly base: number;
  public readonly subtotal: number;
  public readonly total: number;
  public readonly measurementTotal: number;
  public readonly externalId?: number;
  public readonly maxQuantity?: number;
  public readonly discountedExternalId?: number;
  public readonly expireDate?: Date | null;
  private _quantity?: number;
  public readonly discount?: number | null;

  constructor({
    ico,
    iva,
    base,
    subtotal,
    total,
    measurementTotal,
    externalId,
    maxQuantity,
    discountedExternalId,
    expireDate,
    quantity,
    discount,
  }: PriceOptions) {
    this.ico = ico;
    this.iva = iva;
    this.base = base;
    this.subtotal = subtotal;
    this.total = total;
    this.measurementTotal = measurementTotal;
    this.externalId = externalId;
    this.maxQuantity = maxQuantity;
    this.discountedExternalId = discountedExternalId;
    this.expireDate = expireDate;
    this._quantity = quantity;
    this.discount = discount;
  }

  get isExpired(): boolean {
    return !!this.expireDate && new Date() > this.expireDate;
  }

  /**
   * Set the price quantity until maxQuantity and returns the remaining quantity
   * @param quantity the quantity
   * @returns the remaining quantity
   */
  set quantity(quantity: number | undefined) {
    if (quantity != undefined) {
      if (this.isExpired && quantity > 0) {
        throw new Error("Price is expired");
      }
      if (
        this.maxQuantity !== undefined &&
        this.maxQuantity !== null &&
        this.maxQuantity !== 0 &&
        quantity > this.maxQuantity
      ) {
        throw new Error(
          `Quantity can't be higher than maxQuantity(${this.maxQuantity})`
        );
      }
    }

    this._quantity = quantity;
  }

  get quantity(): number | undefined {
    return this._quantity;
  }

  /**
   * Calculates the total based on quantity
   *
   * @returns the total * quantity
   */
  get extendedTotal() {
    return this.quantity == null ? null : this.total * this.quantity;
  }

  /**
   * Calculates the subtotal based on quantity
   *
   * @returns the total * subtotal
   */
  get extendedSubtotal() {
    return this.quantity == null ? null : this.subtotal * this.quantity;
  }

  static fromPricing(json: any): Price[] {
    const prices = [];
    for (const item of json) {
      const {
        values,
        ico,
        iva,
        scheduleEndDate,
        discountedMaximumQuantity,
        maximumQuantity,
      } = item;
      const maxHelper = new MaxQuantityHelper(
        maximumQuantity,
        discountedMaximumQuantity
      );
      for (const value of values) {
        const { externalId, discountedExternalId, startQuantity, endQuantity } =
          value;
        if (value.discountedTotal && value.discountedTotal < value.total) {
          const {
            discountedPrice: base,
            discountedSubtotal: subtotal,
            discountedTotal: total,
            discountedTotalPerUnit,
            total: regularTotal,
          } = value;
          const expireDate = scheduleEndDate
            ? new Date(scheduleEndDate)
            : undefined;
          const maxQuantity =
            expireDate && expireDate < new Date()
              ? 0
              : maxHelper.discountedMaxQuantity(startQuantity, endQuantity);
          if (maxQuantity !== 0) {
            prices.push(
              new Price({
                ico,
                iva,
                base,
                subtotal,
                total,
                externalId,
                maxQuantity,
                discountedExternalId,
                measurementTotal: discountedTotalPerUnit,
                expireDate: scheduleEndDate
                  ? new Date(scheduleEndDate)
                  : undefined,
                discount: getDiscount(regularTotal, total),
              })
            );
          }
        }
        const { price: base, subtotal, total, totalPerUnit } = value;
        const maxQuantity = maxHelper.maxQuantity(startQuantity, endQuantity);
        prices.push(
          new Price({
            ico,
            iva,
            base,
            subtotal,
            total,
            measurementTotal: totalPerUnit,
            externalId,
            maxQuantity,
          })
        );
      }
    }
    return prices;
  }

  static fromCatalogue(json: any): Price[] {
    const prices: Price[] = [];
    const maxHelper = new MaxQuantityHelper(
      json.maximumQuantity,
      json.discountedMaximumQuantity
    );
    for (const item of json.prices) {
      if (item.discountedTotal) {
        const ico = item.discountedSubtotal - item.discountedBase;
        const iva = (item.discountedTotal - ico) / item.discountedBase - 1;
        const expireDate = json.scheduleEndDate
          ? new Date(json.scheduleEndDate)
          : undefined;
        const maxQuantity =
          expireDate && expireDate < new Date()
            ? 0
            : maxHelper.discountedMaxQuantity(
                item.startQuantity,
                item.endQuantity
              );
        if (maxQuantity !== 0) {
          prices.push(
            new Price({
              ico,
              iva: Math.round(iva * 100) / 100,
              base: item.discountedBase,
              subtotal: item.discountedSubtotal,
              total: item.discountedTotal,
              measurementTotal: item.unitPrice,
              discount: item.discount,
              externalId: item.externalId,
              discountedExternalId: item.discountedExternalId,
              maxQuantity,
              expireDate,
            })
          );
        }
      }

      const ico = item.managerSubtotal - item.managerPrice;
      const iva = (item.managerTotal - ico) / item.managerPrice - 1;
      const maxQuantity = maxHelper.maxQuantity(
        item.startQuantity,
        item.endQuantity
      );
      prices.push(
        new Price({
          ico,
          iva: Math.round(iva * 100) / 100,
          base: item.managerPrice,
          subtotal: item.managerSubtotal,
          total: item.managerTotal,
          externalId: item.externalId,
          measurementTotal: item.unitPrice,
          maxQuantity,
        })
      );
    }

    return prices;
  }

  toJSON(customerTotal: number) {
    return {
      customerTotal, // deprecated
      ico: this.ico,
      iva: this.iva,
      base: this.base,
      subtotal: this.subtotal,
      managerSubtotal: this.subtotal, // deprecated
      total: this.total,
      measurementTotal: this.measurementTotal,
      externalId: this.externalId,
      maxQuantity: this.maxQuantity,
      discountedExternalId: this.discountedExternalId
        ? this.discountedExternalId
        : null,
      expireDate: this.expireDate,
      quantity: this.quantity,
      discount:
        this.discount != null ? Math.round(this.discount * 100) / 100 : null,
    };
  }
}
