import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import {
  Component,
  ChangeDetectionStrategy,
  OnInit,
  OnDestroy,
  ViewChild,
  ViewChildren,
  QueryList,
  TrackByFunction,
  AfterViewInit,
  inject,
} from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ApplicationService } from '@core/application';
import { BreadcrumbService } from '@core/breadcrumb';
import { EnvironmentService } from '@core/environment';
import { DomainCheckoutService } from '@domain/checkout';
import { ItemDTO } from '@generated/swagger/cat-search-api';
import { AddToShoppingCartDTO } from '@generated/swagger/checkout-api';
import { UiErrorModalComponent, UiModalService } from '@ui/modal';
import { CacheService } from '@core/cache';
import { isEqual } from 'lodash';
import { BehaviorSubject, combineLatest, Subscription } from 'rxjs';
import {
  debounceTime,
  first,
  map,
  switchMap,
  withLatestFrom,
} 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';
import { ProductCatalogNavigationService } from '@shared/services/navigation';
import {
  Filter,
  FilterInputGroupMainComponent,
} from '@shared/components/filter';
import { DomainAvailabilityService, ItemData } from '@domain/availability';
import { asapScheduler } from 'rxjs';
import { ShellService } from '@shared/shell';

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

  @ViewChild(FilterInputGroupMainComponent, { static: false })
  sharedFilterInputGroupMain: FilterInputGroupMainComponent;

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

  filter$ = this.searchService.filter$;

  selectedItemIds$ = this.searchService.selectedItemIds$;

  searchboxHint$ = this.searchService.searchboxHint$;

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

  getProcessId(): number {
    return this.application.activatedProcessId;
  }

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

  private subscriptions = new Subscription();

  trackByItemId: TrackByFunction<ItemDTO> = (index, item) => item.id;

  get isTablet() {
    return this._environment.matchTablet();
  }

  get isDesktopLarge() {
    return this._environment.matchDesktopLarge();
  }

  hasFilter$ = combineLatest([
    this.searchService.filter$,
    this.searchService.defaultSettings$,
  ]).pipe(
    map(([filter, defaultFilter]) => {
      const filterQueryParams = filter?.getQueryParams();
      return !isEqual(
        this.resetQueryParamsQueryAndOrderBy(filterQueryParams),
        Filter.create(defaultFilter).getQueryParams(),
      );
    }),
  );

  get filterRoute() {
    const itemId = this.route?.snapshot?.params?.id;
    return this._navigationService.getArticleSearchResultsAndFilterPath({
      processId: this.application.activatedProcessId,
      itemId,
    }).path;
  }

  get filterQueryParams() {
    return this.cleanupQueryParams(
      this.searchService?.filter?.getQueryParams(),
    );
  }

  get primaryOutletActive$() {
    return this._environment.matchDesktop$.pipe(
      map((matches) => matches && this.route.outlet === 'primary'),
    );
  }

  private readonly SCROLL_INDEX_TOKEN = 'CATALOG_RESULTS_LIST_SCROLL_INDEX';

  shellService = inject(ShellService);

  scale$ = this.shellService.scale$;

  constructor(
    public searchService: ArticleSearchService,
    public route: ActivatedRoute,
    public application: ApplicationService,
    private breadcrumb: BreadcrumbService,
    private cache: CacheService,
    private _uiModal: UiModalService,
    private _checkoutService: DomainCheckoutService,
    private _environment: EnvironmentService,
    private _navigationService: ProductCatalogNavigationService,
    private _availability: DomainAvailabilityService,
    private _router: Router,
  ) {}

  ngOnInit() {
    this.subscriptions.add(
      combineLatest([
        this.application.activatedProcessId$,
        this.route.queryParams,
      ])
        .pipe(
          debounceTime(0),
          switchMap(([processId, queryParams]) =>
            this.application.getSelectedBranch$(processId).pipe(
              map((selectedBranch) => ({
                processId,
                queryParams,
                selectedBranch,
              })),
            ),
          ),
        )
        .subscribe(async ({ processId, queryParams, selectedBranch }) => {
          const processChanged = processId !== this.searchService.processId;

          const branchChanged =
            selectedBranch?.id !== this.searchService?.selectedBranch?.id;

          if (processChanged) {
            if (
              !!this.searchService.processId &&
              this.searchService.filter instanceof Filter
            ) {
              this.cacheCurrentData(
                this.searchService.processId,
                this.searchService.filter.getQueryParams(),
                this.searchService?.selectedBranch?.id,
              );
              this.updateBreadcrumbs(
                this.searchService.processId,
                this.searchService.filter.getQueryParams(),
              );
            }
            this.searchService.setProcess(processId);
          }

          if (branchChanged) {
            this.searchService.setBranch(selectedBranch);
          }

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

          const cleanQueryParams = this.cleanupQueryParams(queryParams);

          if (processChanged) {
            this.scrollToItem(await this._getScrollIndexFromCache());
          }

          if (
            !isEqual(
              cleanQueryParams,
              this.cleanupQueryParams(
                this.searchService.filter.getQueryParams(),
              ),
            )
          ) {
            await this.searchService.setDefaultFilter(queryParams);
            const data = await this.getCachedData(
              processId,
              queryParams,
              selectedBranch?.id,
            );

            if (data.items?.length > 0) {
              this.searchService.setItems(data.items);
              this.searchService.setHits(data.hits);
            }
            if (
              data.items?.length === 0 &&
              this.route?.parent?.children?.find(
                (childRoute) => childRoute?.outlet === 'side',
              )?.snapshot?.routeConfig?.path !== 'filter'
            ) {
              this.search({ clear: true });
            } else {
              const selectedItemIds: Array<string> =
                queryParams?.selected_item_ids?.split(',') ?? [];
              for (const id of selectedItemIds) {
                if (id) {
                  this.searchService.setSelected({
                    selected: true,
                    itemId: Number(id),
                  });
                }
              }
            }
          }

          const process = await this.application
            .getProcessById$(processId)
            .pipe(first())
            .toPromise();
          if (process) {
            await this.updateBreadcrumbs(processId, queryParams);
            await this.createBreadcrumb(processId, queryParams);
          }

          if (this.route?.outlet === 'primary') {
            await this.removeDetailsBreadcrumb(processId);
          }
        }),
    );

    this.subscriptions.add(
      this.searchService.searchCompleted
        .pipe(withLatestFrom(this.application.activatedProcessId$))
        .subscribe(async ([searchCompleted, processId]) => {
          const params = searchCompleted.state.filter.getQueryParams();
          if (searchCompleted.state.searchState === '') {
            // Ticket 4524 Korrekte Navigation bei orderBy mit aktuellen queryParams
            if (searchCompleted?.orderBy) {
              return await this._router.navigate([], { queryParams: params });
            }

            // Navigation auf Details bzw. Results | Details wenn hits 1
            // Navigation auf Results bei clear search, oder immer auf Tablet oder wenn große Resultliste aktiv ist
            if (searchCompleted.state.hits === 1) {
              const item = searchCompleted.state.items.find((f) => f);
              const ean = this.route?.snapshot?.params?.ean;

              // Navigation from Cart uses ean
              if (ean) {
                await this._navigationService
                  .getArticleDetailsPathByEan({
                    processId,
                    ean,
                    extras: { queryParams: params },
                  })
                  .navigate();
              } else {
                await this._navigationService
                  .getArticleDetailsPath({
                    processId,
                    itemId: item.id,
                    extras: { queryParams: params },
                  })
                  .navigate();
              }
            } else if (
              searchCompleted?.clear ||
              this.route.outlet === 'primary'
            ) {
              const ean = this.route?.snapshot?.params?.ean;

              if (ean) {
                await this._navigationService
                  .getArticleDetailsPathByEan({
                    processId,
                    ean,
                    extras: { queryParams: params },
                  })
                  .navigate();
              } else {
                await this._navigationService
                  .getArticleSearchResultsPath(processId, {
                    queryParams: params,
                  })
                  .navigate();
              }
            }
          }
        }),
    );

    // #4143 To make Splitscreen Search and Filter work combined
    this.subscriptions.add(
      this.searchService.searchStarted.subscribe(async (options) => {
        if (!options?.clear) {
          const queryParams = {
            ...this.cleanupQueryParams(
              this.searchService.filter.getQueryParams(),
            ),
            main_qs: this.sharedFilterInputGroupMain?.uiInput?.value,
          };

          await this.searchService.setDefaultFilter(queryParams);
        }
      }),
    );
  }

  ngAfterViewInit(): void {
    this._getScrollIndexFromCache().then((index) => this.scrollToItem(index));
  }

  private _addScrollIndexToCache(index: number): void {
    this.cache.set<number>(
      { processId: this.getProcessId(), token: this.SCROLL_INDEX_TOKEN },
      index,
    );
  }

  private async _getScrollIndexFromCache(): Promise<number> {
    return (
      (await this.cache.get<number>({
        processId: this.getProcessId(),
        token: this.SCROLL_INDEX_TOKEN,
      })) ?? 0
    );
  }

  async scrollToItem(i?: number) {
    let index = i;

    if (!index) {
      index = await this._getScrollIndexFromCache();
    } else {
      this._addScrollIndexToCache(index);
    }

    asapScheduler.schedule(() => {
      this.scrollContainer.scrollToIndex(index, 'smooth');
    }, 150);
  }

  scrolledIndexChange(index: number) {
    const completeListFetched =
      this.searchService.items.length === this.searchService.hits;

    if (
      index &&
      !completeListFetched &&
      this.searchService.items.length <=
        this.scrollContainer?.getRenderedRange()?.end
    ) {
      this.search({ clear: false });
    }

    if (this.getProcessId() === this.searchService.processId) {
      this._addScrollIndexToCache(index);
    }
  }

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

    this.cacheCurrentData(
      this.searchService.processId,
      this.searchService.filter.getQueryParams(),
      this.searchService?.selectedBranch?.id,
    );
    await this.updateBreadcrumbs(
      this.searchService.processId,
      this.searchService.filter.getQueryParams(),
    );

    this.unselectAll();
  }

  resetQueryParamsQueryAndOrderBy(params: Record<string, string> = {}) {
    const clean = { ...params };

    for (const key in clean) {
      if (key === 'main_qs') {
        clean[key] = undefined;
      } else if (key?.includes('order_by')) {
        delete clean[key];
      }
    }

    return clean;
  }

  search({
    filter,
    clear = false,
    orderBy = false,
  }: {
    filter?: Filter;
    clear?: boolean;
    orderBy?: boolean;
  }) {
    if (filter) {
      this.sharedFilterInputGroupMain.cancelAutocomplete();
    }

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

  getDetailsPath(itemId: number) {
    return this._navigationService.getArticleDetailsPath({
      processId: this.application.activatedProcessId,
      itemId,
    }).path;
  }

  async updateBreadcrumbs(
    processId: number = this.searchService.processId,
    queryParams: Record<
      string,
      string
    > = this.searchService.filter?.getQueryParams(),
  ) {
    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, 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: this._navigationService.getArticleSearchResultsPath(processId, {
          queryParams,
        }).path,
        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> = {},
    branchId: number,
  ) {
    const qparams = this.cleanupQueryParams({
      ...params,
      processId: String(processId),
      branchId: String(branchId),
    });

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

  async getCachedData(
    processId: number,
    params: Record<string, string> = {},
    branchId: number,
  ) {
    const qparams = this.cleanupQueryParams({
      ...params,
      processId: String(processId),
      branchId: String(branchId),
    });
    const cacheData = await this.cache.get<{
      items: ItemDTO[];
      hits: number;
    }>(qparams);

    return cacheData ?? { items: [], hits: 0 };
  }

  cleanupQueryParams(params: Record<string, string> = {}) {
    const clean = { ...params };
    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);
  }

  isSelected(item: ItemDTO) {
    return this.searchService.selectedItemIds.includes(item.id);
  }

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

  async addToCart(item?: ItemDTO) {
    this.loading$.next(true);

    if (item) {
      await this.addItemsToCart(item);
    } else {
      await this.addItemsToCart();
    }

    this.loading$.next(false);
  }

  private _createShoppingCartItem(item: ItemDTO): AddToShoppingCartDTO {
    return {
      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,
      },
      itemType: item?.type,
      promotion: { value: item?.promoPoints },
    };
  }

  async addItemsToCart(item?: ItemDTO) {
    const selectedItems = !item
      ? await this.selectedItems$.pipe(first()).toPromise()
      : [item];
    const items: AddToShoppingCartDTO[] = [];

    const canAddItemsPayload = [];

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

      if (isDownload) {
        const itemId = item?.id;
        const ean = item?.product?.ean;
        const downloadItem: ItemData = { ean, itemId, price };

        // #4180 Für Download Artikel muss hier immer zwingend der logistician gesetzt werden, da diese Artikel direkt zugeordnet dem Warenkorb hinzugefügt werden
        const downloadAvailability = await this._availability
          .getDownloadAvailability({ item: downloadItem })
          .pipe(first())
          .toPromise();
        shoppingCartItem.destination = {
          data: { target: 16, logistician: downloadAvailability?.logistician },
        };
        if (downloadAvailability) {
          shoppingCartItem.availability = {
            ...shoppingCartItem.availability,
            ...downloadAvailability,
          };
        }
        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);
      }),
    );
  }
}
