import { Injectable } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { ApplicationService } from '@core/application';
import { DomainAvailabilityService } from '@domain/availability';
import { DomainCatalogService } from '@domain/catalog';
import { DomainCheckoutService } from '@domain/checkout';
import { DomainOmsService } from '@domain/oms';
import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { ItemDTO } from '@swagger/cat';
import { AddToShoppingCartDTO, AvailabilityDTO, BranchDTO, DestinationDTO, PriceDTO, ProductDTO, PromotionDTO } from '@swagger/checkout';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { Observable } from 'rxjs';
import { first, map, switchMap, tap, withLatestFrom } from 'rxjs/operators';

interface CheckoutDummyState {
  item: ItemDTO;
  shoppingCartItemId: number;
  addToCartItem: AddToShoppingCartDTO;
  estimatedShippingDate: string;
  message: string;
  query: string;
  fetching: boolean;
}

@Injectable()
export class CheckoutDummyStore extends ComponentStore<CheckoutDummyState> {
  get item() {
    return this.get((s) => s.item);
  }

  set item(item: ItemDTO) {
    if (this.item !== item) {
      this.patchState({ item });
    }
  }

  readonly item$ = this.select((s) => s.item);

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

  set shoppingCartItemId(shoppingCartItemId: number) {
    if (this.shoppingCartItemId !== shoppingCartItemId) {
      this.patchState({ shoppingCartItemId });
    }
  }

  readonly shoppingCartItemId$ = this.select((s) => s.shoppingCartItemId);

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

  set addToCartItem(addToCartItem: AddToShoppingCartDTO) {
    if (this.addToCartItem !== addToCartItem) {
      this.patchState({ addToCartItem });
    }
  }

  readonly addToCartItem$ = this.select((s) => s.addToCartItem);

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

  set query(query: string) {
    if (this.query !== query) {
      this.patchState({ query });
    }
  }

  readonly query$ = this.select((s) => s.query);

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

  set fetching(fetching: boolean) {
    this.patchState({ fetching });
  }

  readonly fetching$ = this.select((s) => s.fetching);

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

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

  readonly message$ = this.select((s) => s.message);

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

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

  readonly processId$ = this._application.activatedProcessId$;

  readonly customer$ = this.processId$.pipe(switchMap((processId) => this._checkoutService.getBuyer({ processId })));

  readonly customerFeatures$ = this.processId$.pipe(switchMap((processId) => this._checkoutService.getCustomerFeatures({ processId })));

  readonly customerFilter$ = this.customerFeatures$.pipe(
    withLatestFrom(this.processId$),
    switchMap(([customerFeatures, processId]) => this._checkoutService.canSetCustomer({ processId, customerFeatures })),
    map((res) => res.filter)
  );

  readonly estimatedShippingDate$ = this.select((s) => s.estimatedShippingDate);

  readonly vats$ = this._omsService.getVATs();
  readonly suppliers$ = this._availabilityService.getSuppliers().pipe(
    map((suppliers) =>
      suppliers
        .filter((supplier) => {
          const displaySupplierIds = [2, 3, 4, 5, 6, 8, 10, 13, 15, 16];
          return displaySupplierIds.find((id) => id === supplier.id);
        })
        .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))
    )
  );
  readonly currentBranch$ = this._availabilityService.getCurrentBranch();

  constructor(
    private _application: ApplicationService,
    private _omsService: DomainOmsService,
    private _checkoutService: DomainCheckoutService,
    private _availabilityService: DomainAvailabilityService,
    private _catalogService: DomainCatalogService,
    private _modal: UiModalService
  ) {
    super({
      item: undefined,
      shoppingCartItemId: undefined,
      addToCartItem: undefined,
      estimatedShippingDate: '',
      query: '',
      message: '',
      fetching: false,
    });
  }

  search = this.effect(($) =>
    $.pipe(
      tap((_) => this.patchState({ fetching: true })),
      withLatestFrom(this.query$),
      switchMap(([_, ean]) =>
        this.searchRequest(ean).pipe(
          tapResponse(
            (res) => {
              const item = res.result[0];
              if (!!item && item?.product?.format !== 'EB' && item?.product?.format !== 'DL') {
                this.patchState({
                  item: res.result[0],
                  message: '',
                  fetching: false,
                });
              } else {
                this.patchState({
                  item: undefined,
                  message: 'Keine Suchergebnisse',
                  fetching: false,
                });
              }
            },
            (error: Error) => {
              this._modal.open({
                title: 'Fehler bei der Suche nach der EAN',
                content: UiErrorModalComponent,
                data: error,
              });
              this.patchState({ fetching: false, message: '' });
              console.error('CheckoutDummyStore.search()', error);
            }
          )
        )
      )
    )
  );

  addToCart = this.effect((cb$: Observable<Function>) =>
    cb$.pipe(
      tap((_) => this.patchState({ fetching: true })),
      withLatestFrom(this.processId$, this.addToCartItem$),
      switchMap(([cb, processId, newItem]) =>
        this.addToCartRequest(processId, newItem).pipe(
          tapResponse(
            (res) => {
              this.patchState({
                fetching: false,
              });
              cb?.call(undefined);
            },
            (error: Error) => {
              this._modal.open({
                title: 'Fehler beim Hinzufügen zum Warenkorb',
                content: UiErrorModalComponent,
                data: error,
              });
              this.patchState({ fetching: false });
              console.error('CheckoutDummyStore.addToCart()', error);
            }
          )
        )
      )
    )
  );

  updateCart = this.effect((cb$: Observable<Function>) =>
    cb$.pipe(
      tap((_) => this.patchState({ fetching: true })),
      withLatestFrom(this.processId$, this.addToCartItem$, this.shoppingCartItemId$),
      switchMap(([cb, processId, newItem, shoppingCartItemId]) => {
        const availability = newItem.availability;
        const quantity = newItem.quantity;
        const destination = newItem.destination;
        return this.updateCartRequest({ processId, shoppingCartItemId, availability, quantity, destination }).pipe(
          tapResponse(
            (res) => {
              this.patchState({
                fetching: false,
              });
              cb?.call(undefined);
            },
            (error: Error) => {
              this._modal.open({
                title: 'Fehler beim Updaten des Warenkorbs',
                content: UiErrorModalComponent,
                data: error,
              });
              this.patchState({ fetching: false });
              console.error('CheckoutDummyStore.updateCart()', error);
            }
          )
        );
      })
    )
  );

  searchRequest(ean: string) {
    const queryToken = {
      input: {
        qs: ean,
      },
      take: 1,
      friendlyName: ean,
      doNotTrack: true,
    };
    return this._catalogService.search({ queryToken });
  }

  addToCartRequest(processId: number, newItem: AddToShoppingCartDTO) {
    return this._checkoutService.addItemToShoppingCart({ processId, items: [newItem] });
  }

  updateCartRequest({
    processId,
    shoppingCartItemId,
    availability,
    quantity,
    destination,
  }: {
    processId: number;
    shoppingCartItemId: number;
    availability: AvailabilityDTO;
    quantity: number;
    destination: DestinationDTO;
  }) {
    return this._checkoutService.updateItemInShoppingCart({
      processId,
      shoppingCartItemId,
      update: {
        availability,
        quantity,
        destination,
      },
    });
  }

  async createAddToCartItem(control: FormGroup, branch: BranchDTO, update?: boolean) {
    let item: ItemDTO;
    const quantity = Number(control.get('quantity').value);
    const price = this._createPriceDTO(control);
    let promoPoints: number;
    // Check if item exists or ean inside the control changed in the meantime
    if (!!this.item && this.item.product.ean === control.get('ean').value) {
      item = this.item;
      promoPoints = await this._getPromoPoints({ itemId: item.id, quantity, price: price.value.value });
    } else {
      item = undefined;
    }
    const availability = this._createAvailabilityDTO({ price, control });
    const product = this._createProductDTO({ item, control });
    const newItem: AddToShoppingCartDTO = {
      quantity,
      availability,
      product,
      promotion: !!item ? { points: promoPoints } : undefined,
      destination: {
        data: { target: 1, targetBranch: { id: branch.id } },
      },
    };

    if (update) {
      const shoppingCart = await this._checkoutService
        .getShoppingCart({ processId: this._application.activatedProcessId })
        .pipe(first())
        .toPromise();
      const existingItem = shoppingCart?.items?.find(
        (i) => i?.data?.product?.ean === i?.data?.product?.ean && i?.data?.features['orderType'] === 'Abholung'
      );
      this.patchState({ addToCartItem: newItem, shoppingCartItemId: existingItem?.id });
    }
    this.patchState({ addToCartItem: newItem });
  }

  private async _getPromoPoints({ itemId, quantity, price }: { itemId: number; quantity: number; price: number }) {
    let points: number;
    try {
      points = await this._catalogService
        .getPromotionPoints({
          items: [
            {
              id: itemId,
              quantity,
              price,
            },
          ],
        })
        .pipe(
          first(),
          map((response) => response.result.itemId)
        )
        .toPromise();
    } catch (error) {
      this._modal.open({
        title: 'Fehler beim Abfragen der Lesepunkte',
        content: UiErrorModalComponent,
        data: error,
      });
      console.error('CheckoutDummyStore._getPromoPoints()', error);
    }
    return points;
  }

  private _createPriceDTO(control: FormGroup): PriceDTO {
    return {
      value: {
        value: Number(String(control.get('price').value).replace(',', '.')),
        currency: 'EUR',
      },
      vat: {
        vatType: control.get('vat').value || null,
      },
    };
  }

  private _createAvailabilityDTO({ price, control }: { price: PriceDTO; control: FormGroup }): AvailabilityDTO {
    return {
      availabilityType: 1024,
      supplier: {
        id: control.get('supplier').value,
      },
      price,
      estimatedShippingDate: control.get('estimatedShippingDate').value || null,
      supplyChannel: 'MANUALLY',
    };
  }

  private _createProductDTO({ item, control }: { item?: ItemDTO; control: FormGroup }): ProductDTO {
    const formValues: Partial<ProductDTO> = {
      ean: control.get('ean').value,
      name: control.get('name').value,
      contributors: control.get('contributors').value,
      manufacturer: control.get('manufacturer').value,
    };
    return !!item
      ? {
          catalogProductNumber: String(item.id),
          ...item.product,
          ...formValues,
        }
      : {
          catalogProductNumber: '',
          ...formValues,
        };
  }
}
