import { Injectable } from '@angular/core';
import { Select } from '@ngxs/store';
import { BranchSelectors } from 'apps/sales/src/app/core/store/selectors/branch.selector';
import { Observable, NEVER } from 'rxjs';
import { switchMap, map, take, catchError } from 'rxjs/operators';
import { CollectingShelfService } from 'apps/sales/src/app/core/services/collecting-shelf.service';
import { AutocompleteTokenDTO, ResponseArgsOfIEnumerableOfAutocompleteDTO } from '@swagger/oms';
import { SearchStateFacade } from '@shelf-store';
import { CustomerService } from '@sales/core-services';

@Injectable({ providedIn: 'root' })
export class ShelfSearchFacadeService {
  @Select(BranchSelectors.getUserBranch) currentUserBranchId$: Observable<string>;

  constructor(
    private readonly customerService: CustomerService,
    private readonly collectingShelfService: CollectingShelfService,
    private readonly searchStateFacade: SearchStateFacade
  ) {}

  async search(
    queryString: string,
    options: {
      bypassValidation: boolean;
      allowCustomerCardSearch?: boolean;
    } = {
      bypassValidation: false,
      allowCustomerCardSearch: true,
    }
  ) {
    const { bypassValidation, allowCustomerCardSearch } = options;
    const searchQuery = await this.getSearchQuery(queryString, {
      allowCustomerCardSearch,
    });

    if (!bypassValidation && !this.isValidSearchQuery(searchQuery)) {
      return;
    }

    this.searchStateFacade.setInput(searchQuery);
    return this.requestSearch(true);
  }

  async searchWithBarcode(
    barcode: string,
    options: { allowCustomerCardSearch: boolean } = {
      allowCustomerCardSearch: true,
    }
  ) {
    const searchQuery = await this.getSearchQuery(this.getBarcodeSearchQuery(barcode), {
      allowCustomerCardSearch: options.allowCustomerCardSearch,
    });

    if (!this.isValidSearchQuery(searchQuery)) {
      return;
    }

    this.searchStateFacade.setInput(searchQuery);
    return this.requestSearch(true);
  }

  searchForAutocomplete(queryString: string, options: { selectedFilters?: { [key: string]: string } } = {}) {
    const searchQuery = queryString.trim();
    const autoCompleteQuery: AutocompleteTokenDTO = this.generateAutocompleteToken({
      queryString: searchQuery,
      filter: options.selectedFilters || {},
      take: 5,
    });

    if (!this.isValidSearchQuery(searchQuery)) {
      return NEVER;
    }

    return this.requestAutocompleteSearch(autoCompleteQuery);
  }

  private requestSearch(isNewSearch: boolean = false) {
    return this.searchStateFacade.fetchResult({ isNewSearch });
  }

  private getSearchQuery(queryString: string, options: { allowCustomerCardSearch: boolean }): Promise<string> {
    if (options.allowCustomerCardSearch && this.isCustomerCard(queryString)) {
      return this.getCustomerNumber(queryString);
    }

    return Promise.resolve(queryString.trim());
  }

  private generateAutocompleteToken(params: {
    queryString: string;
    take?: number;
    filter?: {
      [key: string]: string;
    };
  }): AutocompleteTokenDTO {
    return {
      input: params.queryString,
      take: params.take || 5,
      filter: params.filter || {},
    };
  }

  private requestAutocompleteSearch(autocompleteToken: AutocompleteTokenDTO): Observable<ResponseArgsOfIEnumerableOfAutocompleteDTO> {
    return this.currentUserBranchId$.pipe(switchMap(() => this.collectingShelfService.searchWarenausgabeAutocomplete(autocompleteToken)));
  }

  private isValidSearchQuery(queryString: string): boolean {
    return !!queryString && !!queryString.length;
  }

  private getBarcodeSearchQuery(barcode: string) {
    return barcode.replace('ORD:', '').trim();
  }

  private isCustomerCard(queryString: string): boolean {
    const regExp = /^(?=.*\d)(?=.*[A-Z])[A-Z0-9]{8,12}$/gi;
    return regExp.test(queryString.trim());
  }

  private getCustomerNumber(customerCardNumber: string): Promise<string> {
    return this.customerService
      .searchCustomer(customerCardNumber.trim())
      .pipe(
        map((response) => response.customers),
        map((customers) => (customers[0] ? customers[0].customerNumber : customerCardNumber)),
        catchError(() => customerCardNumber),
        take(1)
      )
      .toPromise();
  }
}
