import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy, ViewChild, ViewChildren, QueryList } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ApplicationService } from '@core/application';
import { BreadcrumbService } from '@core/breadcrumb';
import { DomainCheckoutService } from '@domain/checkout';
import { ItemDTO } from '@swagger/cat';
import { AddToShoppingCartDTO } from '@swagger/checkout';
import { UiFilter } from '@ui/filter';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { CacheService } from 'apps/core/cache/src/public-api';
import { isEqual } from 'lodash';
import { BehaviorSubject, combineLatest, Subscription } from 'rxjs';
import { debounceTime, first, map, tap } from 'rxjs/operators';
import { ArticleSearchService } from '../article-search.store';
import { AddedToCartModalComponent } from './added-to-cart-modal/added-to-cart-modal.component';
import { SearchResultItemComponent } from './search-result-item.component';

@Component({
  selector: 'page-search-results',
  templateUrl: 'search-results.component.html',
  styleUrls: ['search-results.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ArticleSearchResultsComponent implements OnInit, OnDestroy {
  @ViewChildren(SearchResultItemComponent) listItems: QueryList<SearchResultItemComponent>;
  @ViewChild('scrollContainer', { static: true })
  scrollContainer: CdkVirtualScrollViewport;

  results$ = this.searchService.items$;
  fetching$ = this.searchService.fetching$;
  hits$ = this.searchService.hits$;

  filter$ = this.searchService.filter$;

  selectedItemIds$ = this.searchService.selectedItemIds$;

  selectedItems$ = combineLatest([this.results$, this.selectedItemIds$]).pipe(
    map(([items, selectedItemIds]) => {
      return items?.filter((item) => selectedItemIds?.find((selectedItemId) => item.id === selectedItemId));
    })
  );

  loading$ = new BehaviorSubject<boolean>(false);

  private subscriptions = new Subscription();

  trackByItemId = (item: ItemDTO) => item.id;

  constructor(
    public searchService: ArticleSearchService,
    private route: ActivatedRoute,
    private application: ApplicationService,
    private breadcrumb: BreadcrumbService,
    private cache: CacheService,
    private _uiModal: UiModalService,
    private _checkoutService: DomainCheckoutService
  ) {}

  ngOnInit() {
    this.subscriptions.add(
      combineLatest([this.application.activatedProcessId$, this.route.queryParams])
        .pipe(debounceTime(0))
        .subscribe(async ([processId, queryParams]) => {
          if (processId !== this.searchService.processId) {
            if (!!this.searchService.processId && this.searchService.filter instanceof UiFilter) {
              this.cacheCurrentData(this.searchService.processId, this.searchService.filter.getQueryParams());
              this.updateBreadcrumbs(this.searchService.processId, this.searchService.filter.getQueryParams());
            }
            this.searchService.setProcess(processId);
          }

          if (!(this.searchService.filter instanceof UiFilter)) {
            await this.searchService.setDefaultFilter();
          }

          const cleanQueryParams = this.cleanupQueryParams(queryParams);

          if (!isEqual(cleanQueryParams, this.cleanupQueryParams(this.searchService.filter.getQueryParams()))) {
            await this.searchService.setDefaultFilter(queryParams);
            const data = this.getCachedData(processId, queryParams);
            this.searchService.setItems(data.items);
            this.searchService.setHits(data.hits);

            if (data.items?.length === 0) {
              this.search();
            } else {
              this.scrollTop(Number(queryParams.scroll_position ?? 0));
              const selectedItemIds: Array<string> = queryParams?.selected_item_ids?.split(',') ?? [];
              for (const id of selectedItemIds) {
                if (id) {
                  this.searchService.setSelected({ selected: true, itemId: Number(id) });
                }
              }
            }
          }

          await this.updateBreadcrumbs(processId, queryParams);
          await this.createBreadcrumb(processId, queryParams);
          await this.removeDetailsBreadcrumb(processId);
        })
    );
  }

  ngOnDestroy() {
    this.subscriptions?.unsubscribe();

    this.cacheCurrentData(this.searchService.processId, this.searchService.filter.getQueryParams());
    this.updateBreadcrumbs(this.searchService.processId, this.searchService.filter.getQueryParams());
    this.unselectAll();
  }

  search() {
    this.searchService.search({ clear: true });
  }

  async removeDetailBreadcrumb(processId: number) {
    const crumbs = await this.breadcrumb.getBreadcrumbsByKeyAndTags$(processId, ['catalog', 'details']).pipe(first()).toPromise();

    for (const crumb of crumbs) {
      this.breadcrumb.removeBreadcrumb(crumb.id);
    }
  }

  scrollTop(scrollPos: number) {
    setTimeout(() => this.scrollContainer.scrollTo({ top: scrollPos }), 0);
  }

  async scrolledIndexChange(index: number) {
    const results = await this.results$.pipe(first()).toPromise();
    const hits = await this.hits$.pipe(first()).toPromise();

    if (results.length >= hits) {
      return;
    }

    if (index >= results.length - 20 && results.length - 20 > 0) {
      this.searchService.search({ clear: false });
    }
  }

  async updateBreadcrumbs(
    processId: number = this.searchService.processId,
    queryParams: Record<string, string> = this.searchService.filter?.getQueryParams()
  ) {
    const scroll_position = this.scrollContainer.measureScrollOffset('top');
    const selected_item_ids = this.searchService?.selectedItemIds?.toString();

    if (queryParams) {
      const crumbs = await this.breadcrumb
        .getBreadcrumbsByKeyAndTags$(processId, ['catalog', 'filter', 'results'])
        .pipe(first())
        .toPromise();

      const name = queryParams.main_qs ? queryParams.main_qs : 'Alle Artikel';
      const params = { ...queryParams, scroll_position, selected_item_ids };

      for (const crumb of crumbs) {
        this.breadcrumb.patchBreadcrumb(crumb.id, {
          name,
          params,
        });
      }
    }
  }

  async createBreadcrumb(processId: number, queryParams: Record<string, string>) {
    if (queryParams) {
      const name = queryParams.main_qs ? queryParams.main_qs : 'Alle Artikel';
      await this.breadcrumb.addBreadcrumbIfNotExists({
        key: processId,
        name,
        path: `/kunde/${this.application.activatedProcessId}/product/search/results`,
        params: queryParams,
        section: 'customer',
        tags: ['catalog', 'filter', 'results'],
      });
    }
  }

  async removeDetailsBreadcrumb(processId: number) {
    const crumbs = await this.breadcrumb.getBreadcrumbsByKeyAndTags$(processId, ['catalog', 'details']).pipe(first()).toPromise();

    crumbs?.forEach((crumb) => this.breadcrumb.removeBreadcrumb(crumb.id));
  }

  cacheCurrentData(processId: number, params: Record<string, string> = {}) {
    const qparams = this.cleanupQueryParams({ ...params, processId: String(processId) });

    this.cache.set(qparams, {
      items: this.searchService.items,
      hits: this.searchService.hits,
    });
  }

  getCachedData(processId: number, params: Record<string, string> = {}) {
    const qparams = this.cleanupQueryParams({ ...params, processId: String(processId) });

    return (
      this.cache.get<{
        items: ItemDTO[];
        hits: number;
      }>(qparams) || { items: [], hits: 0 }
    );
  }

  cleanupQueryParams(params: Record<string, string> = {}) {
    const clean = { ...params };
    delete clean['scroll_position'];
    delete clean['selected_item_ids'];

    for (const key in clean) {
      if (Object.prototype.hasOwnProperty.call(clean, key)) {
        if (clean[key] == undefined) {
          delete clean[key];
        }
      }
    }

    return clean;
  }

  isSelectable(item: ItemDTO) {
    // Zeige Select Radio Button nicht an wenn Item Archivartikel oder Fortsetzungsartikel ist
    const isArchiv = item?.catalogAvailability?.status === 1;
    const isFortsetzung = item?.features?.find((i) => i?.key === 'PFO');
    return !(isArchiv || isFortsetzung);
  }

  unselectAll() {
    this.listItems.forEach((listItem) => this.searchService.setSelected({ selected: false, itemId: listItem.item.id }));
    this.searchService.patchState({ selectedItemIds: [] });
  }

  async addSelectedItemsToCart() {
    this.loading$.next(true);
    const selectedItems = await this.selectedItems$.pipe(first()).toPromise();
    const items: AddToShoppingCartDTO[] = [];
    const canAddItemsPayload = [];

    for (const item of selectedItems) {
      const isDownload = item?.product?.format === 'EB' || item?.product?.format === 'DL';
      const shoppingCartItem: AddToShoppingCartDTO = {
        quantity: 1,
        availability: {
          availabilityType: item?.catalogAvailability?.status,
          price: item?.catalogAvailability?.price,
          supplierProductNumber: item?.ids?.dig ? String(item.ids?.dig) : item?.product?.supplierProductNumber,
        },
        product: {
          catalogProductNumber: String(item?.id),
          ...item?.product,
        },
        promotion: { points: item?.promoPoints },
      };

      if (isDownload) {
        shoppingCartItem.destination = { data: { target: 16 } };
        canAddItemsPayload.push({
          availabilities: [{ ...item.catalogAvailability, format: 'DL' }],
          id: item.product.catalogProductNumber,
        });
      }

      items.push(shoppingCartItem);
    }

    if (canAddItemsPayload.length > 0) {
      try {
        const response = await this._checkoutService
          .canAddItems({
            processId: this.application.activatedProcessId,
            payload: canAddItemsPayload,
            orderType: 'Download',
          })
          .pipe(first())
          .toPromise();

        if (response) {
          const cantAdd = (response as any)?.filter((r) => r.status >= 2);
          if (cantAdd?.length > 0) {
            this.openModal({ itemLength: cantAdd.length, canAddMessage: cantAdd[0].message });
            return;
          }
        }
      } catch (error) {
        this.openModal({ itemLength: selectedItems?.length, error });
        console.error(error);
        return;
      }
    }

    try {
      await this._checkoutService
        .addItemToShoppingCart({
          processId: this.application.activatedProcessId,
          items,
        })
        .toPromise();
      this.openModal({ itemLength: selectedItems?.length });
    } catch (error) {
      this.openModal({ itemLength: selectedItems?.length, error });
      console.error(error);
    }
  }

  openModal({ itemLength, canAddMessage, error }: { itemLength: number; canAddMessage?: string; error?: Error }) {
    const modal = this._uiModal.open({
      title:
        !error && !canAddMessage
          ? `${itemLength} Artikel ${itemLength > 1 ? 'wurden' : 'wurde'} in den Warenkorb gelegt`
          : `Artikel ${itemLength > 1 ? 'konnten' : 'konnte'} nicht in den Warenkorb gelegt werden`,
      content: !error ? AddedToCartModalComponent : UiErrorModalComponent,
      data: error ? error : { canAddMessage },
      config: { showScrollbarY: false },
    });
    this.subscriptions.add(
      modal.afterClosed$.subscribe(() => {
        if (!error) {
          this.unselectAll();
        }
        this.loading$.next(false);
      })
    );
  }
}
