import { Item, ItemOptions, ItemType } from "./item";
import { Price, PriceOptions } from "./price";
import { New } from "./new";
import { CartEvent } from "../event";

export class Measurement {
  constructor(public readonly unit: string, public readonly quantity: number) {}
  toJSON() {
    return {
      unit: this.unit,
      quantity: this.quantity,
    };
  }
}
export type ProductOptions = ItemOptions & {
  prices: Price[];
  retailPrice: number;
  measurement: Measurement;
  sku: string;
  sponsored: boolean;
  referenceId: number;
  ledgerAccount: string;
  description: string;
  lowStock: boolean;
  brandName: string;
  minQuantity: number;
  maxQuantity?: number;
  referencePromotionId?: number;
  chiperPrice?: number;
  oldPrice?: number;
  clusterCode?: string;
  isKvi?: boolean;
  partnerId?: number;
  citrusAdId?: string;
};
export type ProductRaw = Omit<ProductOptions, "prices"> & {
  prices: PriceOptions[];
  type: ItemType;
};

export class Product extends Item {
  private _prices: Price[];
  public readonly measurement: Measurement;
  public readonly sku: string;
  public readonly sponsored: boolean;
  public readonly referenceId: number;
  public readonly ledgerAccount: string;
  public readonly description: string;
  public readonly lowStock: boolean;
  public readonly brandName: string;
  public readonly minQuantity: number;
  public readonly retailPrice: number;
  public readonly _maxQuantity?: number;
  public readonly referencePromotionId?: number;
  private readonly _chiperPrice?: number;
  public readonly oldPrice?: number;
  public readonly clusterCode?: string;
  public readonly isKvi?: boolean;
  public readonly partnerId?: number;
  public readonly citrusAdId?: string;

  constructor({
    stock,
    quantity,
    multipleQuantity,
    name,
    image,
    id,
    warehouseId,
    packagingType,
    prices,
    measurement,
    sku,
    sponsored,
    referenceId,
    ledgerAccount,
    description,
    lowStock,
    brandName,
    minQuantity,
    maxQuantity,
    retailPrice,
    referencePromotionId,
    chiperPrice,
    oldPrice,
    clusterCode,
    isKvi,
    partnerId,
    citrusAdId
  }: ProductOptions, rate:number, isBackend?: boolean, isOffline?:boolean, offlinePrices?:any) {
    super(ItemType.PRODUCT, {
      stock,
      multipleQuantity,
      name,
      image,
      warehouseId,
      id,
      packagingType,
      minQuantity,
      measurement
    },rate, isBackend, isOffline);
    this.citrusAdId = citrusAdId;
    if (offlinePrices) {
      this._prices = offlinePrices;
    }else{
      this._prices = prices;
    }
    this.retailPrice = retailPrice;
    this.measurement = measurement;
    this.sku = sku;
    this.sponsored = sponsored;
    this.referenceId = referenceId;
    this.ledgerAccount = ledgerAccount;
    this.description = description;
    this.lowStock = lowStock;
    this.brandName = brandName;
    this.minQuantity = minQuantity;
    this._maxQuantity = maxQuantity;
    this.quantity = quantity;
    this.referencePromotionId = referencePromotionId;
    this._chiperPrice = chiperPrice;
    this.oldPrice = oldPrice;
    this.clusterCode = clusterCode;
    this.isKvi = isKvi;
    this.partnerId = partnerId;
  }

  get prices(): Price[] {
    return this._prices;
  }

  set prices(prices: Price[]) {
    const currentPrice = this.oldPrice || this.prices[0].total;
    const newPrice = prices[0].total;
    this._prices = prices;
    this.quantity = this._quantity; // update prices quantities
    if (newPrice > currentPrice) {
      let dif = newPrice - currentPrice;
      this.emit(CartEvent.NEWS, {
        id: this.id,
        cardType: "changePrice",
        productType: this.type,
        medium: this.image,
        name: this.name,
        packagingType: this.packagingType,
        previousTotal: currentPrice,
        quantity: this.quantity,
        stock: this.stock,
        total: newPrice,
        warehouseId: this.warehouseId,
        meta: (dif<0) ? "INCREASE" : "DECREASED"
      } as New);
    }
  }

  get maxQuantity(): number | undefined {
    let sum: number | undefined = 0;
    for (const price of this.prices) {
        if (!price.maxQuantity && price.maxQuantity !== 0) {
            return undefined;
        }
        sum += price.maxQuantity;
    }
    return sum;
  }

  get total(): number | null {
    if (this.quantity == null) return null;
    return +this.prices
      .reduce(
        (total: number, price: Price) => total + (price.extendedTotal ?? 0),
        0
      )
      .toFixed(2);
  }

  get subtotal(): number | null {
    if (this.quantity == null) return null;
    return +this.prices
      .reduce(
        (total: number, price: Price) => total + (price.extendedSubtotal ?? 0),
        0
      )
      .toFixed(2);
  }

  /**
   * Set the quantity and updates the prices quantities
   *
   * @param quantity the quantity
   */
  override set quantity(quantity: number | undefined) {
    super.quantity = quantity;
    if (quantity === null || !this.prices?.length) return;

    for (const [idx, price] of this.prices.entries()) {
      if (price.isExpired || quantity === undefined) {
        price.quantity = 0;
        continue;
      }
      const before = price.quantity;
      price.quantity = price.maxQuantity
        ? Math.min(price.maxQuantity, quantity)
        : quantity;
      if (idx > 0 && before === 0 && price.quantity > 0) {
        this.emit(CartEvent.PRICE_SCALE_UP, this.prices[idx - 1], price);
      }
      quantity -= price.quantity;
    }
  }

  override get quantity(): number | undefined {
    return super.quantity;
  }

  static from({
    id,
    name,
    stock,
    warehouseId,
    medium,
    packagingType,
    multipleQuantity,
    prices,
    quantity,
    measurement,
    sku,
    sponsored,
    citrusAdId,
    referenceId,
    ledgerAccount,
    description,
    lowStock,
    brandName,
    minQuantity,
    maximumQuantity,
    chiperPrice,
    oldPrice,
    clusterCode,
    isKvi,
    partnerId,
  }: any, rate:number, isBackend?: boolean, isOffline?:boolean, offlinePrices?:any) {
    const pricesToInsert = (!offlinePrices) ? prices : offlinePrices;
    return new Product({
      id,
      warehouseId,
      name,
      stock,
      image: medium,
      packagingType,
      minQuantity,
      retailPrice: pricesToInsert[0].customerTotal,
      multipleQuantity,
      prices:  pricesToInsert.map(
        (p: PriceOptions) =>
          new Price({
            ...p,
            expireDate: p.expireDate && new Date(p.expireDate),
          })
      ),
      quantity,
      measurement,
      sku,
      sponsored,
      citrusAdId,
      referenceId,
      ledgerAccount,
      description,
      lowStock,
      brandName,
      maxQuantity: maximumQuantity,
      chiperPrice,
      oldPrice,
      clusterCode,
      isKvi,
      partnerId,
    }, rate, isBackend, isOffline, offlinePrices);
  }

  static fromShopCart({
    multipleQuantity,
    prices,
    discountedTotal,
    managerPrice,
    discountedSubtotal,
    stock,
    quantity,
    medium,
    id,
    warehouseId,
    packagingType,
    sku,
    sponsored,
    citrusAdId,
    referenceId,
    ledgerAccount,
    description,
    lowStock,
    customerTotal,
    brandName,
    minQuantity,
    customerMeasurement,
    customerMeasurementUnit,
    name,
  }: any, rate: number): Product {
    if (prices) {
      multipleQuantity =
        multipleQuantity || prices[0].values[0].multipleQuantity;
      if (discountedTotal) {
        prices[0].values[0].discountedPrice = managerPrice;
        prices[0].values[0].discountedSubtotal = discountedSubtotal;
        prices[0].values[0].discountedTotal = discountedTotal;
      }
    }

    return new Product({
      name,
      warehouseId,
      prices: Price.fromPricing(prices),
      stock,
      quantity,
      multipleQuantity,
      maxQuantity: prices[0].maximumQuantity,
      retailPrice: customerTotal,
      image: medium,
      id,
      packagingType,
      measurement: new Measurement(
        customerMeasurementUnit ?? "",
        customerMeasurement ?? 1
      ),
      sku,
      sponsored,
      citrusAdId,
      referenceId,
      ledgerAccount,
      description,
      lowStock,
      brandName,
      minQuantity
    }, rate);
  }

  static fromCatalog({
    stock,
    multipleQuantity,
    name,
    medium,
    id,
    warehouseId,
    packagingType,
    prices,
    primaryPackaging,
    sku,
    sponsored,
    referenceId,
    ledgerAccount,
    description,
    customerPrice,
    lowStock = false,
    brandName,
    minQuantity = 1,
    maxQuantity,
    maximumQuantity,
    discountedMaximumQuantity,
    scheduleEndDate,
    quantity,
    isKvi = false,
    partnerId,
    citrusAdId
  }: any, rate:number): Product {
    return new Product({
      stock,
      multipleQuantity,
      name,
      image: medium,
      id,
      warehouseId,
      packagingType,
      prices: Price.fromCatalogue({
        prices,
        maximumQuantity,
        discountedMaximumQuantity,
        scheduleEndDate,
      }),
      retailPrice: customerPrice,
      measurement: primaryPackaging
        ? new Measurement(
            primaryPackaging.measurementUnit,
            primaryPackaging.measurement
          )
        : new Measurement("", 1),
      sku,
      sponsored,
      referenceId,
      ledgerAccount,
      description,
      lowStock,
      brandName,
      minQuantity,
      maxQuantity: maxQuantity ?? maximumQuantity,
      quantity,
      isKvi,
      partnerId,
      citrusAdId
    }, rate);
  }

  static fromPromoDetail({
    id,
    multipleQuantity,
    sku,
    displayName,
    measurementUnit,
    salesUnit,
    packagingType,
    prices,
    warehouseId,
    stock,
    image,
    medium,
    partnerId
  }: any, rate: number): Product {
    return new Product({
      stock,
      multipleQuantity,
      name: displayName,
      image: image || medium,
      id,
      warehouseId,
      packagingType,
      prices: prices.map((p: any) => new Price(p)),
      measurement: new Measurement(measurementUnit, salesUnit),
      sku,
      referenceId: 0,
      ledgerAccount: "0",
      description: displayName,
      lowStock: false,
      brandName: "Unknown",
      minQuantity: 1,
      maxQuantity: undefined,
      retailPrice: 0,
      sponsored: false,
      citrusAdId:undefined,
      isKvi: false,
      partnerId,
    }, rate);
  }

  get discountExpirationDate(): Date | undefined | null {
    const price = this.prices.find((price: Price) => price.discount);
    return price?.expireDate;
  }
  get regularPrice(): number {
    const price = this.prices.find((price: Price) => !price.discount);
    if (price === undefined) throw new Error("Product has no regular price");
    return price.total;
  }

  get totalDollars(): number | null {
    if (this.quantity == null) return null;
    return +(this.prices
      .reduce(
        (total: number, price: Price) => total + (price.extendedTotal ?? 0),
        0
      ) * this.rate)
      .toFixed(2);
  }

  get regularPriceDolar(): number {
    const price = this.prices.find((price: Price) => !price.discount);
    if (price === undefined) throw new Error("Product has no regular price");
    return price.total * this.rate;
  }

  get discountedPrice(): number | undefined {
    return this.prices.find(
      (p) => p.discount !== null && p.discount !== undefined && !p.isExpired
    )?.total;
  }

  /** price without taxes */
  get price(): number {
    const price = this.prices.find((price: Price) => !price.discount);
    if (price === undefined) throw new Error("Product has no regular price");
    return price.subtotal;
  }

  get chiperPrice(): number {
    return this._chiperPrice || this.regularPrice;
  }

  increase() {
    this.quantity =
      this.quantity == null
        ? this.minQuantity
        : this.quantity + this.multipleQuantity;
  }
  decrease() {
    this.quantity =
      this.quantity == null
        ? this.minQuantity
        : this.quantity - this.multipleQuantity;
  }
  clone(): Product {
    return new Product({
      id: this.id,
      warehouseId: this.warehouseId,
      name: this.name,
      brandName: this.brandName,
      description: this.description,
      prices: this.prices,
      retailPrice: this.retailPrice,
      ledgerAccount: this.ledgerAccount,
      lowStock: this.lowStock,
      maxQuantity: this.maxQuantity,
      measurement: this.measurement,
      minQuantity: this.minQuantity,
      multipleQuantity: this.multipleQuantity,
      packagingType: this.packagingType,
      quantity: this.quantity,
      referenceId: this.referenceId,
      sku: this.sku,
      image: this.image,
      stock: this.stock,
      sponsored: this.sponsored,
      isKvi: this.isKvi,
      partnerId: this.partnerId,
      citrusAdId: this.citrusAdId
    }, this.rate);
  }

  // create toJSON method
  toJSON() {
    return {
      ...super.toJSON(),
      brandName: this.brandName,
      description: this.description,
      prices: this.prices.map((prices: Price) =>
        prices.toJSON(this.retailPrice)
      ),
      retailPrice: this.retailPrice,
      ledgerAccount: this.ledgerAccount,
      lowStock: this.lowStock,
      maxQuantity: this.maxQuantity,
      maximumQuantity: this.maxQuantity, // deprecated
      measurement: this.measurement,
      referenceId: this.referenceId,
      sku: this.sku,
      sponsored: this.sponsored ? true : false,
      citrusAdId: this.citrusAdId ? this.citrusAdId : "",
      clusterCode: this.clusterCode,
      isKvi: this.isKvi,
      partnerId: this.partnerId
    };
  }
}
