import { Injectable } from '@angular/core';
import { ApplicationService } from '@core/application';
import { DomainAvailabilityService } from '@domain/availability';
import { DomainCheckoutService } from '@domain/checkout';
import { CrmCustomerService } from '@domain/crm';
import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { ItemDTO } from '@swagger/cat';
import { AvailabilityDTO, BranchDTO, OLAAvailabilityDTO, ShoppingCartItemDTO, VATType } from '@swagger/checkout';
import { isBoolean, isNullOrUndefined, isString } from '@utils/common';
import { NEVER, Observable } from 'rxjs';
import { delay, filter, map, switchMap, tap, withLatestFrom } from 'rxjs/operators';

export type PurchasingOptions = 'take-away' | 'pick-up' | 'delivery' | 'dig-delivery' | 'b2b-delivery' | 'download';

interface PurchasingOptionsModalState {
  item?: ItemDTO;
  shoppingCartItem?: ShoppingCartItemDTO;
  option?: PurchasingOptions;
  defaultBranch?: BranchDTO;
  branch?: BranchDTO;
  processId?: number;
  fetchingAvailability: boolean;
  availableOptions: PurchasingOptions[];
  availableBranches: BranchDTO[];
  quantity: number;
  maxQuantityError: boolean;
  checkingCanAdd: boolean;
  canAdd: boolean;
  canAddError?: string;
  canUpgrade: boolean;
  availabilities: { [key: string]: AvailabilityDTO };
  customPrice?: number;
  customVat?: VATType;
}

@Injectable()
export class PurchasingOptionsModalStore extends ComponentStore<PurchasingOptionsModalState> {
  readonly selectItem = this.select((s) => s.item);

  readonly selectShoppingCartItem = this.select((s) => s.shoppingCartItem);

  readonly selectOption = this.select((s) => s.option);

  readonly selectOrderType = this.select(this.selectOption, (option) => {
    switch (option) {
      case 'take-away':
        return 'Rücklage';
      case 'pick-up':
        return 'Abholung';
      case 'delivery':
        return 'Versand';
      case 'b2b-delivery':
        return 'B2B-Versand';
      case 'dig-delivery':
        return 'DIG-Versand';
      case 'download':
        return 'Download';
    }
  });

  readonly selectHasOption = this.select((s) => !!s.option);

  readonly selectBranch = this.select((s) => {
    return s.branch || s.defaultBranch;
  });

  readonly selectQuantity = this.select((s) => s.quantity);

  readonly selectCustomPrice = this.select((s) => s.customPrice);

  readonly selectCustomVat = this.select((s) => s.customVat);

  readonly selectAvailabilities = this.select((s) => s.availabilities);

  readonly selectAvailability = this.select((s) => s.availabilities[s.option]);

  readonly selectAvailabilityIsValid = this.select(this.selectAvailability, (availability) =>
    this.availabilityService.isAvailable({ availability })
  );

  readonly selectAvailableOptions = this.select((s) => s.availableOptions);

  readonly selectAvailableBranches = this.select((s) => s.availableBranches);

  readonly selectProcessId = this.select((s) => s.processId);

  readonly selectCheckingCanAdd = this.select((s) => s.checkingCanAdd);

  readonly selectCanAdd = this.select(
    this.selectAvailability,
    this.select((s) => s.canAdd),
    (availability, canAdd) => canAdd && this.availabilityService.isAvailable({ availability })
  );

  readonly selectCanAddError = this.select((s) => s.canAddError);

  readonly selectFetchingAvailability = this.select((s) => s.fetchingAvailability);

  readonly selectMaxQuantityError = this.select((s) => s.maxQuantityError);

  readonly selectQuantityError = this.select(
    this.selectQuantity,
    this.selectOption,
    this.selectAvailability,
    this.selectFetchingAvailability,
    this.selectMaxQuantityError,
    (quantity, option, availability, fetching, maxQuantityError) => {
      if (!fetching) {
        if (maxQuantityError) {
          return `Achtung, Maximal 999 Exemplare bestellbar.`;
        }

        if (availability?.inStock < quantity) {
          if (option === 'pick-up') {
            return `${availability?.inStock} Exemplare sofort lieferbar.`;
          }
          if (option === 'take-away') {
            return `${availability?.inStock} Exemplare sofort lieferbar.`;
          }
        }

        if (!this.availabilityService.isAvailable({ availability })) {
          return availability?.sscText;
        }
      }

      return undefined;
    }
  );

  readonly selectOlaAvailability = this.select(
    (s): OLAAvailabilityDTO =>
      this.availabilityService.mapToOlaAvailability({
        availability: s.availabilities[s.option],
        item: s.item,
        quantity: s.quantity,
      })
  );

  readonly selectCanUpgrade = this.select((s) => s.canUpgrade);

  readonly selectCanContinueShopping = this.select(
    this.selectFetchingAvailability,
    this.selectCanAdd,
    this.selectOption,
    this.selectQuantityError,
    this.selectQuantity,
    this.selectCanUpgrade,
    (fetching, canAdd, option, quantityError, quantity, canUpgrade) => {
      let hasError = !!quantityError;

      if (option === 'pick-up' && quantity <= 999) {
        hasError = false;
      }
      return !fetching && (canAdd || canUpgrade);
    }
  ).pipe(delay(1));

  readonly selectCanContinueShoppingIsLoading = this.select(this.selectFetchingAvailability, this.selectCanAdd, (fetching, canAdd) => {
    return fetching;
  });

  constructor(
    private checkoutService: DomainCheckoutService,
    private availabilityService: DomainAvailabilityService,
    private customerService: CrmCustomerService,
    private applicationService: ApplicationService
  ) {
    super({
      availableBranches: [],
      availableOptions: [],
      quantity: 1,
      canAdd: false,
      canUpgrade: false,
      availabilities: {},
      checkingCanAdd: false,
      fetchingAvailability: false,
      maxQuantityError: false,
    });

    this.loadDefaultBranch();
  }

  readonly setItem = this.updater((state, item: ItemDTO) => {
    this.loadAvailability();
    return {
      ...state,
      item,
      availability: undefined,
      canAdd: false,
      canAddError: undefined,
    };
  });

  readonly setShoppingCartItem = this.updater((state, shoppingCartItem: ShoppingCartItemDTO) => {
    this.loadAvailability();
    return {
      ...state,
      shoppingCartItem,
    };
  });

  readonly setOption = this.updater((state, option: PurchasingOptions) => {
    this.loadAvailability();
    return {
      ...state,
      option,
      availability: undefined,
      canAdd: false,
      canAddError: undefined,
    };
  });

  readonly setBranch = this.updater((state, branch: BranchDTO) => {
    this.loadAvailability();
    return {
      ...state,
      branch,
      availability: undefined,
      canAdd: false,
      canAddError: undefined,
    };
  });

  readonly setBranchId = this.updater((state, branchId: number) => {
    const branch = state.availableBranches.find((branch) => branch.id === branchId);

    this.loadAvailability();
    return {
      ...state,
      branchId,
      branch,
    };
  });

  readonly setAvailability = this.updater(
    (state, { availability, option, item }: { availability: AvailabilityDTO; option: PurchasingOptions; item: ItemDTO }) => {
      this.checkCanAdd();
      let updatedAvailability = availability;
      if ((option && option === 'delivery') || option === 'dig-delivery') {
        const catalogPrice = item?.catalogAvailability?.price?.value?.value;
        const availabilityPrice = availability?.price?.value?.value;
        const updatedPrice = catalogPrice <= availabilityPrice ? catalogPrice : availabilityPrice;
        updatedAvailability = {
          ...availability,
          price: {
            ...availability.price,
            value: {
              ...availability.price.value,
              value: updatedPrice,
            },
          },
        };
      }
      return {
        ...state,
        availabilities: {
          ...state.availabilities,
          [option]: updatedAvailability,
        },
      };
    }
  );

  readonly setQuantity = this.updater((state, quantity: number = 1) => {
    let qty = quantity;
    if (quantity > 999) {
      qty = 999;
      this.patchState({ maxQuantityError: true });
    } else {
      this.patchState({ maxQuantityError: false });
    }

    this.loadAvailability();
    return {
      ...state,
      quantity: qty,
      canAdd: false,
      canAddError: undefined,
    };
  });

  readonly setCustomPrice = this.updater((state, customPrice: number) => {
    return {
      ...state,
      customPrice,
    };
  });

  readonly setCustomVat = this.updater((state, customVat: VATType) => {
    return {
      ...state,
      customVat,
    };
  });

  readonly setAvailableOptions = this.updater((state, availableOptions: PurchasingOptions[]) => {
    let option = state.option;
    if (availableOptions?.length === 1 && availableOptions[0] === 'download') {
      option = availableOptions[0];
    }

    return {
      ...state,
      availableOptions,
      option,
    };
  });

  readonly setAvailableBranches = this.updater((state, availableBranches: BranchDTO[]) => {
    const branch = state.branch || state.defaultBranch;
    return {
      ...state,
      availableBranches,
      branch,
    };
  });

  readonly setProcessId = this.updater((state, processId: number) => ({
    ...state,
    processId,
  }));

  readonly setCanAdd = this.updater((state, canAddItem: true | string) => {
    let canAdd = isBoolean(canAddItem) ? Boolean(canAddItem) : false;
    if (!canAdd) {
      this.checkCanUpgrade();
    }
    return { ...state, canAdd, canAddError: isString(canAddItem) ? String(canAddItem) : undefined };
  });

  readonly setAvailabilities = this.updater((state, availabilities: { [key: string]: AvailabilityDTO }) => {
    this.checkCanAdd();
    return {
      ...state,
      availabilities: {
        ...state.availabilities,
        ...availabilities,
      },
    };
  });

  loadBranches = this.effect((branchId$: Observable<number>) =>
    branchId$.pipe(
      switchMap((branchId) =>
        this.checkoutService.getBranches().pipe(
          tapResponse(
            (branches: BranchDTO[]) => {
              this.setAvailableBranches(branches);
              this.setBranchId(branchId);
            },
            () => this.setAvailableBranches([])
          )
        )
      )
    )
  );

  loadAvailability = this.effect(($) =>
    $.pipe(
      delay(10),
      withLatestFrom(this.selectItem, this.selectQuantity, this.selectOption, this.selectBranch),
      switchMap(([_, item, quantity, option, branch]) => {
        let availability$: Observable<AvailabilityDTO> = NEVER;

        if (!isNullOrUndefined(item) && quantity > 0 && isString(option)) {
          this.patchState({ fetchingAvailability: true });
          switch (option) {
            case 'take-away':
              availability$ = this.availabilityService.getTakeAwayAvailabilityByBranch({
                itemId: item.id,
                price: item.catalogAvailability.price,
                quantity,
                branch,
              });
              break;
            case 'pick-up':
              if (!isNullOrUndefined(branch)) {
                availability$ = this.availabilityService.getPickUpAvailability({
                  item: { itemId: item.id, ean: item.product.ean, price: item.catalogAvailability.price },
                  quantity,
                  branch,
                });
              }
              break;
            case 'delivery':
              availability$ = this.availabilityService.getDeliveryAvailability({
                item: { itemId: item.id, ean: item.product.ean, price: item.catalogAvailability.price },
                quantity,
              });
              break;
            case 'dig-delivery':
              availability$ = this.availabilityService.getDigDeliveryAvailability({
                item: { itemId: item.id, ean: item.product.ean, price: item.catalogAvailability.price },
                quantity,
              });
              break;
            case 'b2b-delivery':
              if (!isNullOrUndefined(branch)) {
                availability$ = this.availabilityService.getB2bDeliveryAvailability({
                  item: { itemId: item.id, ean: item.product.ean, price: item.catalogAvailability.price },
                  quantity,
                });
              }
              break;
            case 'download':
              availability$ = this.availabilityService.getDownloadAvailability({
                item: { itemId: item.id, ean: item.product.ean, price: item.catalogAvailability.price },
              });
              break;
          }
        }
        return availability$.pipe(
          tapResponse(
            (availability) => {
              this.setAvailability({ option, availability, item });
              this.patchState({ fetchingAvailability: false });
            },
            () => {
              this.setAvailability(null);
              this.patchState({ fetchingAvailability: false });
            }
          )
        );
      })
    )
  );

  checkCanAdd = this.effect(($) =>
    $.pipe(
      delay(10),
      withLatestFrom(this.selectOlaAvailability, this.selectProcessId, this.selectOrderType),
      switchMap(([_, availability, processId, orderType]) => {
        this.patchState({ checkingCanAdd: true });
        return this.checkoutService.canAddItems({ processId, payload: [{ availabilities: [availability] }], orderType }).pipe(
          tapResponse(
            (response: any) => {
              this.setCanAdd(response?.find((_) => true)?.status === 0 ? true : response?.find((_) => true)?.message);
            },
            (error: Error) => this.setCanAdd(error?.message)
          ),
          tap((_) => this.patchState({ checkingCanAdd: false }))
        );
      })
    )
  );

  checkCanUpgrade = this.effect(($) =>
    $.pipe(
      withLatestFrom(this.applicationService.activatedProcessId$),
      switchMap(([_, processId]) => this.checkoutService.getBuyer({ processId })),
      map((buyer) => buyer?.source),
      filter((customerId) => !isNaN(customerId)),
      switchMap((customerId) =>
        this.customerService.canUpgrade(customerId).pipe(
          tapResponse(
            (response) => {
              let canUpgrade = response.options?.values?.some((u) => u.value === 'webshop');
              this.patchState({ canUpgrade });
            },
            (error) => {
              this.patchState({ canUpgrade: false });
            }
          )
        )
      )
    )
  );

  readonly loadDefaultBranch = this.effect(($) =>
    $.pipe(
      switchMap((_) =>
        this.availabilityService.getCurrentBranch().pipe(
          tapResponse(
            (defaultBranch) => this.patchState({ defaultBranch }),
            (err) => {}
          )
        )
      )
    )
  );
}
