import { Injectable } from '@angular/core';
import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { AvailabilityDTO, BranchDTO, ShoppingCartItemDTO } from '@swagger/checkout';
import { map, mergeMap, switchMap, withLatestFrom } from 'rxjs/operators';
import { DomainAvailabilityService } from '@domain/availability';
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { DomainCheckoutService } from '@domain/checkout';

interface PurchasingOptionsListModalState {
  processId: number;
  shoppingCartItems: ShoppingCartItemDTO[];
  selectedFilterOption: string;
  takeAwayAvailabilities: { [key: string]: AvailabilityDTO | true };
  pickUpAvailabilities: { [key: string]: AvailabilityDTO | true };
  deliveryAvailabilities: { [key: string]: AvailabilityDTO | true };
  deliveryB2bAvailabilities: { [key: string]: AvailabilityDTO | true };
  deliveryDigAvailabilities: { [key: string]: AvailabilityDTO | true };
  customerFeatures: { [key: string]: string };
  canAdd: { [key: string]: { message: string; status: number } };
  selectedShoppingCartItems: ShoppingCartItemDTO[];
  branches: BranchDTO[];
  currentBranch: BranchDTO;
  selectedTakeAwayBranch: BranchDTO;
  selectedPickUpBranch: BranchDTO;
}

@Injectable()
export class PurchasingOptionsListModalStore extends ComponentStore<PurchasingOptionsListModalState> {
  lastSelectedFilterOption$ = new BehaviorSubject<string>(undefined);

  branches$ = this.select((s) => s.branches);
  currentBranch$ = this.select((s) => s.currentBranch);
  takeAwayAvailabilities$ = this.select((s) => s.takeAwayAvailabilities);
  pickUpAvailabilities$ = this.select((s) => s.pickUpAvailabilities);
  deliveryAvailabilities$ = this.select((s) => s.deliveryAvailabilities);
  deliveryB2bAvailabilities$ = this.select((s) => s.deliveryB2bAvailabilities);
  canAdd$ = this.select((s) => s.canAdd);
  deliveryDigAvailabilities$ = this.select((s) => s.deliveryDigAvailabilities);

  shoppingCartItems$ = this.select((s) => s.shoppingCartItems);

  set shoppingCartItems(shoppingCartItems: ShoppingCartItemDTO[]) {
    shoppingCartItems = shoppingCartItems.sort((a, b) => a.product?.name.localeCompare(b.product.name));
    this.patchState({ shoppingCartItems });
  }

  processId$ = this.select((s) => s.processId);

  set processId(processId: number) {
    this.patchState({ processId });
  }

  customerFeatures$ = this.select((s) => s.customerFeatures);

  set customerFeatures(customerFeatures: { [key: string]: string }) {
    this.patchState({ customerFeatures });
  }

  selectedFilterOption$ = this.select((s) => s.selectedFilterOption);

  set selectedFilterOption(selectedFilterOption: string) {
    this.patchState({ selectedFilterOption });
  }

  get selectedFilterOption() {
    return this.get((s) => s.selectedFilterOption);
  }

  selectedShoppingCartItems$ = this.select((s) => s.selectedShoppingCartItems);

  get selectedShoppingCartItems() {
    return this.get((s) => s.selectedShoppingCartItems);
  }

  selectedTakeAwayBranch$ = this.select((s) => s.selectedTakeAwayBranch);

  set selectedTakeAwayBranch(selectedTakeAwayBranch: BranchDTO) {
    this.patchState({ selectedTakeAwayBranch });
  }

  selectedPickUpBranch$ = this.select((s) => s.selectedPickUpBranch);

  set selectedPickUpBranch(selectedPickUpBranch: BranchDTO) {
    this.patchState({ selectedPickUpBranch });
  }

  fetchingAvailabilities$ = combineLatest([this.takeAwayAvailabilities$, this.pickUpAvailabilities$, this.deliveryAvailabilities$]).pipe(
    map(([takeAway, pickUp, delivery]) => {
      const fetchingCheck = (obj) => {
        for (const key in obj) {
          if (Object.prototype.hasOwnProperty.call(obj, key)) {
            const element = obj[key];
            if (typeof element === 'boolean') {
              return true;
            }
          }
        }
        return false;
      };
      return !takeAway || fetchingCheck(takeAway) || !pickUp || fetchingCheck(pickUp) || !delivery || fetchingCheck(delivery);
    })
  );

  constructor(private _availabilityService: DomainAvailabilityService, private _checkoutService: DomainCheckoutService) {
    super({
      processId: undefined,
      shoppingCartItems: [],
      selectedFilterOption: undefined,
      pickUpAvailabilities: undefined,
      deliveryAvailabilities: undefined,
      takeAwayAvailabilities: undefined,
      deliveryB2bAvailabilities: undefined,
      deliveryDigAvailabilities: undefined,
      selectedShoppingCartItems: [],
      branches: [],
      currentBranch: undefined,
      selectedTakeAwayBranch: undefined,
      selectedPickUpBranch: undefined,
      customerFeatures: undefined,
      canAdd: undefined,
    });
  }

  loadAvailabilities(options: { items?: ShoppingCartItemDTO[] }) {
    const shoppingCartItems = options.items ?? this.get((s) => s.shoppingCartItems);

    for (const item of shoppingCartItems) {
      this.loadTakeAwayAvailability({ item });
      this.loadPickUpAvailability({ item });
      this.loadDeliveryAvailability({ item });
      this.loadDeliveryB2bAvailability({ item });
      this.loadDeliveryDigAvailability({ item });
    }
  }

  readonly setAvailabilityFetching = this.updater((state, { name, id, fetching }: { name: string; id: string; fetching?: boolean }) => {
    const availability = { ...state[name] };

    if (fetching) {
      availability[id] = fetching;
    } else {
      delete availability[id];
    }

    return {
      ...state,
      [name]: {
        ...availability,
      },
    };
  });

  readonly setAvailability = this.updater((state, { name, availability }: { name: string; availability: any }) => {
    const av = { ...state[name] };

    if (this._availabilityService.isAvailable({ availability })) {
      av[availability.itemId] = availability;
    }

    return {
      ...state,
      [name]: av,
    };
  });

  loadPickUpAvailability = this.effect((options$: Observable<{ item?: ShoppingCartItemDTO }>) =>
    options$.pipe(
      withLatestFrom(this.selectedPickUpBranch$),
      mergeMap(([options, branch]) => {
        this.setAvailabilityFetching({
          name: 'pickUpAvailabilities',
          id: options.item.product.catalogProductNumber,
          fetching: true,
        });

        return this._availabilityService
          .getPickUpAvailability({
            item: {
              itemId: +options.item.product.catalogProductNumber,
              ean: options.item.product.ean,
              price: options.item.availability.price,
            },
            branch,
            quantity: options.item.quantity,
          })
          .pipe(
            tapResponse(
              (availability) => {
                this.setAvailabilityFetching({
                  name: 'pickUpAvailabilities',
                  id: options.item.product.catalogProductNumber,
                });
                this.setAvailability({
                  name: 'pickUpAvailabilities',
                  availability: { ...availability, itemId: options.item.product.catalogProductNumber },
                });
              },
              () => {
                this.setAvailabilityFetching({
                  name: 'pickUpAvailabilities',
                  id: options.item.product.catalogProductNumber,
                  fetching: false,
                });
                this.setAvailability({ name: 'pickUpAvailabilities', availability: {} });
              }
            )
          );
      })
    )
  );

  loadDeliveryAvailability = this.effect((options$: Observable<{ item?: ShoppingCartItemDTO }>) =>
    options$.pipe(
      mergeMap((options) => {
        this.setAvailabilityFetching({
          name: 'deliveryAvailabilities',
          id: options.item.product.catalogProductNumber,
          fetching: true,
        });

        return this._availabilityService
          .getDeliveryAvailability({
            item: {
              itemId: +options.item.product.catalogProductNumber,
              ean: options.item.product.ean,
              price: options.item.availability.price,
            },
            quantity: options.item.quantity,
          })
          .pipe(
            tapResponse(
              (availability) => {
                this.setAvailabilityFetching({
                  name: 'deliveryAvailabilities',
                  id: options.item.product.catalogProductNumber,
                });
                this.setAvailability({
                  name: 'deliveryAvailabilities',
                  availability: { ...availability, itemId: options.item.product.catalogProductNumber },
                });
              },
              () => {
                this.setAvailabilityFetching({
                  name: 'deliveryAvailabilities',
                  id: options.item.product.catalogProductNumber,
                  fetching: false,
                });
                this.setAvailability({ name: 'deliveryAvailabilities', availability: {} });
              }
            )
          );
      })
    )
  );

  loadDeliveryB2bAvailability = this.effect((options$: Observable<{ item?: ShoppingCartItemDTO }>) =>
    options$.pipe(
      mergeMap((options) => {
        this.setAvailabilityFetching({
          name: 'deliveryB2bAvailabilities',
          id: options.item.product.catalogProductNumber,
          fetching: true,
        });

        return this._availabilityService
          .getB2bDeliveryAvailability({
            item: {
              itemId: +options.item.product.catalogProductNumber,
              ean: options.item.product.ean,
              price: options.item.availability.price,
            },
            quantity: options.item.quantity,
          })
          .pipe(
            tapResponse(
              (availability) => {
                this.setAvailabilityFetching({
                  name: 'deliveryB2bAvailabilities',
                  id: options.item.product.catalogProductNumber,
                });

                this.setAvailability({
                  name: 'deliveryB2bAvailabilities',
                  availability: { ...availability, itemId: options.item.product.catalogProductNumber },
                });
              },
              () => {
                this.setAvailabilityFetching({
                  name: 'deliveryB2bAvailabilities',
                  id: options.item.product.catalogProductNumber,
                  fetching: false,
                });
                this.setAvailability({ name: 'deliveryB2bAvailabilities', availability: {} });
              }
            )
          );
      })
    )
  );

  loadDeliveryDigAvailability = this.effect((options$: Observable<{ item?: ShoppingCartItemDTO }>) =>
    options$.pipe(
      mergeMap((options) => {
        this.setAvailabilityFetching({
          name: 'deliveryDigAvailabilities',
          id: options.item.product.catalogProductNumber,
          fetching: true,
        });

        return this._availabilityService
          .getDigDeliveryAvailability({
            item: {
              itemId: +options.item.product.catalogProductNumber,
              ean: options.item.product.ean,
              price: options.item.availability.price,
            },
            quantity: options.item.quantity,
          })
          .pipe(
            tapResponse(
              (availability) => {
                this.setAvailabilityFetching({
                  name: 'deliveryDigAvailabilities',
                  id: options.item.product.catalogProductNumber,
                });
                this.setAvailability({
                  name: 'deliveryDigAvailabilities',
                  availability: { ...availability, itemId: options.item.product.catalogProductNumber },
                });
              },
              () => {
                this.setAvailabilityFetching({
                  name: 'deliveryDigAvailabilities',
                  id: options.item.product.catalogProductNumber,
                  fetching: false,
                });
                this.setAvailability({ name: 'deliveryDigAvailabilities', availability: {} });
              }
            )
          );
      })
    )
  );

  loadTakeAwayAvailability = this.effect((options$: Observable<{ item?: ShoppingCartItemDTO }>) =>
    options$.pipe(
      withLatestFrom(this.selectedTakeAwayBranch$),
      mergeMap(([options, branch]) => {
        this.setAvailabilityFetching({
          name: 'takeAwayAvailabilities',
          id: options.item.product.catalogProductNumber,
          fetching: true,
        });

        return this._availabilityService
          .getTakeAwayAvailabilityByBranch({
            itemId: +options.item.product.catalogProductNumber,
            price: options.item.availability.price,
            quantity: options.item.quantity,
            branch,
          })
          .pipe(
            tapResponse(
              (availability) => {
                this.setAvailabilityFetching({
                  name: 'takeAwayAvailabilities',
                  id: options.item.product.catalogProductNumber,
                });
                this.setAvailability({
                  name: 'takeAwayAvailabilities',
                  availability: { ...availability, itemId: options.item.product.catalogProductNumber },
                });
              },
              () => {
                this.setAvailabilityFetching({
                  name: 'takeAwayAvailabilities',
                  id: options.item.product.catalogProductNumber,
                  fetching: false,
                });
                this.setAvailability({ name: 'takeAwayAvailabilities', availability: {} });
              }
            )
          );
      })
    )
  );

  loadBranches = this.effect(($) =>
    $.pipe(
      switchMap(() =>
        this._availabilityService.getBranches().pipe(
          map((branches) =>
            branches.filter(
              (branch) => branch.status === 1 && branch.branchType === 1 && branch.isOnline === true && branch.isShippingEnabled === true
            )
          ),
          withLatestFrom(this._availabilityService.getCurrentBranch()),
          tapResponse(
            ([branches, currentBranch]) => {
              this.patchState({
                branches,
                selectedTakeAwayBranch: currentBranch,
                selectedPickUpBranch: currentBranch,
                currentBranch,
              });
              this.loadAvailabilities({});
            },
            () =>
              this.patchState({
                branches: [],
                selectedTakeAwayBranch: undefined,
                selectedPickUpBranch: undefined,
                currentBranch: undefined,
              })
          )
        )
      )
    )
  );

  checkCanAddItems = this.effect((items$: Observable<ShoppingCartItemDTO[]>) =>
    items$.pipe(
      withLatestFrom(
        this.processId$,
        this.selectedFilterOption$,
        this.takeAwayAvailabilities$,
        this.pickUpAvailabilities$,
        this.deliveryAvailabilities$,
        this.deliveryB2bAvailabilities$,
        this.deliveryDigAvailabilities$
      ),
      mergeMap(([items, processId, selectedOption, takeAway, pickUp, delivery, deliveryB2b, deliveryDig]) => {
        let orderType: string;
        const payload = items.map((item) => {
          switch (selectedOption) {
            case 'take-away':
              orderType = 'Rücklage';
              return {
                availabilities: [this.getOlaAvailability(takeAway[item.product.catalogProductNumber], item)],
                id: item.product.catalogProductNumber,
              };
            case 'pick-up':
              orderType = 'Abholung';
              return {
                availabilities: [this.getOlaAvailability(pickUp[item.product.catalogProductNumber], item)],
                id: item.product.catalogProductNumber,
              };
            case 'delivery':
              orderType = 'Versand';
              if (
                deliveryDig[item.product.catalogProductNumber] &&
                deliveryB2b[item.product.catalogProductNumber] &&
                delivery[item.product.catalogProductNumber]
              ) {
                return {
                  availabilities: [this.getOlaAvailability(delivery[item.product.catalogProductNumber], item)],
                  id: item.product.catalogProductNumber,
                };
              } else if (deliveryDig[item.product.catalogProductNumber]) {
                return {
                  availabilities: [this.getOlaAvailability(deliveryDig[item.product.catalogProductNumber], item)],
                  id: item.product.catalogProductNumber,
                };
              } else if (deliveryB2b[item.product.catalogProductNumber]) {
                return {
                  availabilities: [this.getOlaAvailability(deliveryB2b[item.product.catalogProductNumber], item)],
                  id: item.product.catalogProductNumber,
                };
              }
              break;
          }
        });

        return this._checkoutService.canAddItems({ processId, payload, orderType }).pipe(
          tapResponse(
            (result: any) => {
              const canAdd = {};

              result?.forEach((r) => {
                canAdd[r.id] = { message: r.message, status: r.status };
              });

              this.patchState({ canAdd });
            },
            (error: Error) => {
              const canAdd = {};

              items?.forEach((i) => {
                canAdd[i.product?.catalogProductNumber] = { message: error?.message };
              });

              this.patchState({ canAdd });
            }
          )
        );
      })
    )
  );

  getOlaAvailability(availability: AvailabilityDTO, item: ShoppingCartItemDTO) {
    return {
      qty: item.quantity,
      ean: item.product.ean,
      itemId: item.product.catalogProductNumber,
      format: item.product.format,
      at: availability?.estimatedShippingDate,
      isPrebooked: availability?.isPrebooked,
      status: availability?.availabilityType,
      logisticianId: availability?.logistician?.id,
      price: availability?.price,
      ssc: availability?.ssc,
      sscText: availability?.sscText,
      supplierId: availability?.supplier?.id,
    };
  }

  readonly updateItemQuantity = this.updater((state, value: { itemId: number; quantity: number }) => {
    const itemToUpdate = state.shoppingCartItems.find((item) => item.id === value.itemId);
    const otherItems = state.shoppingCartItems.filter((item) => item.id !== value.itemId);
    const updatedItem = { ...itemToUpdate, quantity: value.quantity };
    const shoppingCartItems = [...otherItems, updatedItem].sort((a, b) => a.product?.name.localeCompare(b.product.name));

    // Ausgewählte Items auch aktualisieren
    let selectedShoppingCartItems = state.selectedShoppingCartItems;
    if (state.selectedShoppingCartItems.find((item) => item.id === value.itemId)) {
      const selectedItems = state.selectedShoppingCartItems.filter((item) => item.id !== value.itemId);
      selectedShoppingCartItems = [...selectedItems, updatedItem].sort((a, b) => a.product?.name.localeCompare(b.product.name));
    }

    return {
      ...state,
      shoppingCartItems,
      selectedShoppingCartItems,
    };
  });

  async removeShoppingCartItem(item: ShoppingCartItemDTO) {
    const items = this.get((s) => s.shoppingCartItems);
    const processId = this.get((s) => s.processId);

    await this._checkoutService
      .updateItemInShoppingCart({
        processId,
        shoppingCartItemId: item.id,
        update: {
          quantity: 0,
          availability: null,
        },
      })
      .toPromise();
    this.selectShoppingCartItem([item], false);
    const shoppingCartItems = items.filter((i) => i.id !== item.id);
    this.patchState({ shoppingCartItems });
  }

  selectShoppingCartItem(shoppingCartItems: ShoppingCartItemDTO[], selected: boolean) {
    if (selected) {
      this.patchState({
        selectedShoppingCartItems: [
          ...this.selectedShoppingCartItems.filter((item) => !shoppingCartItems.find((i) => item.id === i.id)),
          ...shoppingCartItems,
        ],
      });
    } else {
      this.patchState({
        selectedShoppingCartItems: this.selectedShoppingCartItems.filter((item) => !shoppingCartItems.find((i) => item.id === i.id)),
      });
    }
  }

  clearSelectedShoppingCartItems() {
    this.patchState({ selectedShoppingCartItems: [] });
  }
}
