import { Injectable } from '@angular/core';
import { DomainCatalogService } from '@domain/catalog';
import { Observable, Subject } from 'rxjs';
import { switchMap, takeUntil, tap, withLatestFrom } from 'rxjs/operators';

import { ComponentStore } from '@ngrx/component-store';
import { ItemDTO, QueryTokenDTO, UISettingsDTO } from '@generated/swagger/cat-search-api';
import { BranchDTO } from '@generated/swagger/checkout-api';
import { Filter } from '@shared/components/filter';
import { injectCancelSearchSubject } from '@shared/services/cancel-subject';
import { tapResponse } from '@ngrx/operators';

export interface ArticleSearchState {
  processId: number;
  filter: Filter;
  searchState: '' | 'fetching' | 'empty' | 'error';
  items: ItemDTO[];
  hits: number;
  selectedBranch: BranchDTO;
  selectedItemIds: number[];
  defaultSettings?: UISettingsDTO;
}

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

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

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

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

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

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

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

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

  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();
  }

  fetching$ = this.select((s) => s.searchState === 'fetching');

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

  searchCompleted = new Subject<{ state: ArticleSearchState; clear: boolean; orderBy: boolean }>();

  searchboxHint$ = this.select((s) => (s.searchState === 'empty' ? 'Keine Suchergebnisse' : undefined));

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

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

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

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

  constructor(private catalog: DomainCatalogService) {
    super({
      filter: undefined,
      hits: 0,
      items: [],
      processId: 0,
      searchState: '',
      selectedItemIds: [],
      selectedBranch: undefined,
    });
    this.setDefaultFilter();
  }

  setProcess(processId: number) {
    this.patchState({ processId });
  }

  setItems(items: ItemDTO[]) {
    this.patchState({ items });
  }

  setHits(hits: number) {
    this.patchState({ hits });
  }

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

  async setDefaultFilter(defaultQueryParams?: Record<string, string>) {
    const defaultSettings = await this.catalog.getSettings().toPromise();

    const filter = Filter.create(defaultSettings);

    if (defaultQueryParams) {
      filter?.fromQueryParams(defaultQueryParams);
    }

    this.setFilter(filter);
    this.patchState({ defaultSettings });
  }

  setFilter(filter: Filter) {
    this.patchState({ filter });
  }

  async resetFilter() {
    await this.setDefaultFilter();
  }

  setSelected({ selected, itemId }: { selected: boolean; itemId: number }) {
    const included = this.selectedItemIds.includes(itemId);

    if (!included && selected) {
      this.patchState({
        selectedItemIds: [...this.selectedItemIds, itemId],
      });
    } else if (included && !selected) {
      this.patchState({
        selectedItemIds: this.selectedItemIds.filter((id) => id !== itemId),
      });
    }
  }

  searchRequest(queryToken: QueryTokenDTO) {
    if (queryToken?.stockId) {
      return this.catalog.searchWithStockId({ queryToken });
    } else {
      return this.catalog.search({ queryToken });
    }
  }

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

  search = this.effect((options$: Observable<{ clear?: boolean; orderBy?: boolean; doNotTrack?: boolean }>) =>
    options$.pipe(
      tap((options) => {
        this.searchStarted.next({ clear: options?.clear });
        this.patchState({
          searchState: 'fetching',
          items: options.clear ? [] : this.items,
        });
      }),
      withLatestFrom(this.filter$, this.items$, this.selectedBranch$),
      switchMap(([options, filter, items, selectedBranch]) => {
        return this.searchRequest({
          ...filter.getQueryToken(),
          skip: items.length,
          take: 25,
          friendlyName: this.friendlyName,
          stockId: selectedBranch?.id,
          doNotTrack: options?.doNotTrack,
        }).pipe(
          takeUntil(this._cancelSubject),

          tapResponse(
            (res) => {
              const searchState = res.hits ? '' : 'empty';
              if (options.clear) {
                this.patchState({
                  hits: res.hits,
                  items: res.result,
                  searchState,
                });
              } else {
                const items = this.get((s) => s.items);
                this.patchState({
                  hits: res.hits,
                  items: [...items, ...res.result],
                  searchState,
                });
              }
              this.searchCompleted.next({ state: this.get(), clear: options?.clear, orderBy: options?.orderBy });
            },
            (err) => {
              this.patchState({ hits: 0, searchState: 'error', items: [] });
              this.searchCompleted.next({ state: this.get(), clear: options?.clear, orderBy: options?.orderBy });
            },
            () => {
              if (this.get((s) => s.searchState) === 'fetching') {
                this.patchState({ searchState: '' });
              }
            },
          ),
        );
      }),
    ),
  );
}
