import { PriceDTO } from '@generated/swagger/checkout-api';
import {
  DEFAULT_PRICE_DTO,
  GIFT_CARD_MAX_PRICE,
  PURCHASE_OPTIONS,
} from '../constants';
import { isArchive, isGiftCard, isItemDTO } from './purchase-options.helpers';
import { PurchaseOptionsState } from './purchase-options.state';
import {
  ActionType,
  Availability,
  Branch,
  CanAdd,
  FetchingAvailability,
  Item,
  PurchaseOption,
} from './purchase-options.types';

export function getType(state: PurchaseOptionsState): ActionType {
  return state.type;
}

export function getUseRedemptionPoints(state: PurchaseOptionsState): boolean {
  return state.useRedemptionPoints;
}

export function getShoppingCartId(state: PurchaseOptionsState): number {
  return state.shoppingCartId;
}

export function getItems(state: PurchaseOptionsState): Item[] {
  return state.items;
}

export function getPurchaseOption(state: PurchaseOptionsState): PurchaseOption {
  const options = getPurchaseOptionsInAvailabilities(state);

  if (options.length === 1) {
    return options[0];
  }

  if (state.purchaseOption) {
    return state.purchaseOption;
  }

  // #4380 Abholung als Standard wenn möglich und keine Auswahl getroffen wurde
  if (options.includes('pickup')) {
    return 'pickup';
  }

  return undefined;
}

export function getSelectedItemIds(state: PurchaseOptionsState): number[] {
  let selectedItemIds = state.selectedItemIds;

  if (selectedItemIds.length === 0 && state.items.length === 1) {
    selectedItemIds = state.items.map((item) => item.id);
  }
  const purchaseOption = getPurchaseOption(state);
  selectedItemIds = selectedItemIds.filter((id) =>
    getCanAddForItemWithPurchaseOption(id, purchaseOption)(state),
  );
  return selectedItemIds;
}

export function getDefaultBranch(state: PurchaseOptionsState): Branch {
  if (state.defaultBranch?.isOrderingEnabled === false) {
    return undefined;
  }
  return state.defaultBranch;
}

export function getPickupBranch(state: PurchaseOptionsState): Branch {
  const branch = state.pickupBranch || getDefaultBranch(state);

  if (branch?.branchType === 1) {
    return branch;
  }

  return undefined;
}

export function getInStoreBranch(state: PurchaseOptionsState): Branch {
  const branch = state.inStoreBranch || getDefaultBranch(state);

  if (branch?.branchType === 1) {
    return branch;
  }

  return undefined;
}

export function getAvailabilities(state: PurchaseOptionsState): Availability[] {
  return state.availabilities;
}

export function getPrices(state: PurchaseOptionsState): {
  [itemId: number]: PriceDTO;
} {
  return state.prices;
}

export function getCanAddResults(state: PurchaseOptionsState): CanAdd[] {
  return state.canAddResults;
}

export function getCanAddForItemWithPurchaseOption(
  itemId: number,
  purchaseOption: PurchaseOption,
): (state: PurchaseOptionsState) => CanAdd {
  return (state: PurchaseOptionsState) => {
    const canAddResults = getCanAddResults(state);
    return canAddResults.find(
      (ca) =>
        ca.canAdd &&
        ca.itemId === itemId &&
        ca.purchaseOption === purchaseOption,
    );
  };
}

export function getPurchasingOptions(
  state: PurchaseOptionsState,
): PurchaseOption[] {
  const availabilities = getAvailabilities(state);
  const purchaseOptions = availabilities.map(
    (availability) => availability.purchaseOption,
  );
  return Array.from(new Set(purchaseOptions)).sort(
    (a, b) => PURCHASE_OPTIONS.indexOf(a) - PURCHASE_OPTIONS.indexOf(b),
  );
}

export function getPurchaseOptionsInAvailabilities(
  state: PurchaseOptionsState,
): PurchaseOption[] {
  const availabilities = getAvailabilities(state);
  let purchaseOptions = availabilities.map(
    (availability) => availability.purchaseOption,
  );

  purchaseOptions = Array.from(new Set(purchaseOptions));

  // if 'delivery' is not present but 'dig-delivery' or 'b2b-delivery' is present, add 'delivery' to the list
  if (
    !purchaseOptions.includes('delivery') &&
    (purchaseOptions.includes('dig-delivery') ||
      purchaseOptions.includes('b2b-delivery'))
  ) {
    purchaseOptions.push('delivery');
  }

  // if remove 'dig-delivery' and 'b2b-delivery' and 'catalog' as 'catalog' is not a purchase option but is needed for other data (Needed for #4813 and Bugfix for #4710)
  purchaseOptions = purchaseOptions.filter(
    (purchaseOption) =>
      purchaseOption !== 'dig-delivery' &&
      purchaseOption !== 'b2b-delivery' &&
      purchaseOption !== 'catalog',
  );

  return purchaseOptions.sort(
    (a, b) => PURCHASE_OPTIONS.indexOf(a) - PURCHASE_OPTIONS.indexOf(b),
  );
}

export function getFetchingAvailabilities(
  state: PurchaseOptionsState,
): Array<FetchingAvailability> {
  return state.fetchingAvailabilities;
}

export function getFetchingAvailabilitiesForItem(
  itemId: number,
): (state: PurchaseOptionsState) => Array<FetchingAvailability> {
  return (state: PurchaseOptionsState) => {
    const fetchingAvailabilities = getFetchingAvailabilities(state);
    return fetchingAvailabilities.filter((fa) => fa.itemId === itemId);
  };
}

export function getItemsThatHaveAnAvailabilityForPurchaseOption(
  purchaseOption: PurchaseOption,
): (state: PurchaseOptionsState) => Item[] {
  return (state) => {
    const availabilities = getAvailabilities(state);
    const items = getItems(state);

    const itemIds = availabilities
      .filter((availability) => {
        if (purchaseOption === 'delivery') {
          return (
            availability.purchaseOption === purchaseOption ||
            availability.purchaseOption === 'dig-delivery' ||
            availability.purchaseOption === 'b2b-delivery'
          );
        }
        return availability.purchaseOption === purchaseOption;
      })
      .map((availability) => availability.itemId);
    return items.filter((item) => itemIds.includes(item.id));
  };
}

export function getItemsThatHaveAnAvailabilityAndCanAddForPurchaseOption(
  purchaseOption: PurchaseOption,
) {
  return (state: PurchaseOptionsState): Item[] => {
    const items =
      getItemsThatHaveAnAvailabilityForPurchaseOption(purchaseOption)(state);

    const canAddResults = getCanAddResults(state);

    return items.filter((item) =>
      canAddResults.some(
        (canAdd) =>
          canAdd.itemId === item.id &&
          canAdd.purchaseOption === purchaseOption &&
          canAdd.canAdd,
      ),
    );
  };
}

export function getItemsForList(state: PurchaseOptionsState): Item[] {
  const purchaseOption = getPurchaseOption(state);
  const items = getItems(state);

  if (purchaseOption == undefined) {
    return items;
  }

  const availabilities = getAvailabilities(state);

  const itemIds = availabilities
    .filter((availability) => {
      if (purchaseOption === 'delivery') {
        return (
          availability.purchaseOption === purchaseOption ||
          availability.purchaseOption === 'dig-delivery' ||
          availability.purchaseOption === 'b2b-delivery'
        );
      }
      return availability.purchaseOption === purchaseOption;
    })
    .map((availability) => availability.itemId);
  return items.filter((item) => itemIds.includes(item.id));
}

export function getAvailabilitiesForItem(
  itemId: number,
  allDeliveryAvailabilities?: boolean,
): (state: PurchaseOptionsState) => Availability[] {
  return (state) => {
    let availabilities = getAvailabilities(state);
    availabilities = availabilities.filter(
      (availability) => availability.itemId === itemId,
    );

    // if 'delivery', 'dig-delivery' and 'b2b-delivery' are present remove 'dig-delivery' and 'b2b-delivery'
    if (
      !allDeliveryAvailabilities &&
      availabilities.some(
        (availability) => availability.purchaseOption === 'delivery',
      )
    ) {
      availabilities = availabilities.filter(
        (availability) =>
          availability.purchaseOption !== 'dig-delivery' &&
          availability.purchaseOption !== 'b2b-delivery',
      );
    }
    const optionsOrder = PURCHASE_OPTIONS;

    return availabilities.sort(
      (a, b) =>
        optionsOrder.indexOf(a.purchaseOption) -
        optionsOrder.indexOf(b.purchaseOption),
    );
  };
}

export function getCanEditPrice(
  itemId: number,
): (state: PurchaseOptionsState) => boolean {
  return (state) => {
    const item = getItems(state).find((item) => item.id === itemId);

    if (isGiftCard(item, getType(state))) {
      return true;
    }

    const purchaseOption = getPurchaseOption(state);

    if (
      isArchive(item, getType(state)) &&
      !getAvailabilityPriceForPurchaseOption(itemId, purchaseOption)(state)
    ) {
      return true;
    }

    return false;
  };
}

export function getCanEditVat(
  itemId: number,
): (state: PurchaseOptionsState) => boolean {
  return (state) => {
    const item = getItems(state).find((item) => item.id === itemId);
    const purchaseOption = getPurchaseOption(state);

    if (
      isArchive(item, getType(state)) &&
      !getAvailabilityPriceForPurchaseOption(itemId, purchaseOption)(state)
    ) {
      return true;
    }

    return false;
  };
}

export function getIsGiftCard(
  itemId: number,
): (state: PurchaseOptionsState) => boolean {
  return (state) => {
    const item = getItems(state).find((item) => item.id === itemId);
    return isGiftCard(item, getType(state));
  };
}

export function getAvailabilityPriceForPurchaseOption(
  itemId: number,
  purchaseOption: PurchaseOption,
): (
  state: PurchaseOptionsState,
) => (PriceDTO & { fromCatalogue?: boolean }) | undefined {
  return (state) => {
    const item = getItems(state).find((item) => item.id === itemId);
    const type = getType(state);
    const availabilities = getAvailabilitiesForItem(itemId)(state);

    let availability = availabilities.find(
      (availability) => availability.purchaseOption === purchaseOption,
    );

    /**
     * Wenn Abholung oder Versand, dann den günstigeren Preis nehmen wenn priceMaintained false ist
     * Genauere Infos https://dev.azure.com/hugendubel/ISA/_wiki/wikis/ISA.wiki/186/Anzeige-Preise
     * 18.01.24 #4611 Preis // Preisunterschiede im Warenkorb
     */
    if (purchaseOption === 'delivery' || purchaseOption === 'pickup') {
      let deliveryAvailability = availabilities.find(
        (availability) => availability.purchaseOption === 'delivery',
      );
      const digAvailability = availabilities.find(
        (availability) => availability.purchaseOption === 'dig-delivery',
      );
      const b2bAvailability = availabilities.find(
        (availability) => availability.purchaseOption === 'b2b-delivery',
      );

      if (purchaseOption === 'delivery') {
        if (isDigCustomer(state)) {
          deliveryAvailability = digAvailability ?? deliveryAvailability;
        } else if (isB2bCustomer(state)) {
          deliveryAvailability = b2bAvailability ?? deliveryAvailability;
        }
      }

      deliveryAvailability =
        deliveryAvailability ?? digAvailability ?? b2bAvailability;

      if (purchaseOption === 'delivery') {
        availability = deliveryAvailability ?? availability;
      }

      // Wennn Artkkel nicht Preisgebinden ist, dann den günstigeren Preis nehmen wenn priceMaintained false ist
      if (!availability?.data?.priceMaintained) {
        const pickupAvailability = availabilities.find(
          (availability) => availability.purchaseOption === 'pickup',
        );
        const catalogAvailability = availabilities.find(
          (availability) => availability.purchaseOption === 'catalog',
        );

        const availabilityHasInvalidPrice = !(
          availability?.data?.price?.value?.value > 0
        );
        const pickupAvailabilityHasInvalidPrice = !(
          pickupAvailability?.data?.price?.value?.value > 0
        );
        const catalogAvailabilityHasInvalidPrice = !(
          catalogAvailability?.data?.price?.value?.value > 0
        );

        // Early exit wenn Rücklagepreis und Versandpreis nicht vorhanden sind, dann auf den Katalogpreis zurückgreifen
        if (availabilityHasInvalidPrice && pickupAvailabilityHasInvalidPrice) {
          return catalogAvailabilityHasInvalidPrice
            ? DEFAULT_PRICE_DTO
            : catalogAvailability?.data?.price;
        }

        // Wenn beide Preise vorhanden sind, dann den günstigeren Preis nehmen
        if (
          !availabilityHasInvalidPrice &&
          !pickupAvailabilityHasInvalidPrice
        ) {
          // Wenn beide Preise gleich sind dann nehme den Preis für die ausgeählte Option
          if (
            deliveryAvailability?.data?.price?.value?.value ===
            pickupAvailability?.data?.price?.value?.value
          ) {
            return availability?.data?.price;
          }

          return deliveryAvailability?.data?.price?.value?.value >
            pickupAvailability?.data?.price?.value?.value
            ? pickupAvailability?.data?.price
            : deliveryAvailability?.data?.price;
        }

        // Wenn nur der Versandpreis vorhanden ist, dann diesen nehmen
        if (!availabilityHasInvalidPrice && pickupAvailabilityHasInvalidPrice) {
          return deliveryAvailability?.data?.price;
        }

        // Wenn nur der Rücklagepreis vorhanden ist, dann diesen nehmen
        if (availabilityHasInvalidPrice && !pickupAvailabilityHasInvalidPrice) {
          return pickupAvailability?.data?.price;
        }
      }

      // #4760 Fehler bei Abholpreisberechnung in Filiale Darmstadt Ernst-Ludwig-Straße (negativ Preis im Warenkorb)
      // Überprüfen ob der Preis ungültig ist und der Versandpreis vorhanden ist
      // Wenn ja, dann den Versandpreis nehmen
      if (!(availability?.data?.price?.value?.value > 0)) {
        if (deliveryAvailability?.data?.price?.value?.value > 0) {
          const deliveryPrice = deliveryAvailability?.data?.price;
          availability.data.price = deliveryPrice;
        } else {
          // Wenn der Versandpreis auch ungültig ist, dann den Katalogpreis nehmen
          const catalogAvailability = availabilities.find(
            (availability) => availability.purchaseOption === 'catalog',
          );
          return catalogAvailability?.data?.price ?? DEFAULT_PRICE_DTO;
        }
      }
    }

    if (
      availability &&
      availability.data.price?.value?.value &&
      availability.data.price
    ) {
      return availability.data.price;
    }

    if (isItemDTO(item, type)) {
      return item?.catalogAvailability?.price;
    } else {
      return item?.unitPrice;
    }
  };
}

export function getPriceForPurchaseOption(
  itemId: number,
  purchaseOption: PurchaseOption,
): (state: PurchaseOptionsState) => PriceDTO {
  return (state) => {
    if (getCanEditPrice(itemId)(state)) {
      const price = getPrices(state)[itemId];
      if (price) {
        return price;
      }
    }

    return (
      getAvailabilityPriceForPurchaseOption(itemId, purchaseOption)(state) ??
      DEFAULT_PRICE_DTO
    );
  };
}

export function getQuantityForItem(
  itemId: number,
): (state: PurchaseOptionsState) => number {
  return (state) => {
    const item = getItems(state).find((item) => item.id === itemId);
    const quantity = item?.quantity;
    return quantity ?? 1;
  };
}

export function getCanAddResultForItemAndCurrentPurchaseOption(
  itemId: number,
): (state: PurchaseOptionsState) => CanAdd {
  return (state) => {
    const fetchingAvailabilities =
      getFetchingAvailabilitiesForItem(itemId)(state);
    const purchaseOption = getPurchaseOption(state);
    if (fetchingAvailabilities.length > 0) {
      return { canAdd: false, itemId, purchaseOption };
    }

    const availability = getAvailabilityWithPurchaseOption(
      itemId,
      purchaseOption,
    )(state);

    if (purchaseOption === 'in-store') {
      const quantity = getQuantityForItem(itemId)(state);
      if (quantity > availability?.data?.inStock) {
        return { canAdd: false, itemId, purchaseOption };
      }
    }

    const canAddResults = getCanAddResults(state);

    return canAddResults.find(
      (canAddResult) =>
        canAddResult.itemId === itemId &&
        canAddResult.purchaseOption === purchaseOption,
    );
  };
}

export function getAvailabilityWithPurchaseOption(
  itemId: number,
  purchaseOption: PurchaseOption,
): (state: PurchaseOptionsState) => Availability {
  return (state) => {
    const availabilities = getAvailabilitiesForItem(itemId, true)(state);

    let availability = availabilities.find(
      (availability) => availability.purchaseOption === purchaseOption,
    );
    if (purchaseOption === 'delivery') {
      const digAvailability = availabilities.find(
        (availability) => availability.purchaseOption === 'dig-delivery',
      );
      const b2bAvailability = availabilities.find(
        (availability) => availability.purchaseOption === 'b2b-delivery',
      );

      if (!(digAvailability && b2bAvailability)) {
        availability = digAvailability ?? b2bAvailability;
      }
    }
    return availability;
  };
}

export function getCanAddResultWithPurchaseOption(
  itemId: number,
  purchaseOption: PurchaseOption,
): (state: PurchaseOptionsState) => CanAdd {
  return (state) => {
    const canAddResults = getCanAddResults(state);
    return canAddResults.find(
      (canAddResult) =>
        canAddResult.itemId === itemId &&
        canAddResult.purchaseOption === purchaseOption,
    );
  };
}

export function canContinue(state: PurchaseOptionsState): boolean {
  const purchaseOption = getPurchaseOption(state);

  if (purchaseOption === undefined) {
    return false;
  }

  if (purchaseOption === 'pickup' && getPickupBranch(state) === undefined) {
    return false;
  }

  if (purchaseOption === 'in-store' && getInStoreBranch(state) === undefined) {
    return false;
  }

  if (
    (purchaseOption === 'delivery' ||
      purchaseOption === 'b2b-delivery' ||
      purchaseOption === 'dig-delivery') &&
    isGuestWithoutAccountCustomer(state)
  ) {
    return false;
  }

  const items = getItems(state);

  if (items.length === 0) {
    return false;
  }

  const actionType = getType(state);

  for (const item of items) {
    if (isGiftCard(item, actionType)) {
      const price = getPriceForPurchaseOption(item.id, purchaseOption)(state);
      if (
        !(price?.value?.value > 0 && price?.value?.value <= GIFT_CARD_MAX_PRICE)
      ) {
        return false;
      }
    } else if (
      isArchive(item, actionType) &&
      !getAvailabilityPriceForPurchaseOption(item.id, purchaseOption)(state)
    ) {
      const price = getPriceForPurchaseOption(item.id, purchaseOption)(state);
      const hasPrice = price?.value?.value > 0;
      const hasVat = price?.vat?.vatType > 0;
      if (!(hasPrice && hasVat)) {
        return false;
      }
    }
  }

  const selectedItemIds = getSelectedItemIds(state);

  if (selectedItemIds.length === 0) {
    return false;
  }

  const selectedItems = items.filter((item) =>
    selectedItemIds.includes(item.id),
  );

  if (selectedItems.length === 0) {
    return false;
  }

  for (const item of selectedItems) {
    if (item.quantity > 0) {
      return true;
    }

    const price = getPriceForPurchaseOption(item.id, purchaseOption)(state);

    if (price === undefined) {
      return false;
    }

    if (price?.value?.value > 0 && !(price?.vat?.value >= 0)) {
      return false;
    }

    const availability = getAvailabilityWithPurchaseOption(
      item.id,
      purchaseOption,
    )(state);

    if (availability === undefined) {
      return false;
    }

    const canAddResult = getCanAddResultWithPurchaseOption(
      item.id,
      purchaseOption,
    )(state);

    if (canAddResult === undefined || !canAddResult.canAdd) {
      return false;
    }
  }

  return true;
}

export function isDigCustomer(state: PurchaseOptionsState): boolean {
  return !!state.customerFeatures['webshop'];
}

export function isB2bCustomer(state: PurchaseOptionsState): boolean {
  return !!state.customerFeatures['b2b'];
}

export function isGuestWithoutAccountCustomer(
  state: PurchaseOptionsState,
): boolean {
  return (
    !!state.customerFeatures['guest'] &&
    !!state.customerFeatures['d-no-account']
  );
}
