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

import { CustomerSearchState } from './customer-search.state';
import * as S from './selectors';
import { inject, Injectable, OnDestroy } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import {
  distinctUntilChanged,
  filter,
  switchMap,
  takeUntil,
  tap,
  withLatestFrom,
  delayWhen,
  first,
  map,
} from 'rxjs/operators';
import { CrmCustomerService } from '@domain/crm';
import { Result } from '@domain/defs';
import { CustomerDTO, ListResponseArgsOfCustomerInfoDTO, QuerySettingsDTO } from '@generated/swagger/crm-api';
import { Filter } from '@shared/components/filter';
import { DomainOmsService } from '@domain/oms';
import { OrderDTO, OrderListItemDTO } from '@generated/swagger/oms-api';
import { hash } from '@utils/common';
import { UiModalService } from '@ui/modal';
import { injectCancelSearchSubject } from '@shared/services/cancel-subject';

@Injectable()
export class CustomerSearchStore extends ComponentStore<CustomerSearchState> implements OnStoreInit, OnDestroy {
  private _customerService = inject(CrmCustomerService);
  private _omsService = inject(DomainOmsService);
  private _modal = inject(UiModalService);

  private _cancelSearch = injectCancelSearchSubject();

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

  processId$ = this.select(S.selectProcessId);

  get fetchingCustomer() {
    return this.get(S.selectFetchingCustomer);
  }

  fetchingCustomer$ = this.select(S.selectFetchingCustomer);

  get customerId() {
    return this.get(S.selectCustomerId);
  }

  customerId$ = this.select(S.selectCustomerId);

  get customer() {
    return this.get(S.selectCustomer);
  }

  customer$ = this.select(S.selectCustomer);

  get message() {
    return this.get(S.selectMessage);
  }

  message$ = this.select(S.selectMessage);

  get isBestellungOhneKonto() {
    return this.get(S.selectIsBestellungOhneKonto);
  }

  isBestellungOhneKonto$ = this.select(S.selectIsBestellungOhneKonto);

  get isOnlinekonto() {
    return this.get(S.selectIsOnlinekonto);
  }

  isOnlinekonto$ = this.select(S.selectIsOnlinekonto);

  get isOnlineKontoMitKundenkarte() {
    return this.get(S.selectIsOnlinekontoMitKundenkundenkarte);
  }

  isOnlineKontoMitKundenkarte$ = this.select(S.selectIsOnlinekontoMitKundenkundenkarte);

  get isKundenkarte() {
    return this.get(S.selectIsKundenkarte);
  }

  isKundenkarte$ = this.select(S.selectIsKundenkarte);

  get isBusinessKonto() {
    return this.get(S.selectIsBusinessKonto);
  }

  isBusinessKonto$ = this.select(S.selectIsBusinessKonto);

  get isMitarbeiter() {
    return this.get(S.selectIsMitarbeiter);
  }

  isMitarbeiter$ = this.select(S.selectIsMitarbeiter);

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

  queryParams$ = this.select(S.selectQueryParams);

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

  filter$ = this.select(S.selectFilter);

  get fetchingFilter() {
    return this.get(S.selectFetchingFilter);
  }

  fetchingFilter$ = this.select(S.selectFetchingFilter);

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

  defaultFilter$ = this.select(S.selectDefaultFilter);

  get fetchingCustomerList() {
    return this.get(S.selectFetchingCustomerList);
  }

  fetchingCustomerList$ = this.select(S.selectFetchingCustomerList);

  get customerList() {
    return this.get(S.selectCustomerList);
  }

  customerList$ = this.select(S.selectCustomerList);

  get customerListCount() {
    return this.get(S.selectCustomerListCount);
  }

  customerListCount$ = this.select(S.selectCustomerListCount);

  get customerOrders() {
    return this.get(S.selectCustomerOrders);
  }

  customerOrders$ = this.select(S.selectCustomerOrders);

  get order() {
    return this.get(S.selectOrder);
  }

  order$ = this.select(S.selectOrder);

  get fetchingCustomerOrders() {
    return this.get(S.selectFetchingCustomerOrders);
  }

  fetchingCustomerOrders$ = this.select(S.selectFetchingCustomerOrders);

  private _customerListResponse = new Subject<[ListResponseArgsOfCustomerInfoDTO, Filter, number, boolean, boolean]>();

  customerListResponse$ = this._customerListResponse.asObservable();

  private _customerListRestored = new Subject<void>();

  customerListRestored$ = this._customerListRestored.asObservable();

  selectedOrderItemId$ = this.select(S.selectSelectedOrderItemId);

  get selectedOrderItemId() {
    return this.get(S.selectSelectedOrderItemId);
  }

  get selectedOrderItem() {
    return this.get(S.selectSelectedOrderItem);
  }

  selectedOrderItem$ = this.select(S.selectSelectedOrderItem);

  constructor() {
    super({ customerListCount: 0 });
  }

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

  ngOnDestroy = () => {
    this._cancelSearch.next();
    this._cancelSearch.complete();

    this._customerListResponse.complete();
    this._customerListRestored.complete();
  };

  setProcessId = this.updater((state, processId: number) => ({ ...state, processId }));

  selectCustomer = this.effect((options$: Observable<{ customerId: number; reload?: boolean }>) =>
    options$.pipe(
      distinctUntilChanged(),
      filter(({ customerId, reload }) => reload || (!!customerId && Number(customerId) !== this.customerId)),
      tap(({ customerId }) => {
        this.patchState({ fetchingCustomer: true, customer: { id: +customerId } });
        this.restoreCustomerInfo();
      }),
      switchMap(({ customerId }) =>
        this._customerService
          .getCustomer(+customerId, 2)
          .pipe(
            tapResponse(
              this.handleSelectCustomerResponse,
              this.handleSelectCustomerError,
              this.handleSelectCustomerComplete,
            ),
          ),
      ),
    ),
  );

  setFilter = this.updater((state, filter: Filter) => ({ ...state, queryParams: filter?.getQueryParams() ?? {} }));

  handleSelectCustomerResponse = ({ result }: Result<CustomerDTO>) => {
    this.patchState({ customer: result });
    this.cacheCustomerInfo();
  };

  handleSelectCustomerError = (err: any) => {
    this._modal.error('Fehler beim Auswählen des Kundens', err);
    this.patchState({ fetchingCustomer: false });
  };

  handleSelectCustomerComplete = () => {
    this.patchState({ fetchingCustomer: false });
  };

  selectOrder = this.effect((orderId$: Observable<number>) =>
    orderId$.pipe(
      distinctUntilChanged(),
      filter((orderId) => !!orderId && Number(orderId) !== this.order?.id),
      tap((orderId) => this.patchState({ fetchingOrder: true, order: { id: orderId } })),
      switchMap((orderId) =>
        this._omsService
          .getOrder(orderId)
          .pipe(
            tapResponse(this.handleSelectOrderResponse, this.handleSelectOrderError, this.handleSelectOrderComplete),
          ),
      ),
    ),
  );

  handleSelectOrderResponse = (order: OrderDTO) => {
    this.patchState({ order });
  };

  handleSelectOrderError = (err: any) => {
    this._modal.error('Fehler beim Auswählen der Bestellung', err);
    this.patchState({ fetchingOrder: false });
  };

  handleSelectOrderComplete = () => {
    this.patchState({ fetchingOrder: false });
  };

  fetchOrders = this.effect(($: Observable<void>) =>
    $.pipe(
      delayWhen(() => this.customer$.pipe(filter((customer) => !!customer?.customerNumber))),
      withLatestFrom(this.customer$),
      tap(([_, __]) => {
        this.patchState({ fetchingCustomerOrders: true, customerOrders: [] });
      }),
      switchMap(([_, customer]) =>
        this._omsService
          .getOrderItemsByCustomerNumber(customer.customerNumber, 0)
          .pipe(
            tapResponse(
              this.handleFetchCustomerOrdersResponse,
              this.handleFetchCustomerOrdersError,
              this.handleFetchCustomerOrdersComplete,
            ),
          ),
      ),
    ),
  );

  handleFetchCustomerOrdersResponse = (orders: OrderListItemDTO[]) => {
    this.patchState({ customerOrders: orders });
  };

  handleFetchCustomerOrdersError = (err: any) => {
    this._modal.error('Fehler beim Laden der Kundenbestellungen', err);
    this.patchState({ fetchingCustomerOrders: false });
  };

  handleFetchCustomerOrdersComplete = () => {
    this.patchState({ fetchingCustomerOrders: false });
  };

  fetchFilter = this.effect(($: Observable<void>) =>
    $.pipe(
      tap(() => this.patchState({ fetchingFilter: true })),
      switchMap(() =>
        this._customerService
          .filterSettings()
          .pipe(
            tapResponse(this.handleFetchFilterResponse, this.handleFetchFilterError, this.handleFetchFilterComplete),
          ),
      ),
    ),
  );

  handleFetchFilterResponse = (result: QuerySettingsDTO) => {
    this.patchState({ defaultFilter: Filter.create(result) });
  };

  handleFetchFilterError = (err: any) => {
    this._modal.error('Fehler beim Laden der Filter', err);
    this.patchState({ fetchingFilter: false });
  };

  handleFetchFilterComplete = () => {
    this.patchState({ fetchingFilter: false });
  };

  search = this.effect(
    ($: Observable<{ resetScrollIndex?: boolean; ignoreRestore?: boolean; skipNavigation?: boolean }>) =>
      $.pipe(
        delayWhen(() =>
          this.filter$.pipe(
            filter((filter) => !!filter),
            first(),
          ),
        ),
        withLatestFrom(this.filter$, this.processId$),
        map(([a1, a2, a3]) => {
          // #4564 Setze "customer" undefined immer wenn neu gesucht wird,
          // da sonst Änderungen einer zweiten ISA am Kunden, selbst nach erneuter Suche, nicht geupdated werden,
          // da noch der alte Kundendatensatz im Store gespeichert ist
          this.patchState({
            fetchingCustomerList: true,
            customerList: [],
            customer: undefined,
            customerListCount: 0,
            message: '',
          });

          let retored = false;
          if (a1.ignoreRestore) {
            this.resetScrollIndex();
          } else {
            retored = this.restoreSearchResult();
          }

          return [a1, a2, a3, retored] as [
            { ignoreRestore?: boolean; skipNavigation?: boolean },
            Filter,
            number,
            boolean,
          ];
        }),
        switchMap(([{ ignoreRestore, skipNavigation }, filter, processId, restored]) =>
          this._customerService
            .getCustomersWithQueryToken({
              ...filter.getQueryToken(),
              take: this.customerList?.length || 20,
            })
            .pipe(
              takeUntil(this._cancelSearch),
              tapResponse(
                this.handleSearchResponse(filter, processId, ignoreRestore ? false : restored, !!skipNavigation),
                this.handleSearchError,
                this.handleSearchComplete,
              ),
            ),
        ),
      ),
  );

  handleSearchResponse =
    (filter: Filter, processId: number, restored: boolean, skipNavigation: boolean) =>
    (result: ListResponseArgsOfCustomerInfoDTO) => {
      this.patchState({
        customerList: result.result,
        customerListCount: result.hits,
        message: result?.hits > 0 ? '' : 'Keine Suchergebnisse',
      });
      this._customerListResponse.next([result, filter, processId, restored, skipNavigation]);
      this.cacheSearchResult();
    };

  handleSearchError = (err: any) => {
    this._modal.error('Fehler beim Laden der Liste', err);
    this.patchState({ fetchingCustomerList: false });
  };

  handleSearchComplete = () => {
    this.patchState({ fetchingCustomerList: false });
  };

  cancelSearch() {
    this._cancelSearch.next();
  }
  paginate = this.effect(($: Observable<void>) =>
    $.pipe(
      withLatestFrom(this.filter$, this.processId$, this.customerList$, this.fetchingCustomerList$),
      filter(
        ([_, __, ___, customerList, fetchingCustomerList]) =>
          !fetchingCustomerList && customerList.length && customerList.length < this.customerListCount,
      ),
      delayWhen(() => this.fetchingCustomerList$.pipe(filter((fetching) => !fetching))),
      tap(() => {
        this.patchState({ fetchingCustomerList: true });
      }),
      switchMap(([_, filter, processId, customerList]) =>
        this._customerService
          .getCustomersWithQueryToken({
            ...filter.getQueryToken(),
            skip: customerList.length,
          })
          .pipe(
            takeUntil(this._cancelSearch),
            tapResponse(this.handlePaginateResponse, this.handlePaginateError, this.handlePaginateComplete),
          ),
      ),
    ),
  );

  handlePaginateResponse = (result: ListResponseArgsOfCustomerInfoDTO) => {
    this.patchState({ customerList: [...this.customerList, ...result.result], customerListCount: result.hits });
    this.cacheSearchResult();
  };

  handlePaginateError = (err: any) => {
    console.error(err);
  };

  handlePaginateComplete = () => {
    this.patchState({ fetchingCustomerList: false });
  };

  reset(queryParams: Record<string, string> = {}) {
    this.patchState({ customerList: [], customerListCount: 0, queryParams, selectedOrderItemId: undefined });
    this.cancelSearch();
  }

  setQueryParams(queryParams: Record<string, string>) {
    this.patchState({ queryParams });
  }

  patchQueryParams(queryParams: Record<string, string>) {
    this.patchState({ queryParams: { ...this.queryParams, ...queryParams } });
  }

  _getSearchResultKey() {
    const jsonStr = JSON.stringify(this.filter?.getQueryParams());

    if (!jsonStr) {
      return;
    }

    return hash(jsonStr);
  }

  cacheSearchResult() {
    const customerList = this.customerList;
    const customerListCount = this.customerListCount;
    const key = this._getSearchResultKey();
    window.sessionStorage.setItem(
      String(key),
      JSON.stringify({
        customerList,
        customerListCount,
      }),
    );
  }

  restoreSearchResult() {
    const key = this._getSearchResultKey();
    const cache = window.sessionStorage.getItem(String(key));

    if (cache) {
      const { customerList, customerListCount } = JSON.parse(cache);
      this.patchState({ customerList, customerListCount, fetchingCustomerList: false });
      this._customerListRestored.next();
      return true;
    }
    return false;
  }

  cacheCustomerInfo() {
    const customerId = this.customerId;
    const customer = this.customer;

    if (!customerId || !customer) {
      return;
    }

    window.sessionStorage.setItem(
      `CUSTOMER_INFO_CACHE_${customerId}`,
      JSON.stringify({
        customer,
      }),
    );
  }

  restoreCustomerInfo() {
    const customerId = this.customerId;
    const cache = window.sessionStorage.getItem(`CUSTOMER_INFO_CACHE_${customerId}`);

    if (cache) {
      const { customer } = JSON.parse(cache);

      this.patchState({ customer });
    }
  }

  selectOrderItemId(orderItemId: number) {
    this.patchState({ selectedOrderItemId: orderItemId });
  }

  storeScrollIndex(scrollIndex: number) {
    const hash = this._getSearchResultKey();

    if (!hash) {
      return;
    }

    sessionStorage.setItem(`scrollIndex_${hash}_${this.processId}`, String(scrollIndex));
  }

  restoreScrollIndex() {
    const hash = this._getSearchResultKey();

    if (!hash) {
      return;
    }

    const scrollIndex = sessionStorage.getItem(`scrollIndex_${hash}_${this.processId}`);

    let result = 0;

    if (scrollIndex) {
      result = Number(scrollIndex);
    }
    return result;
  }

  resetScrollIndex() {
    const hash = this._getSearchResultKey();

    if (!hash) {
      return;
    }

    sessionStorage.removeItem(`scrollIndex_${hash}_${this.processId}`);
  }
}
