import { Injectable } from '@angular/core';
import { of, Observable } from 'rxjs';
import { Product } from '../models/product.model';
import { RecentArticleSearch } from '../models/recent-article-search.model';
import { map } from 'rxjs/operators';
import { Search } from '../models/search.model';
import { FilterMapping } from '../mappings/filter.mapping';
import {
  SearchService,
  ListResponseArgsOfItemDTO,
  ItemDTO,
  AutocompleteTokenDTO,
  AutocompleteDTO,
  QueryTokenDTO,
  ResponseArgsOfItemDTO,
} from '@swagger/cat';
import { isNullOrUndefined } from 'util';
import { Store } from '@ngxs/store';
import { ProductSelectors } from '../store/selectors/product.selector';

@Injectable({
  providedIn: 'root',
})
export class ProductService {
  constructor(private searchService: SearchService, private filterMapping: FilterMapping, private store: Store) {}

  persistLastSearchToLocalStorage(param: string) {
    // get recent searches from local storage
    const recentSearches: RecentArticleSearch[] = JSON.parse(localStorage.getItem('recent_searches'));
    /*
     * check if there are search items in local storage, if there are not add search to local storage
     * else check if current search already exists in loacl storage, if exist delete it (deletion is made becouse
     * we want every search to display in LIFO order).
     * finaly push the new search at the end of the local storage array
     */
    if (recentSearches) {
      const searches = [
        ...recentSearches.filter((data) => {
          return data.name !== param;
        }),
        <RecentArticleSearch>{
          id: recentSearches[recentSearches.length - 1].id + 1,
          name: param,
        },
      ];
      localStorage.setItem('recent_searches', JSON.stringify(searches));
    } else {
      const searches = [
        <RecentArticleSearch>{
          id: 1,
          name: param,
        },
      ];
      localStorage.setItem('recent_searches', JSON.stringify(searches));
    }
  }

  getRecentSearches(): Observable<RecentArticleSearch[]> {
    const recentSearches: RecentArticleSearch[] = JSON.parse(localStorage.getItem('recent_searches'));
    return of(recentSearches);
  }
  // placeholder service method for calling product search API
  // searchProducts(params: string): Observable<Product[]> {
  //   this.persistLastSearchToLocalStorage(params);
  //   return of(procuctsMock);
  // }

  // service method for calling product search API
  searchItemsCheckForresults(search: Search): Observable<number> {
    this.persistLastSearchToLocalStorage(search.query);
    const queryToken = <QueryTokenDTO>{
      input: this.createSearchInput(search),
      skip: search.skip,
      take: search.take,
      hitsOnly: search.hits,
    };

    const queryWithFilters = this.filterMapping.toQueryTokenDto(queryToken, search.fitlers, search.archiveFilter, false);
    return this.searchService.SearchSearch(queryWithFilters).pipe(
      map((response: ListResponseArgsOfItemDTO) => {
        if (response.error) {
          throw new Error(response.message);
        }
        return response;
      }),
      map((response) => response.hits)
    );
  }

  // service method for calling product search API
  searchItems(search: Search): Observable<ItemDTO[]> {
    this.persistLastSearchToLocalStorage(search.query);
    const queryToken = <QueryTokenDTO>{
      input: this.createSearchInput(search),
      skip: search.skip,
      take: search.take,
    };

    const queryWithFilters = this.filterMapping.toQueryTokenDto(queryToken, search.fitlers, search.archiveFilter, false);
    return this.searchService.SearchSearch(queryWithFilters).pipe(
      map((response: ListResponseArgsOfItemDTO) => {
        if (response.error) {
          throw new Error(response.message);
        }
        return response;
      }),
      map((response) => response.result)
    );
  }

  searchItemsWithPagination(search: Search, skip, size, filters, isNegativeFiltering: boolean): Observable<ListResponseArgsOfItemDTO> {
    const queryToken = <QueryTokenDTO>{
      input: this.createSearchInput(search),
      skip: skip,
      take: size,
    };

    const queryWithFilters = this.filterMapping.toQueryTokenDto(queryToken, filters, search.archiveFilter, isNegativeFiltering);
    return this.searchService.SearchSearch(queryWithFilters).pipe(
      map((response) => {
        if (response.error) {
          throw new Error(response.message);
        }
        this.persistLastSearchToLocalStorage(search.query);
        return response;
      })
    );
  }

  getCachedProductResults() {
    const cachedResults = this.store.select(ProductSelectors.getCachedProducts);
    return cachedResults;
  }

  getItemById(id: number): Observable<any> {
    return this.searchService.SearchDetail(id).pipe(
      map((response) => {
        if (response.error) {
          throw new Error(response.message);
        }
        return response.result;
      })
    );
  }

  getItemByEan(ean: string): Observable<ItemDTO> {
    return this.searchService.SearchDetailByEAN(ean).pipe(
      map((response: ResponseArgsOfItemDTO) => {
        if (response.error) {
          throw new Error(response.message);
        }
        return response.result;
      })
    );
  }

  getItemByIds(ids: number[]): Observable<ItemDTO[]> {
    return this.searchService.SearchById(ids).pipe(
      map((response) => {
        if (response.error) {
          throw new Error(response.message);
        }
        return response;
      }),
      map((response) => response.result)
    );
  }

  complete(input: string, take: number, type: string): Observable<AutocompleteDTO[]> {
    return this.searchService
      .SearchAutocomplete(<AutocompleteTokenDTO>{
        input,
        take,
        type,
      })
      .pipe(
        map((response) => {
          if (response.error) {
            console.error(response.message);
          }
          return response.result;
        })
      );
  }

  createSearchInput(search: Search) {
    let input: any;
    if (isNullOrUndefined(search.type)) {
      input = { qs: search.query };
    } else if (search.type === 'author') {
      input = { author: search.query };
    } else if (search.type === 'title') {
      input = { title: search.query };
    } else if (search.type === 'publisher') {
      input = { publisher: search.query };
    }

    return input;
  }
}
