import { inject, Injectable } from '@angular/core';
import { ComponentStore, OnStoreInit } from '@ngrx/component-store';
import { tapResponse } from '@ngrx/operators';

import {
  AddToShoppingCartDTO,
  AvailabilityDTO,
  BranchDTO,
  CheckoutDTO,
  KulturPassResult,
  ShoppingCartDTO,
  ShoppingCartItemDTO,
} from '@generated/swagger/checkout-api';
import { DomainCheckoutService } from '@domain/checkout';
import {
  catchError,
  mergeMap,
  switchMap,
  tap,
  withLatestFrom,
} from 'rxjs/operators';
import {
  BranchService,
  DisplayOrderDTO,
  KeyValueDTOOfStringAndString,
  OrderDTO,
  OrderItemListItemDTO,
  ResponseArgsOfIEnumerableOfBranchDTO,
  ResponseArgsOfValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString,
} from '@generated/swagger/oms-api';
import { Observable, of, zip } from 'rxjs';
import { AuthService } from '@core/auth';
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
import { getCatalogProductNumber } from './catalog-product-number';
import { ItemDTO } from '@generated/swagger/cat-search-api';
import { DomainAvailabilityService } from '@domain/availability';

export interface KulturpassOrderModalState {
  orderItemListItem?: OrderItemListItemDTO;
  shoppingCart?: ShoppingCartDTO;
  fetchShoppingCart?: boolean;
  branch?: BranchDTO;
  order?: OrderDTO;
  ordering?: boolean;
  availabilities: Record<string, AvailabilityDTO>;
}

@Injectable()
export class KulturpassOrderModalStore
  extends ComponentStore<KulturpassOrderModalState>
  implements OnStoreInit
{
  private _checkoutService = inject(DomainCheckoutService);
  private _branchService = inject(BranchService);
  private _authService = inject(AuthService);
  private _availabilityService = inject(DomainAvailabilityService);
  private _modal = inject(UiModalService);

  readonly processId = Date.now();

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

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

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

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

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

  constructor() {
    super({
      availabilities: {},
    });
  }

  ngrxOnStoreInit = () => {
    this.loadBranch();
  };

  readonly orderItemListItem$ = this.select((state) => state.orderItemListItem);

  readonly shoppingCart$ = this.select((state) => state.shoppingCart);

  readonly branch$ = this.select((state) => state.branch);

  readonly order$ = this.select((state) => state.order);

  readonly updateCheckout = this.updater((state, checkout: CheckoutDTO) => ({
    ...state,
    checkout,
  }));

  readonly updateOrder = this.updater((state, order: OrderDTO) => ({
    ...state,
    order,
  }));

  readonly fetchShoppingCart$ = this.select((state) => state.fetchShoppingCart);

  readonly updateFetchShoppingCart = this.updater(
    (state, fetchShoppingCart: boolean) => ({
      ...state,
      fetchShoppingCart,
    }),
  );

  readonly ordering$ = this.select((state) => state.ordering);

  loadBranch = this.effect(($) =>
    $.pipe(
      switchMap(() =>
        this._branchService
          .BranchGetBranches({})
          .pipe(tapResponse(this.handleBranchResponse, this.handleBranchError)),
      ),
    ),
  );

  handleBranchResponse = (res: ResponseArgsOfIEnumerableOfBranchDTO) => {
    const branchNumber = this._authService.getClaimByKey('branch_no');

    this.patchState({
      branch: res.result.find((b) => b.branchNumber === branchNumber),
    });
  };

  handleBranchError = (err) => {
    this._modal.error('Fehler beim Laden der Filiale', err);
  };

  createShoppingCart = this.effect(
    (orderItemListItem$: Observable<OrderItemListItemDTO>) =>
      orderItemListItem$.pipe(
        tap((orderItemListItem) => {
          this.patchState({ orderItemListItem });
          this.updateFetchShoppingCart(true);
        }),
        switchMap((orderItemListItem) =>
          this._checkoutService
            .getShoppingCart({ processId: this.processId })
            .pipe(
              tapResponse(
                this.handleCreateShoppingCartResponse,
                this.handleCreateShoppingCartError,
              ),
            ),
        ),
      ),
  );

  handleCreateShoppingCartResponse = (res: ShoppingCartDTO) => {
    this.patchState({ shoppingCart: res });
    this._checkoutService.setBuyer({
      processId: this.processId,
      buyer: this.order.buyer,
    });
    this._checkoutService.setPayer({
      processId: this.processId,
      payer: this.order.billing?.data,
    });

    this.updateFetchShoppingCart(false);
  };

  handleCreateShoppingCartError = (err: any) => {
    this._modal.error('Fehler beim Laden des Warenkorbs', err);

    this.updateFetchShoppingCart(false);
  };

  addItem = this.effect((add$: Observable<AddToShoppingCartDTO>) =>
    add$.pipe(
      switchMap((add) =>
        this._checkoutService
          .addItemToShoppingCart({
            processId: this.processId,
            items: [add],
          })
          .pipe(
            tapResponse(this.handleAddItemResponse, this.handleAddItemError),
          ),
      ),
    ),
  );

  handleAddItemResponse = (res: ShoppingCartDTO) => {};

  handleAddItemError = (err: any) => {
    this._modal.error('Fehler beim Hinzufügen des Artikels', err);
  };

  quantityChange = this.effect((change$: Observable<ShoppingCartItemDTO>) =>
    change$.pipe(
      switchMap((change) =>
        this._checkoutService
          .updateItemInShoppingCart({
            processId: this.processId,
            shoppingCartItemId: change.id,
            update: { quantity: change.quantity },
          })
          .pipe(
            tapResponse(
              this.handleQuantityChangeResponse,
              this.handleQuantityChangeError,
            ),
          ),
      ),
    ),
  );

  handleQuantityChangeResponse = (res: ShoppingCartDTO) => {};

  handleQuantityChangeError = (err: any) => {
    this._modal.error('Fehler beim Ändern der Menge', err);
  };

  orderItems = this.effect(($: Observable<void>) =>
    $.pipe(
      tap(() => this.patchState({ ordering: true })),
      withLatestFrom(this.orderItemListItem$),
      switchMap(([_, orderItemListItem]) =>
        this._checkoutService
          .completeKulturpassOrder({
            processId: this.processId,
            orderItemSubsetId: orderItemListItem.orderItemSubsetId,
          })
          .pipe(tapResponse(this.handleOrderResponse, this.handleOrderError)),
      ),
    ),
  );

  handleOrderResponse = (
    res: ResponseArgsOfValueTupleOfIEnumerableOfDisplayOrderDTOAndIEnumerableOfKeyValueDTOOfStringAndString,
  ) => {
    this.onOrderSuccess(res.result.item1[0], res.result.item2);
  };

  onOrderSuccess = (
    displayOrder: DisplayOrderDTO,
    action: KeyValueDTOOfStringAndString[],
  ) => {};

  handleOrderError = (err: any) => {
    this._modal.error('Fehler beim Bestellen', err);
    this.patchState({ ordering: false });
  };

  itemQuantityByCatalogProductNumber(catalogProductNumber: string) {
    return (
      this.shoppingCart?.items?.find(
        (i) => getCatalogProductNumber(i?.data) === catalogProductNumber,
      )?.data?.quantity ?? 0
    );
  }

  canAddItem = this.effect((item$: Observable<ItemDTO>) =>
    item$.pipe(
      switchMap((item) =>
        this._checkoutService
          .canAddItemsKulturpass([item?.product])
          .pipe(
            tapResponse(
              (results) =>
                this.handleCanAddItemResponse({
                  item,
                  result: results?.find((_) => true),
                }),
              this.handleCanAddItemError,
            ),
          ),
      ),
    ),
  );

  handleCanAddItemResponse = ({
    item,
    result,
  }: {
    item: ItemDTO;
    result: KulturPassResult;
  }) => {
    if (result?.canAdd) {
      this.addItemToShoppingCart(item);
    } else {
      this._modal.open({
        content: UiMessageModalComponent,
        title: 'Artikel nicht förderfähig',
        data: {
          message: result?.message,
          closeAction: 'ohne Artikel fortfahren',
        },
      });
    }
  };

  handleCanAddItemError = (err: any) => {
    this._modal.error('Fehler beim Hinzufügen des Artikels', err);
  };

  addItemToShoppingCart = this.effect((item$: Observable<ItemDTO>) =>
    item$.pipe(
      mergeMap((item) => {
        const takeAwayAvailability$ =
          this._availabilityService.getTakeAwayAvailability({
            item: {
              ean: item.product.ean,
              itemId: item.id,
              price: item.catalogAvailability.price,
            },
            quantity:
              this.itemQuantityByCatalogProductNumber(
                getCatalogProductNumber(item),
              ) + 1,
          });

        const deliveryAvailability$ = this._availabilityService
          .getDeliveryAvailability({
            item: {
              ean: item.product.ean,
              itemId: item.id,
              price: item.catalogAvailability.price,
            },
            quantity:
              this.itemQuantityByCatalogProductNumber(
                getCatalogProductNumber(item),
              ) + 1,
          })
          .pipe(
            catchError((err) => {
              return of(undefined);
            }),
          );

        return zip(takeAwayAvailability$, deliveryAvailability$).pipe(
          tapResponse(
            this.handleAddItemToShoppingCartResponse2(item),
            this.handleAddItemToShoppingCartError,
          ),
        );
      }),
    ),
  );

  handleAddItemToShoppingCartResponse2 =
    (item: ItemDTO) =>
    ([takeAwayAvailability, deliveryAvailability]: [
      AvailabilityDTO,
      AvailabilityDTO,
    ]) => {
      let isPriceMaintained = item?.catalogAvailability?.priceMaintained;
      let onlinePrice = -1;

      if (deliveryAvailability) {
        isPriceMaintained =
          isPriceMaintained ?? deliveryAvailability['priceMaintained'] ?? false;
        onlinePrice = deliveryAvailability?.price?.value?.value ?? -1;
      }

      // Preis und priceMaintained werden immer erst vom Katalog genommen. Bei nicht Verfügbarkeit greifen die anderen Availabilities
      const offlinePrice =
        item?.catalogAvailability?.price?.value?.value ??
        takeAwayAvailability?.price?.value?.value ??
        -1;
      const availability = takeAwayAvailability;
      availability.price =
        item?.catalogAvailability?.price ?? takeAwayAvailability?.price;

      /**
       * Onlinepreis ist niedliger als der Offlinepreis
       * wenn der Artikel nicht Preisgebunden ist, wird der Onlinepreis genommen
       * wenn der Artikel Preisgebunden ist, wird der Ladenpreis verwendet
       */

      /**
       * Offlinepreis ist niedliger als der Onlinepreis
       * wenn der Artikel nicht Preisgebunden ist, wird der Ladenpreis genommen
       * wenn der Artikel Preisgebunden ist, wird der Ladenpreis verwendet
       */

      if (
        !!deliveryAvailability &&
        onlinePrice < offlinePrice &&
        !isPriceMaintained
      ) {
        availability.price = deliveryAvailability.price;
      }

      this.setAvailability({
        catalogProductNumber: getCatalogProductNumber(item),
        availability,
      });

      const addToShoppingCartDTO: AddToShoppingCartDTO = {
        quantity: 1,
        availability,
        destination: {
          data: {
            target: 1,
            targetBranch: { id: this.branch.id },
          },
        },
        promotion: {
          value: 0,
        },
        itemType: item.type,
        product: {
          catalogProductNumber: getCatalogProductNumber(item),
          ...item.product,
        },
      };

      this.addItem(addToShoppingCartDTO);
    };

  handleAddItemToShoppingCartError = (err: any) => {
    this._modal.error('Fehler beim Hinzufügen des Artikels', err);
  };

  setAvailability = this.updater(
    (
      state,
      data: { catalogProductNumber: string; availability: AvailabilityDTO },
    ) => {
      return {
        ...state,
        availabilities: {
          ...state.availabilities,
          [data.catalogProductNumber]: data.availability,
        },
      };
    },
  );

  getAvailability(catalogProductNumber: string): AvailabilityDTO | undefined {
    return this.get((state) => state.availabilities[catalogProductNumber]);
  }

  getAvailability$(
    catalogProductNumber: string,
  ): Observable<AvailabilityDTO | undefined> {
    return this.select((state) => state.availabilities[catalogProductNumber]);
  }
}
