import { Injectable } from '@angular/core';
import { Observable, combineLatest, BehaviorSubject, forkJoin, of } from 'rxjs';
import { RestFilterService } from './rest-filter.service';
import { switchMap, map, flatMap, mergeMap, startWith, take, shareReplay } from 'rxjs/operators';
import { filter as rxjsFilter } from 'rxjs/operators';
import { MappingService } from '../mappings/mapping.service';
import { Mapper } from '../mappings/mapper';
import { RemissionFilter } from '../models/remission-filter';
import { RemissionProduct } from '../models/remission-product';
import { RemiService, ReturnItemDTO, ReturnSuggestionDTO, KeyValueDTOOfStringAndString, ReturnService, StockService } from '@swagger/remi';
import { isNumber } from '@utils/common';

@Injectable({ providedIn: 'root' })
export class RestRemissionProductsService {
  constructor(
    private remiService: RemiService,
    private _stock: StockService,
    private returnService: ReturnService,
    private restFilterService: RestFilterService,
    private mapping: MappingService
  ) {}

  // Store priority products for supplierId
  priorityProducts = new Map<number, BehaviorSubject<number[]>>([
    [1, new BehaviorSubject<number[]>([])],
    [2, new BehaviorSubject<number[]>([])],
  ]);

  productGroupNames$: Observable<KeyValueDTOOfStringAndString[]>;

  // Products that should be shown on top right after adding to remission list
  updatePriorityProducts(productId: number, supplierId: number) {
    const applicablePriorityProducts$ = this.priorityProducts.get(supplierId);

    if (!applicablePriorityProducts$) {
      return;
    }

    applicablePriorityProducts$.next([productId, ...applicablePriorityProducts$.value]);
  }

  // Delete priority products
  deletePriorityProducts() {
    this.priorityProducts = new Map<number, BehaviorSubject<number[]>>([
      [1, new BehaviorSubject<number[]>([])],
      [2, new BehaviorSubject<number[]>([])],
    ]);
  }

  // Products that need to be removed from priority list
  removePriorityProducts(productId: number, supplierId: number) {
    const applicablePriorityProducts$ = this.priorityProducts.get(supplierId);

    if (!applicablePriorityProducts$) {
      return;
    }

    applicablePriorityProducts$.next(applicablePriorityProducts$.value.filter((id) => productId !== id));

    // TODO Only temporary until applicable supplierId is known
    const tempSupplierId = supplierId === 1 ? 2 : 1;
    const applicablePriorityProducts2$ = this.priorityProducts.get(tempSupplierId);
    applicablePriorityProducts2$.next(applicablePriorityProducts$.value.filter((id) => productId !== id));
  }

  getProductGroupNames(stockId: number): Observable<KeyValueDTOOfStringAndString[]> {
    if (!this.productGroupNames$) {
      this.productGroupNames$ = this.remiService
        .RemiProductgroups({ stockId })
        .pipe(map((response) => response.result))
        .pipe(shareReplay());
    }

    return this.productGroupNames$;
  }

  getRemissionproducts({ filter, stockId }: { filter: RemissionFilter; stockId: number }) {
    let allProducts$: Observable<{
      skip?: number;
      take?: number;
      hits?: number;
      items: RemissionProduct[];
      completed: boolean;
    }>;

    const filter$ = this.restFilterService.createQueryRemiTokenFilter({
      remissionSourceType: filter.source,
      remissionTargetName: filter.target.name,
      stockId,
      filter,
    });

    if (filter.source === 'ueberlauf') {
      const mapper = this.mapping.get<Mapper<ReturnSuggestionDTO, RemissionProduct>>('ReturnSuggestionDTO', 'RemissionProduct');

      const regularProducts$ = filter$.pipe(
        switchMap((qtFilter) =>
          this.remiService.RemiUeberlauf({
            queryToken: {
              stockId,
              supplierId: filter.target.id,
              filter: qtFilter,
              skip: filter.skip,
              take: filter.take,
            },
          })
        )
      );

      allProducts$ = regularProducts$.pipe(
        take(1),
        map((regular) => ({
          skip: regular.skip,
          take: regular.take,
          hits: regular.hits,
          completed: regular.completed || regular.skip + regular.take > regular.hits,
          items: [...regular.result].map((item) => mapper.map(item)),
        }))
      );
    } else if (filter.source === 'zentral') {
      const mapper = this.mapping.get<Mapper<ReturnItemDTO, RemissionProduct>>('ReturnItemDTO', 'RemissionProduct');

      const applicablePriorityProducts$ = this.priorityProducts.get(filter.target.id);

      const showPriorityProducts = (priorityProductIds) => {
        if (!priorityProductIds.length) {
          return false;
        }

        if (!filter.filter || !filter.filter.zentral) {
          return true;
        }

        const filterToCheck = filter.filter.zentral;
        const filterGroups = Object.keys(filterToCheck[filter.target.name]);
        let isFilterSet = false;

        for (const group of filterGroups) {
          if (!!filterToCheck[filter.target.name][group].length) {
            isFilterSet = true;
          }
        }

        return !isFilterSet;
      };

      // If Network Error return
      if (!applicablePriorityProducts$) {
        return of({ items: [], completed: true });
      }

      const priorityItems$ = applicablePriorityProducts$.pipe(
        switchMap((ids) => (!!showPriorityProducts(ids) ? of(ids) : of([]))),
        mergeMap((ids) => forkJoin(ids.map((id) => this.returnService.ReturnGetReturnItemById({ itemId: id })))),
        map((products) => products.map((product) => product.result)),
        startWith([]),
        map((products) => products.slice(filter.skip))
      );

      const regularProducts$ = filter$.pipe(
        switchMap((qtFilter) =>
          this.remiService.RemiPflichtremissionsartikel({
            queryToken: {
              stockId,
              supplierId: filter.target.id,
              filter: qtFilter,
              skip: filter.skip,
              take: filter.take,
            },
          })
        )
      );

      allProducts$ = combineLatest([regularProducts$, priorityItems$]).pipe(
        map(([regular, priority]) => ({
          skip: regular.skip,
          take: regular.take,
          hits: regular.hits,
          completed: regular.completed || regular.skip + regular.take > regular.hits,
          items: [...priority, ...regular.result.filter((item) => !priority?.some((pitem) => item.id === pitem.id))].map((item) =>
            mapper.map(item)
          ),
        }))
      );
    }

    // Setzen der productGroup im RemissionProduct
    const result1$ = allProducts$.pipe(
      flatMap((products) => {
        const itemsWithGroupnames$ = this.addGroupnameToProducts(products.items, stockId);

        return itemsWithGroupnames$.pipe(
          map((items) => ({
            ...products,
            items,
          }))
        );
      })
    );

    // Setzen des Lagerbestands im Remissionproduct
    const result2$ = result1$.pipe(
      flatMap((result) =>
        !result.items.length
          ? of({ items: [], completed: true })
          : this._stock
              .StockInStock({
                stockId,
                articleIds:
                  result.items &&
                  result.items.filter((item) => !!item.catalogProductNumber).map((product) => Number(product.catalogProductNumber)),
              })
              .pipe(
                rxjsFilter((stocks) => !stocks.error),
                map((stocks) => {
                  return {
                    ...result,
                    items: result.items.map((item) => {
                      const stock = stocks.result.find((info) => info.itemId === +item.catalogProductNumber);

                      if (!stock) {
                        const defaultStockData = {
                          inStock: 0,
                          remainingQuantity: 0,
                          remissionQuantity: item.remissionQuantity || 0,
                        };
                        return { ...item, ...defaultStockData };
                      }

                      // Available stock after stock in active remission
                      const availableStock = stock.inStock - stock.removedFromStock;
                      const remainingStock = availableStock < 0 ? 0 : availableStock;
                      let { remainingQuantity, remissionQuantity, name } = item;
                      // console.log({ name, remainingQuantity, remissionQuantity, availableStock, remainingStock });

                      if (!isNumber(remissionQuantity)) {
                        remissionQuantity = remainingStock - (remainingQuantity || 0);
                        if (remissionQuantity < 0) {
                          remissionQuantity = 0;
                        }
                      }

                      remainingQuantity = undefined;
                      if (!isNumber(remainingQuantity)) {
                        remainingQuantity = remainingStock - (remissionQuantity || 0);
                        if (remainingQuantity < 0) {
                          remainingQuantity = 0;
                        }
                      }

                      return {
                        ...item,
                        inStock: remainingStock,
                        remainingQuantity,
                        remissionQuantity,
                        // remainingQuantity:
                        //   typeof item.remainingQuantity === 'number'
                        //     ? item.remainingQuantity
                        //     : remainingStock - (item.remissionQuantity || 0) >= 0
                        //     ? remainingStock - (item.remissionQuantity || 0)
                        //     : 0,
                        // remissionQuantity:
                        //   typeof item.remissionQuantity === 'number'
                        //     ? item.remissionQuantity
                        //     : remainingStock - (item.remissionQuantity || 0) >= 0
                        //     ? remainingStock - (item.remissionQuantity || 0)
                        //     : 0,
                      };
                    }),
                  };
                })
              )
      ),
      map((products) => {
        products.items = products.items.filter((item) => !!item && item.inStock !== (undefined || null));
        return products;
      })
    );

    return result2$;
  }

  addGroupnameToProducts(products: RemissionProduct[], stockId: number): Observable<RemissionProduct[]> {
    if (!products.length) {
      return of(products);
    }

    const productGroupNames$ = this.getProductGroupNames(stockId);

    return productGroupNames$.pipe(
      map((groupNames) =>
        products.map((product) => {
          const proudctGroupNameMatch = groupNames.find((name) => name.key === product.productGroup);
          return {
            ...product,
            productGroupName: proudctGroupNameMatch ? proudctGroupNameMatch.value : '',
          } as RemissionProduct;
        })
      )
    );
  }
}
