import {
  Component,
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  OnInit,
  OnDestroy,
  ViewChildren,
  QueryList,
  AfterViewInit,
  TrackByFunction,
  inject,
} from '@angular/core';
import { Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { DomainAvailabilityService } from '@domain/availability';
import { DomainCheckoutService } from '@domain/checkout';
import {
  AvailabilityDTO,
  BranchDTO,
  DestinationDTO,
  ShoppingCartItemDTO,
} from '@generated/swagger/checkout-api';
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
import { PrintModalData, PrintModalComponent } from '@modal/printer';
import { delay, first, map, switchMap, takeUntil, tap } from 'rxjs/operators';
import {
  Subject,
  NEVER,
  combineLatest,
  BehaviorSubject,
  Subscription,
  firstValueFrom,
} from 'rxjs';
import { DomainCatalogService } from '@domain/catalog';
import { BreadcrumbService } from '@core/breadcrumb';
import { DomainPrinterService } from '@domain/printer';
import { CheckoutDummyComponent } from '../checkout-dummy/checkout-dummy.component';
import { CheckoutDummyData } from '../checkout-dummy/checkout-dummy-data';
import { PurchaseOptionsModalService } from '@modal/purchase-options';
import {
  CheckoutNavigationService,
  ProductCatalogNavigationService,
} from '@shared/services/navigation';
import { EnvironmentService } from '@core/environment';
import { CheckoutReviewStore } from './checkout-review.store';
import { ToasterService } from '@shared/shell';
import { ShoppingCartItemComponent } from './shopping-cart-item/shopping-cart-item.component';
import { CustomerSearchNavigation } from '@shared/services/navigation';

@Component({
  selector: 'page-checkout-review',
  templateUrl: 'checkout-review.component.html',
  styleUrls: ['checkout-review.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  standalone: false,
})
export class CheckoutReviewComponent
  implements OnInit, OnDestroy, AfterViewInit
{
  #checkoutService = inject(DomainCheckoutService);

  private _onDestroy$ = new Subject<void>();

  private _customerSearchNavigation = inject(CustomerSearchNavigation);
  checkingOla$ = new BehaviorSubject<boolean>(false);

  payer$ = this._store.payer$;

  buyer$ = this._store.buyer$;

  shoppingCart$ = this._store.shoppingCart$;

  fetching$ = this._store.fetching$;

  notificationsControl = this._store.notificationsControl;

  shoppingCartItemsWithoutOrderType$ = this._store.shoppingCartItems$.pipe(
    takeUntil(this._store.orderCompleted),
    map((items) =>
      items?.filter((item) => item?.features?.orderType === undefined),
    ),
  );

  trackByGroupedItems: TrackByFunction<{
    orderType: string;
    destination: DestinationDTO;
    items: ShoppingCartItemDTO[];
  }> = (_, item) => item?.orderType + item?.destination?.id;

  groupedItems$ = this._store.shoppingCartItems$.pipe(
    takeUntil(this._store.orderCompleted),
    map((items) =>
      items.reduce(
        (grouped, item) => {
          const index = grouped.findIndex((g) =>
            item?.availability?.supplyChannel === 'MANUALLY'
              ? g?.orderType === 'Dummy'
              : item?.features?.orderType === 'DIG-Versand'
                ? g?.orderType === 'Versand'
                : g?.orderType === item?.features?.orderType,
          );

          let group = index !== -1 ? grouped[index] : undefined;
          if (!group) {
            group = {
              orderType:
                item?.availability?.supplyChannel === 'MANUALLY'
                  ? 'Dummy'
                  : item?.features?.orderType === 'DIG-Versand'
                    ? 'Versand'
                    : item?.features?.orderType,
              destination: item?.destination?.data,
              items: [],
            };
          }

          group.items = [...group.items, item]?.sort(
            (a, b) =>
              a.destination?.data?.targetBranch?.id -
                b.destination?.data?.targetBranch?.id ||
              a.product?.name.localeCompare(b.product?.name),
          );

          if (index !== -1) {
            grouped[index] = group;
          } else {
            grouped.push(group);
          }

          return [...grouped].sort((a, b) =>
            a?.orderType === undefined
              ? -1
              : b?.orderType === undefined
                ? 1
                : 0,
          );
        },
        [] as {
          orderType: string;
          destination: DestinationDTO;
          items: ShoppingCartItemDTO[];
        }[],
      ),
    ),
  );

  orderTypesExist$ = this.groupedItems$.pipe(
    map((groups) => groups?.every((group) => group?.orderType !== undefined)),
  );

  totalItemCount$ = this._store.shoppingCartItems$.pipe(
    takeUntil(this._store.orderCompleted),
    map((items) => items.reduce((total, item) => total + item.quantity, 0)),
  );

  totalReadingPoints$ = this._store.shoppingCartItems$.pipe(
    switchMap((displayOrders) => {
      if (displayOrders.length === 0) {
        return NEVER;
      }
      const items = displayOrders
        .map((i) => {
          if (i?.product?.catalogProductNumber) {
            return {
              id: Number(i.product?.catalogProductNumber),
              quantity: i.quantity,
              price: i.unitPrice?.value?.value,
            };
          }
        })
        .filter((item) => item !== undefined);
      if (items.length !== 0) {
        return this.domainCatalogService
          .getPromotionPoints({
            items,
          })
          .pipe(
            map((response) =>
              Object.values(response.result).reduce(
                (sum, points) => sum + points,
                0,
              ),
            ),
          );
      } else {
        return NEVER;
      }
    }),
  );

  customerFeatures$ = this._store.customerFeatures$;

  checkNotificationChannelControl$ =
    this._store.checkNotificationChannelControl$;

  showQuantityControlSpinnerItemId: number;
  quantityError$ = new BehaviorSubject<{ [key: string]: string }>({});

  primaryCtaLabel$ = combineLatest([
    this.payer$,
    this.buyer$,
    this.shoppingCartItemsWithoutOrderType$,
  ]).pipe(
    map(([payer, buyer, shoppingCartItemsWithoutOrderType]) => {
      if (shoppingCartItemsWithoutOrderType?.length > 0) {
        return 'Kaufoptionen';
      }
      if (!(payer || buyer)) {
        return 'Weiter';
      }
      return 'Bestellen';
    }),
  );

  primaryCtaDisabled$ = this.quantityError$.pipe(
    map((quantityError) => {
      for (const key in quantityError) {
        if (Object.prototype.hasOwnProperty.call(quantityError, key)) {
          return true;
        }
      }
      return this.showOrderButtonSpinner;
    }),
  );

  loadingOnItemChangeById$ = new Subject<number>();
  loadingOnQuantityChangeById$ = new Subject<number>();
  showOrderButtonSpinner: boolean;

  checkoutIsInValid$ = this.applicationService.activatedProcessId$.pipe(
    takeUntil(this._onDestroy$),
    switchMap((processId) =>
      this.domainCheckoutService.checkoutIsValid({ processId }),
    ),
    map((valid) => !valid),
  );

  get productSearchBasePath() {
    return this._productNavigationService.getArticleSearchBasePath(
      this.applicationService.activatedProcessId,
    ).path;
  }

  get isDesktop$() {
    return this._environmentService.matchDesktopLarge$;
  }

  @ViewChildren(ShoppingCartItemComponent)
  private _shoppingCartItems: QueryList<ShoppingCartItemComponent>;

  olaCheckSubscription: Subscription;

  trackByItemId: TrackByFunction<ShoppingCartItemDTO> = (_, item) => item?.id;

  constructor(
    private domainCheckoutService: DomainCheckoutService,
    public applicationService: ApplicationService,
    private availabilityService: DomainAvailabilityService,
    private uiModal: UiModalService,
    private router: Router,
    private cdr: ChangeDetectorRef,
    private domainCatalogService: DomainCatalogService,
    private breadcrumb: BreadcrumbService,
    private domainPrinterService: DomainPrinterService,
    private _purchaseOptionsModalService: PurchaseOptionsModalService,
    private _productNavigationService: ProductCatalogNavigationService,
    private _navigationService: CheckoutNavigationService,
    private _environmentService: EnvironmentService,
    private _store: CheckoutReviewStore,
    private _toaster: ToasterService,
  ) {}

  async ngOnInit() {
    this.applicationService.activatedProcessId$
      .pipe(takeUntil(this._onDestroy$))
      .subscribe((_) => {
        this._store.loadShoppingCart();
      });

    await this.removeBreadcrumbs();
    await this.updateBreadcrumb();

    window['Checkout'] = {
      refreshAvailabilities: this.refreshAvailabilities.bind(this),
    };
  }

  ngAfterViewInit() {
    this.registerOlaCechk();
  }

  ngOnDestroy(): void {
    this.resetControl();
    this._onDestroy$.next();
    this._onDestroy$.complete();
    this.checkingOla$.complete();
    this.quantityError$.complete();
    this.olaCheckSubscription?.unsubscribe();
  }

  registerOlaCechk() {
    this.olaCheckSubscription?.unsubscribe();
    this.olaCheckSubscription = this.applicationService.activatedProcessId$
      .pipe(
        delay(250),
        switchMap((processId) =>
          this.domainCheckoutService.validateOlaStatus({
            processId,
          }),
        ),
      )
      .subscribe((result) => {
        if (!result) {
          this.refreshAvailabilities();
        }
      });
  }

  checkIfMultipleDestinationsForOrderTypeExist(
    targetBranch: BranchDTO,
    group: { items: ShoppingCartItemDTO[] },
    i: number,
  ) {
    return i === 0
      ? false
      : targetBranch.id !==
          group.items[i - 1].destination?.data?.targetBranch?.data.id;
  }

  async refreshAvailabilities() {
    this.checkingOla$.next(true);

    for (const itemComp of this._shoppingCartItems.toArray()) {
      await itemComp.refreshAvailability();
      await new Promise((resolve) => setTimeout(resolve, 100));
    }
    this.checkingOla$.next(false);
  }

  async updateBreadcrumb() {
    await this.breadcrumb.addOrUpdateBreadcrumbIfNotExists({
      key: this.applicationService.activatedProcessId,
      name: 'Warenkorb',
      path: this._navigationService.getCheckoutReviewPath(
        this.applicationService.activatedProcessId,
      ).path,
      tags: ['checkout', 'cart'],
      section: 'customer',
    });
  }

  async removeBreadcrumbs() {
    const checkoutDummyCrumbs = await this.breadcrumb
      .getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, [
        'checkout',
        'cart',
        'dummy',
      ])
      .pipe(first())
      .toPromise();
    checkoutDummyCrumbs.forEach(async (crumb) => {
      await this.breadcrumb.removeBreadcrumb(crumb.id, true);
    });
  }

  resetControl() {
    this._store.notificationsControl = undefined;
  }

  openDummyModal({
    data,
    changeDataFromCart = false,
  }: {
    data?: CheckoutDummyData;
    changeDataFromCart?: boolean;
  }) {
    this.uiModal.open({
      content: CheckoutDummyComponent,
      data: { ...data, changeDataFromCart },
    });
  }

  changeDummyItem({
    shoppingCartItem,
  }: {
    shoppingCartItem: ShoppingCartItemDTO;
  }) {
    this.openDummyModal({ data: shoppingCartItem, changeDataFromCart: true });
  }

  async changeItem({
    shoppingCartItem,
  }: {
    shoppingCartItem: ShoppingCartItemDTO;
  }) {
    const shoppingCart = await firstValueFrom(this.shoppingCart$);
    const modalRef = await this._purchaseOptionsModalService.open({
      tabId: this.applicationService.activatedProcessId,
      shoppingCartId: shoppingCart.id,
      items: [shoppingCartItem],
      type: 'update',
    });

    await firstValueFrom(modalRef.afterClosed$);
    // Reload Shopping Cart after modal is closed to get updated items
    this.#checkoutService.reloadShoppingCart({
      processId: this.applicationService.activatedProcessId,
    });
  }

  async openPrintModal() {
    const shoppingCart = await this.shoppingCart$.pipe(first()).toPromise();
    this.uiModal.open({
      content: PrintModalComponent,
      data: {
        printerType: 'Label',
        print: (printer) =>
          this.domainPrinterService
            .printCart({ cartId: shoppingCart.id, printer })
            .toPromise(),
      } as PrintModalData,
      config: {
        panelClass: [],
        showScrollbarY: false,
      },
    });
  }

  async updateItemQuantity({
    shoppingCartItem,
    quantity,
  }: {
    shoppingCartItem: ShoppingCartItemDTO;
    quantity: number;
  }) {
    if (quantity === shoppingCartItem.quantity) {
      return;
    }

    this.loadingOnQuantityChangeById$.next(shoppingCartItem.id);

    const shoppingCartItemPrice =
      shoppingCartItem?.availability?.price?.value?.value;
    const orderType = shoppingCartItem?.features?.orderType;
    let availability: AvailabilityDTO;

    if (quantity) {
      const branch = shoppingCartItem?.destination?.data?.targetBranch?.data;

      if (orderType) {
        switch (orderType) {
          case 'Rücklage':
            availability = await this.availabilityService
              .getTakeAwayAvailability({
                item: {
                  itemId: Number(shoppingCartItem.product.catalogProductNumber),
                  ean: shoppingCartItem.product.ean,
                  price: shoppingCartItem.availability.price,
                },
                quantity,
                branch,
              })
              .toPromise();

            // this.setQuantityError(shoppingCartItem, availability, availability?.inStock < quantity);
            break;
          case 'Abholung':
            availability = await this.availabilityService
              .getPickUpAvailability({
                branch,
                item: {
                  itemId: Number(shoppingCartItem.product.catalogProductNumber),
                  ean: shoppingCartItem.product.ean,
                  price: shoppingCartItem.availability.price,
                },
                quantity,
              })
              .pipe(map((av) => av[0]))
              .toPromise();
            break;
          case 'Versand':
            availability = await this.availabilityService
              .getDeliveryAvailability({
                item: {
                  itemId: Number(shoppingCartItem.product.catalogProductNumber),
                  ean: shoppingCartItem.product.ean,
                  price: shoppingCartItem.availability.price,
                },
                quantity,
              })
              .toPromise();
            break;
          case 'DIG-Versand':
            availability = await this.availabilityService
              .getDigDeliveryAvailability({
                item: {
                  itemId: Number(shoppingCartItem.product.catalogProductNumber),
                  ean: shoppingCartItem.product.ean,
                  price: shoppingCartItem.availability.price,
                },
                quantity,
              })
              .toPromise();
            break;
          case 'B2B-Versand':
            availability = await this.availabilityService
              .getB2bDeliveryAvailability({
                item: {
                  itemId: Number(shoppingCartItem.product.catalogProductNumber),
                  ean: shoppingCartItem.product.ean,
                  price: shoppingCartItem.availability.price,
                },
                quantity,
              })
              .toPromise();
            break;
          case 'Download':
            availability = await this.availabilityService
              .getDownloadAvailability({
                item: {
                  itemId: Number(shoppingCartItem.product.catalogProductNumber),
                  ean: shoppingCartItem.product.ean,
                  price: shoppingCartItem.availability.price,
                },
              })
              .toPromise();
            break;
        }
      }
    }

    if (quantity === 0) {
      await this.domainCheckoutService
        .updateItemInShoppingCart({
          processId: this.applicationService.activatedProcessId,
          shoppingCartItemId: shoppingCartItem.id,
          update: {
            quantity,
            availability: null,
          },
        })
        .toPromise();
    } else if (availability) {
      // Wenn das Ergebnis der Availability Abfrage keinen Preis zurückliefert (z.B. HFI Geschenkkarte), wird der Preis aus der
      // Availability vor der Abfrage verwendet
      const updateAvailability = availability?.price
        ? availability
        : { ...availability, price: shoppingCartItem.availability?.price };

      await this.domainCheckoutService
        .updateItemInShoppingCart({
          processId: this.applicationService.activatedProcessId,
          shoppingCartItemId: shoppingCartItem.id,
          update: {
            quantity,
            availability: this.compareDeliveryAndCatalogPrice(
              updateAvailability,
              orderType,
              shoppingCartItemPrice,
            ),
          },
        })
        .toPromise();
    } else {
      await this.domainCheckoutService
        .updateItemInShoppingCart({
          processId: this.applicationService.activatedProcessId,
          shoppingCartItemId: shoppingCartItem.id,
          update: {
            quantity,
          },
        })
        .toPromise();
    }

    this.loadingOnQuantityChangeById$.next(undefined);
  }

  // Bei unbekannten Kunden und DIG Bestellung findet ein Vergleich der Preise statt
  compareDeliveryAndCatalogPrice(
    availability: AvailabilityDTO,
    orderType: string,
    shoppingCartItemPrice: number,
  ) {
    if (
      ['Versand', 'DIG-Versand'].includes(orderType) &&
      shoppingCartItemPrice < availability?.price?.value?.value
    ) {
      return {
        ...availability,
        price: {
          ...availability.price,
          value: {
            ...availability.price.value,
            value: shoppingCartItemPrice,
          },
        },
      };
    }
    return availability;
  }

  async navigateToCustomerSearch(processId: number) {
    const nav = this._customerSearchNavigation.defaultRoute({ processId });

    try {
      const response = await this.customerFeatures$
        .pipe(
          first(),
          switchMap((customerFeatures) => {
            return this.domainCheckoutService.canSetCustomer({
              processId,
              customerFeatures,
            });
          }),
        )
        .toPromise();

      this.router.navigate(nav.path, {
        queryParams: { filter_customertype: response.filter.customertype },
      });
    } catch (error) {
      this.router.navigate(nav.path);
    }
  }

  async showPurchasingListModal(shoppingCartItems: ShoppingCartItemDTO[]) {
    const shoppingCart = await firstValueFrom(this.shoppingCart$);
    const ref = await this._purchaseOptionsModalService.open({
      tabId: this.applicationService.activatedProcessId,
      shoppingCartId: shoppingCart.id,
      items: shoppingCartItems,
      type: 'update',
    });

    await firstValueFrom(ref.afterClosed$);
    // Reload Shopping Cart after modal is closed to get updated items
    this.#checkoutService.reloadShoppingCart({
      processId: this.applicationService.activatedProcessId,
    });

    this.refreshAvailabilities();
  }

  async changeAddress() {
    const processId = this.applicationService.activatedProcessId;
    const customer = await this.domainCheckoutService
      .getBuyer({ processId })
      .pipe(first())
      .toPromise();
    if (!customer) {
      this.navigateToCustomerSearch(processId);
      return;
    }
    const customerId = customer.source;
    const nav = this._customerSearchNavigation.detailsRoute({
      processId,
      customerId,
    });
    this.router.navigate(nav.path);
  }

  async order() {
    const shoppingCartItemsWithoutOrderType =
      await this.shoppingCartItemsWithoutOrderType$.pipe(first()).toPromise();

    if (shoppingCartItemsWithoutOrderType?.length > 0) {
      this.showPurchasingListModal(shoppingCartItemsWithoutOrderType);
      return;
    }

    const processId = this.applicationService.activatedProcessId;
    const customer = await this.domainCheckoutService
      .getBuyer({ processId })
      .pipe(first())
      .toPromise();
    if (!customer) {
      this.navigateToCustomerSearch(processId);
    } else {
      try {
        this.showOrderButtonSpinner = true;
        // Ticket #3287 Um nur E-Mail und SMS Benachrichtigungen zu setzen und um alle anderen Benachrichtigungskanäle wie z.B. Brief zu deaktivieren
        await this._store.onNotificationChange();
        const orders = await this.domainCheckoutService
          .completeCheckout({ processId })
          .toPromise();
        const orderIds = orders.map((order) => order.id).join(',');
        this._store.orderCompleted.next();
        await this.patchProcess(processId);
        await this._navigationService
          .getCheckoutSummaryPath({ processId, orderIds })
          .navigate();
      } catch (error) {
        const response = error?.error;
        let message: string = response?.message ?? '';

        if (
          response?.invalidProperties &&
          Object.values(response?.invalidProperties)?.length
        ) {
          message += `\n${Object.values(response.invalidProperties).join('\n')}`;
        }

        if (message?.length) {
          this.uiModal.open({
            content: UiMessageModalComponent,
            title: 'Hinweis',
            data: { message: message.trim() },
          });
        } else if (error) {
          this.uiModal.error('Fehler beim abschließen der Bestellung', error);
        }

        if (error.status === 409) {
          this._store.orderCompleted.next();
          await this.patchProcess(processId);
          await this._navigationService
            .getCheckoutSummaryPath({ processId })
            .navigate();
        }
      } finally {
        this.showOrderButtonSpinner = false;
        this.cdr.markForCheck();
      }
    }
  }

  async patchProcess(processId: number) {
    const process = await this.applicationService
      .getProcessById$(processId)
      .pipe(first())
      .toPromise();
    if (process) {
      this.applicationService.patchProcess(process.id, {
        name: `${process.name} Bestellbestätigung`,
        type: 'cart-checkout',
      });
    }
  }
}
