import { HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { CacheService } from '@core/cache';
import { DomainCustomerOrderService } from '@domain/oms';
import { ComponentStore } from '@ngrx/component-store';
import { tapResponse } from '@ngrx/operators';
import { Filter } from '@shared/components/filter';
import {
  BranchDTO,
  ListResponseArgsOfOrderItemListItemDTO,
  OrderItemListItemDTO,
  QuerySettingsDTO,
} from '@generated/swagger/oms-api';
import { isResponseArgs } from '@utils/object';
import { injectCancelSearchSubject } from '@shared/services/cancel-subject';
import { Observable, Subject } from 'rxjs';
import { switchMap, takeUntil, tap, withLatestFrom } from 'rxjs/operators';

export interface CustomerOrderSearchState {
  defaultSettings?: QuerySettingsDTO;
  queryParams?: Record<string, string>;
  filter?: Filter;
  message?: string;
  searchHistory?: string[];
  fetching: boolean;
  silentFetching: boolean;
  hits: number;
  results: OrderItemListItemDTO[];
  selectedBranch: BranchDTO;
  processId?: number;
}

@Injectable()
export class CustomerOrderSearchStore extends ComponentStore<CustomerOrderSearchState> {
  private _cancelSubject = injectCancelSearchSubject();

  get defaultSettings() {
    return this.get((s) => s.defaultSettings);
  }

  readonly defaultSettings$ = this.select((s) => s.defaultSettings);

  get filter() {
    return this.get((s) => {
      if (!s?.defaultSettings) {
        return undefined;
      }

      const filter = Filter.create(s?.defaultSettings);
      if (s.queryParams) {
        filter.fromQueryParams(s.queryParams);
      }

      return filter;
    });
  }

  readonly filter$ = this.select((s) => {
    if (!s?.defaultSettings) {
      return undefined;
    }

    const filter = Filter.create(s?.defaultSettings);
    if (s.queryParams) {
      filter.fromQueryParams(s.queryParams);
    }
    return filter;
  });

  get results() {
    return this.get((s) => s.results);
  }

  readonly results$ = this.select((s) => s.results);

  get searchHistory() {
    return this.get((s) => s.searchHistory);
  }

  readonly searchHistory$ = this.select((s) => s.searchHistory);

  get processId() {
    return this.get((s) => s.processId);
  }

  readonly processId$ = this.select((s) => s.processId);

  get hits() {
    return this.get((s) => s.hits);
  }

  readonly hits$ = this.select((s) => s.hits);

  get fetching() {
    return this.get((s) => s.fetching);
  }

  readonly fetching$ = this.select((s) => s.fetching);

  get silentFetching() {
    return this.get((s) => s.silentFetching);
  }

  get queryParams() {
    return this.get((s) => s.queryParams);
  }

  get message() {
    return this.get((s) => s.message);
  }

  selectedBranch$ = this.select((s) => s.selectedBranch);

  get selectedBranch() {
    return this.get((s) => s.selectedBranch);
  }

  get friendlyName() {
    return this.filter.input
      ?.find((f) => f.group === 'main')
      .input?.find((f) => f)
      .toStringValue();
  }

  readonly message$ = this.select((s) => s.message);

  readonly queryParams$ = this.select((s) => s.queryParams);

  readonly searchResultSubject = new Subject<{
    results: ListResponseArgsOfOrderItemListItemDTO;
    cached: boolean;
    clear: boolean;
  }>();

  readonly searchResultFromCacheSubject = new Subject<{ hits: number; results: OrderItemListItemDTO[] }>();

  readonly searchResultClearedSubject = new Subject<void>();

  searchStarted = new Subject<{ clear?: boolean; silentReload?: boolean }>();

  cancelSearch$ = new Subject<void>();

  constructor(
    private _domainGoodsInService: DomainCustomerOrderService,
    private _cache: CacheService,
  ) {
    super({
      fetching: false,
      silentFetching: false,
      hits: 0,
      results: [],
      searchHistory: [],
      selectedBranch: undefined,
    });
    this.loadDefaultSettings();
    this.loadSearchHistoryFromSessionStorage();
  }

  async loadDefaultSettings() {
    const defaultSettings = await this._domainGoodsInService.settings().toPromise();
    this.patchState({ defaultSettings });
  }

  setMessage(message: string) {
    this.patchState({ message });
  }

  setBranch(selectedBranch: BranchDTO) {
    this.patchState({ selectedBranch });
  }

  setSearchHistory(searchHistory: string[]) {
    this.patchState({ searchHistory });
  }

  resetFilter() {
    this.patchState({ queryParams: undefined });
  }

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

  async loadSearchHistoryFromSessionStorage() {
    const searchHistory = await this._cache.get<string[]>({ token: 'search_history_customer_orders' });
    this.setSearchHistory(searchHistory?.slice(0, 7) ?? []);
  }

  saveSearchHistoryToSessionStorage() {
    const searchHistory = Array.from(new Set([this.friendlyName, ...this.searchHistory].filter((value) => !!value))); // new Set to filter double values
    this._cache.set({ token: 'search_history_customer_orders' }, searchHistory);
    this.setSearchHistory(searchHistory?.slice(0, 7));
  }

  getCachedData({ queryToken }: { queryToken: Record<string, any> }) {
    return (
      this._cache.get<{
        hits: number;
        results: OrderItemListItemDTO[];
        fetching: boolean;
      }>(queryToken) || { results: [], hits: 0, fetching: false }
    );
  }

  setCache({
    queryToken,
    hits,
    results,
  }: {
    queryToken: Record<string, any>;
    hits: number;
    results: OrderItemListItemDTO[];
  }) {
    this._cache.set(queryToken, {
      hits,
      results,
      fetching: false,
    });
  }

  cancelSearchRequest() {
    this.cancelSearch$.next();
    this._cancelSubject.next();
    this.patchState({ fetching: false, silentFetching: false });
  }

  search = this.effect((options$: Observable<{ clear?: boolean; siletReload?: boolean }>) =>
    options$.pipe(
      tap((opt) => {
        this.searchStarted.next(opt);
        this.patchState({ message: undefined });
      }),
      withLatestFrom(this.results$, this.filter$, this.selectedBranch$),
      tap(([options, _, filter, __]) => {
        if (!options?.siletReload) {
          this.patchState({ fetching: true });
        } else {
          this.patchState({ silentFetching: true });
        }

        if (options?.clear) {
          this.searchResultClearedSubject.next();
        }
      }),
      // #4822 Quick Fix - HSC Bestellungen Suche wird nicht ausgelöst (Möglicherweise nicht die beste Lösung)
      switchMap(
        async ([options, results, filter, branch]): Promise<
          [typeof options, typeof results, typeof filter, typeof branch, boolean]
        > => {
          const queryToken = filter?.getQueryToken() ?? {};
          const cached: boolean =
            options?.siletReload &&
            (await this._cache.get({
              ...filter?.getQueryToken(),
              processId: this.processId,
              branchId: String(branch?.id),
            }));

          if (cached) {
            const cachedResults = await this._cache.get({
              ...queryToken,
              processId: this.processId,
              branchId: String(branch?.id),
            });
            if (cachedResults?.results?.length > 0) {
              this.patchState(cachedResults);
              this.searchResultFromCacheSubject.next({ hits: cachedResults.hits, results: cachedResults.results });
            }
          }

          return [options, results, filter, branch, cached];
        },
      ),
      switchMap(([options, results, filter, branch, cached]) => {
        const queryToken = filter?.getQueryToken() ?? {};

        if (options.clear) {
          queryToken.skip = 0;
          queryToken.take = 50;
        } else if (options.siletReload) {
          queryToken.skip = 0;
          queryToken.take = 50;
        } else {
          queryToken.skip = results.length;
          queryToken.take = 50;
        }

        if (branch?.id && !!queryToken.filter) {
          queryToken.filter['branch_id'] = String(branch?.id);
        }

        return this._domainGoodsInService.search(queryToken).pipe(
          takeUntil(this.cancelSearch$),
          takeUntil(this._cancelSubject),
          tapResponse(
            (res) => {
              let _results: OrderItemListItemDTO[] = [];
              if (options.siletReload) {
                _results = res.result;
              } else if (options.clear) {
                _results = res.result;
              } else {
                _results = [...results, ...(res.result ?? [])];
              }

              this.patchState({
                hits: res.hits,
                results: _results,
                fetching: false,
                silentFetching: false,
              });

              if (res?.hits > 0 && options?.clear) {
                this.saveSearchHistoryToSessionStorage();
              }

              this.searchResultSubject.next({ results: res, cached, clear: options?.clear });

              if (res?.hits === 0) {
                this.setMessage('keine Suchergebnisse');
              }
            },
            (err: Error) => {
              if (err instanceof HttpErrorResponse && isResponseArgs(err.error)) {
                this.searchResultSubject.next({ results: err.error, cached, clear: options?.clear });
              } else {
                this.searchResultSubject.next({
                  results: {
                    error: true,
                    message: err.message,
                  },
                  cached,
                  clear: options?.clear,
                });
              }
              this.patchState({ fetching: false, silentFetching: false });
              console.error('GoodsInSearchStore.search()', err);
            },
            () => {
              this.patchState({ fetching: false, silentFetching: false });
            },
          ),
        );
      }),
    ),
  );
}
