import { ComponentStore, OnStoreInit } from '@ngrx/component-store';
import { tapResponse } from '@ngrx/operators';
import { PickupShelfState } from './pickup-shelf.state';
import { PickupShelfIOService } from '@domain/pickup-shelf';
import {
  debounceTime,
  delayWhen,
  filter,
  map,
  skipWhile,
  switchMap,
  take,
  takeUntil,
  tap,
  throttleTime,
  withLatestFrom,
} from 'rxjs/operators';
import {
  DBHOrderItemListItemDTO,
  ListResponseArgsOfDBHOrderItemListItemDTO,
  QuerySettingsDTO,
  ResponseArgsOfQuerySettingsDTO,
} from '@generated/swagger/oms-api';
import { DestroyRef, Injectable, inject } from '@angular/core';
import { ToasterService } from '@shared/shell';
import { UiModalService } from '@ui/modal';

import * as Selectors from './pickup-shelf.selectors';
import { Observable, Subject, combineLatest, from } from 'rxjs';
import { CacheService } from '@core/cache';
import { Filter } from '@shared/components/filter';
import { PickupShelfDetailsState } from './pickup-shelf-details.state';
import { injectCancelSearchSubject } from '@shared/services/cancel-subject';

@Injectable()
export class PickupShelfStore extends ComponentStore<PickupShelfState> implements OnStoreInit {
  private _cancelSubject = injectCancelSearchSubject();

  private _cacheService = inject(CacheService);

  private _destronyRef = inject(DestroyRef);

  private _cancelListRequests = new Subject<void>();

  private _pickupShelfIOService = inject(PickupShelfIOService);

  private _toasterService = inject(ToasterService);

  private _modalService = inject(UiModalService);

  readonly processId$ = this.select(Selectors.selectProcessId);

  private _fetchListResponse = new Subject<{
    response: ListResponseArgsOfDBHOrderItemListItemDTO;
    processId: number;
    queryParams: Record<string, string>;
  }>();

  readonly fetchListResponse$ = this._fetchListResponse.asObservable();

  get processId() {
    return this.get(Selectors.selectProcessId);
  }

  readonly filter$ = this.select(Selectors.selectFilter);

  get filter() {
    return this.get(Selectors.selectFilter);
  }

  readonly defaultFilter$ = this.select(Selectors.selectDefaultFilter);

  get defaultFilter() {
    return this.get(Selectors.selectDefaultFilter);
  }

  readonly list$ = this.select(Selectors.selectList);

  readonly queryParams$ = this.select(Selectors.selectQueryParams);

  get queryParams() {
    return this.get(Selectors.selectQueryParams);
  }

  get list() {
    return this.get(Selectors.selectList);
  }

  readonly listHits$ = this.select(Selectors.selectListHits);

  get listHits() {
    return this.get(Selectors.selectListHits);
  }

  readonly fetchingList$ = this.select(Selectors.selectFetchingList);

  get fetchingList() {
    return this.get(Selectors.selectFetchingList);
  }

  searchboxHint$ = combineLatest([this.fetchingList$, this.fetchListResponse$]).pipe(
    debounceTime(100),
    map(([fetching, response]) => (response?.response?.hits === 0 && !fetching ? 'Keine Suchergebnisse' : '')),
  );

  readonly selectedListItems$ = this.select(Selectors.selectSelectedListItems);

  get selectedListItems() {
    return this.get(Selectors.selectSelectedListItems);
  }

  itemsForPreview$ = (state: PickupShelfDetailsState) => this.select(Selectors.selectItemsForPreview(state));

  itemsForPreview = (state: PickupShelfDetailsState) => this.get(Selectors.selectItemsForPreview(state));

  constructor() {
    // Nicht entfernen sonst wird der Store nicht initialisiert
    super({});

    this._destronyRef.onDestroy(() => {
      this.cancelListRequests();
      this._fetchListResponse.complete();
      this.patchState({ fetchingList: false });
    });
  }

  setProcessId(processId: number) {
    if (this.processId === processId) {
      return;
    }

    this.cancelListRequests();
    this.patchState({ processId: processId, fetchingList: false, list: [], listHits: 0, queryParams: {} });
  }

  setQueryParams(queryParams: Record<string, string> | undefined) {
    // #4533 Wenn ein Abholschein gescannt wird, soll ORD: nicht in der Suchbox stehen und somit auch nicht in der Breadcrumb enthalten sein
    const isScannedPickUpCode = queryParams?.main_qs?.includes('ORD:');
    if (isScannedPickUpCode) {
      this.patchState({ queryParams: { ...queryParams, main_qs: queryParams?.main_qs?.replace('ORD:', '') } });
    } else {
      this.patchState({ queryParams });
    }
  }

  cancelListRequests() {
    this._cancelListRequests.next();
  }

  addSelectedListItem = this.updater((state, listItem: DBHOrderItemListItemDTO) => ({
    ...state,
    selectedListItems: [...(state.selectedListItems ?? []), listItem],
  }));

  addSelectedListItems = this.updater((state, listItems: DBHOrderItemListItemDTO[]) => ({
    ...state,
    selectedListItems: [...(state.selectedListItems ?? []), ...listItems],
  }));

  removeSelectedListItem = this.updater((state, listItem: DBHOrderItemListItemDTO) => ({
    ...state,
    selectedListItems: state.selectedListItems?.filter((li) => li.orderItemId !== listItem.orderItemId) ?? [],
  }));

  removeSelectedListItems = this.updater((state, listItems: DBHOrderItemListItemDTO[]) => ({
    ...state,
    selectedListItems:
      state.selectedListItems?.filter((li) => !listItems.some((l) => l.orderItemId === li.orderItemId)) ?? [],
  }));

  resetSelectedListItems = this.updater((state) => ({ ...state, selectedListItems: [] }));

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

  fetchQuerySettings = this.effect((trigger$) =>
    trigger$.pipe(
      switchMap(() => from(this.beforeFetchQuerySettings())),
      filter((shouldFetch) => shouldFetch),
      switchMap(() =>
        this._pickupShelfIOService
          .getQuerySettings()
          .pipe(tapResponse(this.fetchQuerySettingsDone, this.fetchQuerySettingsError)),
      ),
    ),
  );

  /**
   * Before fetching query settings
   * @returns true if query settings should be fetched
   */
  private beforeFetchQuerySettings = async () => {
    const cachedQuerySettings = await this._cacheService.get<QuerySettingsDTO>({
      name: 'pickup-shelf',
      providerName: this._pickupShelfIOService.name(),
    });

    if (cachedQuerySettings) {
      this.patchState({ fetchingQuerySettings: false, querySettings: cachedQuerySettings });
      return false;
    } else {
      this.patchState({ fetchingQuerySettings: true });
      return true;
    }
  };

  private fetchQuerySettingsDone = (resp: ResponseArgsOfQuerySettingsDTO) => {
    this.patchState({ fetchingQuerySettings: false, querySettings: resp.result });

    this._cacheService.set<QuerySettingsDTO>(
      { name: 'pickup-shelf', providerName: this._pickupShelfIOService.name() },
      resp.result,
    );
  };

  private fetchQuerySettingsError = (err: any) => {
    this._modalService.error('Fehler beim Laden der Filtereinstellungen', err);
    this.patchState({ fetchingQuerySettings: false });
  };

  delayWhenFilterIsNotReady = delayWhen((value: { emitFetchListResponse: boolean } | void) =>
    this.filter$.pipe(
      filter((filter) => !!filter),
      take(1),
    ),
  );

  cancelFetchList() {
    this._cancelSubject.next();
  }

  fetchList = this.effect((trigger$: Observable<{ emitFetchListResponse: boolean } | void>) =>
    trigger$.pipe(
      this.delayWhenFilterIsNotReady,
      withLatestFrom(this.filter$, this.processId$),
      switchMap(([{ emitFetchListResponse } = { emitFetchListResponse: true }, filter, processId]) =>
        from(this.beforeFetchList(emitFetchListResponse, filter, processId)),
      ),
      switchMap(({ emitFetchListResponse, filter, processId, list }) =>
        this._pickupShelfIOService
          .search({
            ...filter.getQueryToken(),
            take: Math.max(list.length, 25),
          })
          .pipe(
            takeUntil(this._cancelListRequests),
            takeUntil(this._cancelSubject),
            tapResponse(
              (res) =>
                this.fetchListDone({ processId, queryParams: filter.getQueryParams(), emitFetchListResponse })(res),
              (err) => this.fetchListError(err),
              () => this.patchState({ fetchingList: false }),
            ),
          ),
      ),
    ),
  );

  private beforeFetchList = async (emitFetchListResponse: boolean, filter: Filter, processId: number) => {
    this.cancelListRequests();
    this.patchState({ fetchingList: true });
    const queryToken = filter.getQueryParams();
    const cachedListResponse = await this._cacheService.get<ListResponseArgsOfDBHOrderItemListItemDTO>({
      processId,
      queryToken,
    });

    let list: DBHOrderItemListItemDTO[] = [];

    if (cachedListResponse) {
      this.patchState({ list: cachedListResponse.result, listHits: cachedListResponse.hits });
      list = cachedListResponse.result;
    } else {
      this.patchState({ list: [], listHits: 0 });
    }

    return { emitFetchListResponse, filter, processId, list };
  };

  private fetchListDone =
    ({
      processId,
      queryParams,
      emitFetchListResponse,
    }: {
      processId: number;
      queryParams: Record<string, string>;
      emitFetchListResponse: boolean;
    }) =>
    (response: ListResponseArgsOfDBHOrderItemListItemDTO) => {
      this.patchState({ fetchingList: false, list: response.result, listHits: response.hits });
      if (emitFetchListResponse) {
        this._fetchListResponse.next({ processId, response, queryParams });
      }

      this._cacheService.set<ListResponseArgsOfDBHOrderItemListItemDTO>(
        { processId, queryToken: queryParams },
        response,
      );
    };

  private fetchListError = (err: any) => {
    this._modalService.error('Fehler beim Laden der Liste', err);
    this.patchState({ fetchingList: false });
  };

  fetchMoreList = this.effect((trigger$) =>
    trigger$.pipe(
      throttleTime(500),
      skipWhile(() => this.fetchingList),
      withLatestFrom(this.filter$, this.list$, this.listHits$),
      filter(([_, __, list, listHits]) => list.length < listHits),
      tap(() => this.beforeFetchMoreList()),
      switchMap(([_, filter, list]) => {
        const queryToken = filter.getQueryToken();
        queryToken.skip = list?.length ?? 0;
        queryToken.take = 25;
        return this._pickupShelfIOService.search(queryToken).pipe(
          takeUntil(this._cancelListRequests),
          tapResponse(
            (r) => this.fetchMoreListDone(r),
            (e) => this.fetchMoreListError(e),
          ),
        );
      }),
    ),
  );

  private beforeFetchMoreList = () => {
    this.cancelListRequests();
    this.patchState({ fetchingList: true });
  };

  private fetchMoreListDone = (resp: ListResponseArgsOfDBHOrderItemListItemDTO) => {
    this.patchState({ fetchingList: false, list: [...this.list, ...resp.result], listHits: resp.hits });
  };

  private fetchMoreListError = (err: any) => {
    this._modalService.error('Fehler beim Laden der Liste', err);
    this.patchState({ fetchingList: false });
  };

  patchOrderItem = this.updater(
    (
      state,
      { orderItemSubsetId, changes }: { orderItemSubsetId: number; changes: Partial<DBHOrderItemListItemDTO> },
    ) => {
      return {
        ...state,
        list: this.list.map((li) => (li.orderItemSubsetId === orderItemSubsetId ? { ...li, ...changes } : li)),
      };
    },
  );
}
