import { Injectable, OnDestroy } from '@angular/core';
import { DomainPackageInspectionService, PackageStatusChangedEvent } from '@domain/package-inspection';
import { ComponentStore, OnStoreInit } from '@ngrx/component-store';
import { tapResponse } from '@ngrx/operators';

import { ListResponseArgsOfPackageDTO2, PackageDTO2, QuerySettingsDTO } from '@generated/swagger/wws-api';
import { UiFilter } from '@ui/filter';
import { UiModalService } from '@ui/modal';
import { Observable, Subject } from 'rxjs';
import { debounceTime, filter, switchMap, takeUntil, tap, withLatestFrom } from 'rxjs/operators';
import { INITIAL_RESULT_COMPONENT_STATE, PackageResultComponentState } from './package-result.component.state';

@Injectable()
export class PackageResultComponentStore
  extends ComponentStore<PackageResultComponentState>
  implements OnStoreInit, OnDestroy
{
  showFilter$ = this.select((state) => state.showFilter);

  fetching$ = this.select((state) => state.fetching);

  get fetching(): boolean {
    return this.get((f) => f.fetching);
  }

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

  get filter(): UiFilter {
    return this.get((f) => f.filter);
  }

  pendingFilter$ = this.select((state) => state.pendingFilter);

  defaultFilter$ = this.select((state) => state.defaultFilter);

  packages$ = this.select((state) => state.packages);

  get packages(): PackageDTO2[] {
    return this.get((f) => f.packages);
  }

  total$ = this.select((state) => state.total);

  get total() {
    return this.get((f) => f.total);
  }

  private _onFetchPackagesResponse$ = new Subject<ListResponseArgsOfPackageDTO2>();

  onFetchPackagesResponse$ = this._onFetchPackagesResponse$.asObservable();

  private _cancleFetchPackages$ = new Subject<void>();

  constructor(
    private _packageInspectionService: DomainPackageInspectionService,
    private modal: UiModalService,
  ) {
    super(INITIAL_RESULT_COMPONENT_STATE);
  }

  ngrxOnStoreInit() {
    this.fetchSettings();

    this._packageInspectionService.events.pipe(takeUntil(this.destroy$)).subscribe((event) => {
      if (event instanceof PackageStatusChangedEvent) {
        this.refresh();
      }
    });
  }

  ngOnDestroy() {
    super.ngOnDestroy();
    this.cancelFetchPackages();
  }

  setShowFilter = this.updater((state, showFilter: boolean) => ({ ...state, showFilter }));

  setFetching = this.updater((state, fetching: boolean) => ({ ...state, fetching }));

  setFilter = this.updater((state, filter: UiFilter) => ({ ...state, filter }));

  setPendingFilter = this.updater((state, pendingFilter: UiFilter) => ({ ...state, pendingFilter }));

  setDefaultFilter = this.updater((state, defaultFilter: UiFilter) => ({ ...state, defaultFilter }));

  setPackages = this.updater((state, packages: PackageDTO2[]) => ({ ...state, packages }));

  appendPackages = this.updater((state, packages: PackageDTO2[]) => ({
    ...state,
    packages: [...state.packages, ...packages],
  }));

  setTotal = this.updater((state, total: number) => ({ ...state, total }));

  refresh = this.effect(($) =>
    $.pipe(
      debounceTime(250),
      withLatestFrom(this.filter$, this.packages$),
      tap(() => this.cancelFetchPackages()),
      switchMap(([_, filter, packages]) =>
        this._packageInspectionService
          .queryPackagesResponse({ ...filter.getQueryToken(), skip: 0, take: Math.max(packages.length, 50) })
          .pipe(takeUntil(this._cancleFetchPackages$), tapResponse(this.onFetchPackagesResponse, this.onFetchError)),
      ),
    ),
  );

  fetchSettings = this.effect(($) =>
    $.pipe(
      switchMap((_) =>
        this._packageInspectionService
          .getQuerySettings()
          .pipe(tapResponse(this.onFetchSettingsResponse, this.onFetchError)),
      ),
    ),
  );

  fetchPackages = this.effect((options$: Observable<{ keep?: boolean; take?: number }>) =>
    options$.pipe(
      tap((options) => this.beforeFetchPackages(options?.keep)),
      debounceTime(250),
      withLatestFrom(this.filter$),
      switchMap(([options, filter]) =>
        this._packageInspectionService
          .queryPackagesResponse({ ...filter.getQueryToken(), take: options.take || 50 })
          .pipe(takeUntil(this._cancleFetchPackages$), tapResponse(this.onFetchPackagesResponse, this.onFetchError)),
      ),
    ),
  );

  cancelFetchPackages() {
    this.setFetching(false);
    this._cancleFetchPackages$.next();
  }

  paginatePackages = this.effect(($) =>
    $.pipe(
      debounceTime(250),
      withLatestFrom(this.filter$, this.packages$, this.fetching$, this.total$),
      filter(([_, __, packages, fetching, total]) => !fetching && packages?.length < total),
      tap(() => this.setFetching(true)),
      switchMap(([_, filter, packages]) =>
        this._packageInspectionService
          .queryPackagesResponse({ ...filter.getQueryToken(), skip: packages.length, take: 50 })
          .pipe(tapResponse(this.onPaginatePackagesResponse, this.onFetchError)),
      ),
    ),
  );

  beforeFetchPackages(keep: boolean) {
    this.setFetching(true);
    if (!keep) {
      this.setTotal(0);
      this.setPackages([]);
    }
  }

  onFetchSettingsResponse = (querySettings: QuerySettingsDTO) => {
    const filter = UiFilter.create(querySettings);
    this.setDefaultFilter(UiFilter.create(filter));
    this.setPendingFilter(UiFilter.create(filter));
    this.setFilter(UiFilter.create(filter));
  };

  onFetchPackagesResponse = (response: ListResponseArgsOfPackageDTO2) => {
    this.setFetching(false);
    this.setTotal(response.hits);
    this.setPackages(response.result);
    this._onFetchPackagesResponse$.next(response);
  };

  onPaginatePackagesResponse = (response: ListResponseArgsOfPackageDTO2) => {
    this.setFetching(false);
    this.setTotal(response.hits);
    this.appendPackages(response.result);
    this._onFetchPackagesResponse$.next(response);
  };

  onFetchError = (error: any) => {
    console.error(error);
    this.modal.error('Laden der Packstücke fehlgeschlagen.', error);
    this.setFetching(false);
    this.setPackages([]);
  };

  restorePendingFilter = this.updater((state) => ({ ...state, pendingFilter: UiFilter.create(state.filter) }));

  resetPendingFilter = this.updater((state) => ({ ...state, pendingFilter: UiFilter.create(state.defaultFilter) }));

  applyPendingFilterToFilter = this.updater((state) => ({ ...state, filter: UiFilter.create(state.pendingFilter) }));
}
