import { DataSource, CollectionViewer } from '@angular/cdk/collections';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { Store } from '@ngxs/store';
import { take, takeUntil, debounceTime, switchMap, filter } from 'rxjs/operators';
import { RemissionProduct, RemissionService } from '@isa/remission';
import { SetRemissionProducts } from 'apps/sales/src/app/core/store/actions/remission.actions';
import { RemissionHelperService } from '../../services/remission-helper.service';
import { isNullOrUndefined } from 'util';

export class RemissionListDataSource extends DataSource<RemissionProduct | undefined> {
  private pageSize = 10;
  private fetchedPages = new Set<number>();
  private cachedData = Array.from<RemissionProduct>({ length: 10 });
  private dataStream = new BehaviorSubject<(RemissionProduct | undefined)[]>(this.cachedData);
  destroy$ = new Subject();
  public loading = true;
  public results = false;
  public allHits = 0;
  public firstLoad = true;
  private currentPage = 0;
  private refetchPage = false;

  constructor(
    private search: RemissionService,
    private helper: RemissionHelperService,
    private store: Store,
    private remissionProcessId: number
  ) {
    super();
  }
  connect(collectionViewer: CollectionViewer): Observable<(RemissionProduct | undefined)[]> {
    collectionViewer.viewChange.pipe(takeUntil(this.destroy$)).subscribe((range) => {
      const startPage = this.getPageForIndex(range.start);
      const endPage = this.getPageForIndex(range.end);
      for (let i = startPage; i <= endPage; i++) {
        if (this.firstLoad && i === 0) {
          return;
        }
        this.currentPage = i;
        this.fetchPage(i);
      }
    });

    this.dataStream.pipe(takeUntil(this.destroy$), debounceTime(1000)).subscribe((i) => {
      this.store.dispatch(new SetRemissionProducts([...i]));
    });

    this.fetchPage(0);
    this.deleteItemEventSubscription();
    this.addItemEventSubscription();
    this.updateItemEventSubscription();
    return this.dataStream;
  }

  disconnect(): void {
    this.destroy$.next();
  }

  public getPageForIndex(index: number): number {
    return Math.floor(index / this.pageSize);
  }

  public getPrevItem(index: number) {
    if (index < 1) {
      return null;
    }
    if (!this.cachedData) {
      return null;
    }
    return this.cachedData[index - 1];
  }

  public getNextItem(index: number) {
    if (!index) {
      return null;
    }
    if (!this.cachedData) {
      return null;
    }
    return this.cachedData[index + 1];
  }

  private deleteItemEventSubscription() {
    this.helper.deleteRemissionListItem$
      .pipe(
        filter((data) => !isNullOrUndefined(data)),
        takeUntil(this.destroy$)
      )
      .subscribe((remissionProduct) => {
        const indexOfItemToRemove = this.cachedData.findIndex((it) => it && it.id === remissionProduct.id);
        if (indexOfItemToRemove !== -1) {
          const tempData = [...this.cachedData];
          tempData.splice(indexOfItemToRemove, 1);
          this.cachedData = [...tempData];
          if (this.cachedData[this.cachedData.length - 1 < 5 ? this.cachedData.length - 1 : 5] === undefined) {
            this.refetchPage = true;
            this.fetchPage(this.currentPage);
          }
          this.allHits -= 1;
          this.helper.setRemissionListHits(this.allHits);
          this.dataStream.next(this.cachedData);
        }
      });
  }

  private addItemEventSubscription() {
    this.helper.addRemissionListItem$
      .pipe(
        filter((data) => !isNullOrUndefined(data)),
        takeUntil(this.destroy$)
      )
      .subscribe((remissionProduct) => {
        const tempData = [...this.cachedData.filter((data) => !isNullOrUndefined(data))];
        const pageWhereFallsNewAddedItem = Math.floor(tempData.length / this.pageSize);
        this.currentPage = pageWhereFallsNewAddedItem;
        this.refetchPage = true;
        this.fetchPage(this.currentPage);
      });
  }

  private updateItemEventSubscription() {
    this.helper.updateRemissionListItem$
      .pipe(
        filter((data) => !isNullOrUndefined(data)),
        takeUntil(this.destroy$)
      )
      .subscribe((changes: { quantity: number; product: RemissionProduct }) => {
        const tempData = [...this.cachedData];
        const indexOfProduct = tempData.findIndex((pr) => pr.id === changes.product.id);
        tempData.splice(indexOfProduct, 1, { ...changes.product, remissionQuantity: changes.product.remissionQuantity - changes.quantity });
        this.cachedData = [...tempData];
        this.dataStream.next(this.cachedData);
      });
  }

  private fetchPage(page: number): number {
    if (page === 0) {
      this.results = false;
    }
    if (this.fetchedPages.has(page) && !this.refetchPage) {
      return;
    }
    this.fetchedPages.add(page);
    this.loading = true;

    const remissionSearch$ = this.search
      .updateRemissionFilter({
        remissionProcessId: this.remissionProcessId,
        changes: { skip: page * this.pageSize, take: this.pageSize },
      })
      .pipe(
        switchMap(() => {
          return this.search.getRemissionProducts({ remissionProcessId: this.remissionProcessId });
        })
      );

    remissionSearch$
      .pipe(take(1))
      .subscribe((result: { skip?: number; take?: number; hits?: number; items: RemissionProduct[]; completed: boolean }) => {
        this.loading = false;
        this.results = true;
        const products = [...result.items];

        if (page === 0 && !this.refetchPage) {
          this.cachedData = Array.from<RemissionProduct>({ length: result.hits });
          this.allHits = result.hits;
          this.helper.setRemissionListHits(this.allHits);
        }

        if (this.refetchPage) {
          const transferArray = [...this.cachedData.filter((cd) => !isNullOrUndefined(cd))];
          this.cachedData = Array.from<RemissionProduct>({ length: result.hits });
          this.cachedData.splice(0, transferArray.length, ...transferArray);
          this.allHits = result.hits;
          this.helper.setRemissionListHits(this.allHits);
        }

        const tempData = [...this.cachedData];
        const firstEmptyIndex = tempData.findIndex((dt) => dt === undefined);
        if (this.refetchPage) {
          const tempDataIds = [...tempData.filter((data) => !isNullOrUndefined(data)).map((data) => data.id)];
          const missingProductsFromPage = products.filter((p) => p && !tempDataIds.includes(p.id));
          tempData.splice(firstEmptyIndex !== -1 ? firstEmptyIndex : page * this.pageSize, this.pageSize, ...missingProductsFromPage);
          this.cachedData = Array.from<RemissionProduct>({ length: result.hits });
        } else {
          tempData.splice(firstEmptyIndex !== -1 ? firstEmptyIndex : page * this.pageSize, this.pageSize, ...products);
        }
        this.refetchPage = false;
        const startIndex = page * this.pageSize;
        const remainingItemsAfterTheStartIndex = tempData.length - startIndex;
        const pageEndIndex =
          startIndex + (remainingItemsAfterTheStartIndex < this.pageSize ? remainingItemsAfterTheStartIndex : this.pageSize);
        const currentPageTempData = tempData.slice(startIndex, pageEndIndex);
        const dataHolder = [...this.cachedData];
        for (let i = 0; i < page; i++) {
          dataHolder.splice(i * this.pageSize, this.pageSize, ...tempData.slice(i * this.pageSize, (i + 1) * this.pageSize));
        }
        dataHolder.splice(startIndex, this.pageSize, ...currentPageTempData);
        this.cachedData = [...dataHolder];
        this.dataStream.next(this.cachedData);

        if (page === 0) {
          // dispatch immediately on first page load
          this.firstLoad = false;
          this.store.dispatch(new SetRemissionProducts(this.cachedData));
        }
      });
  }
}
