import { Injectable } from '@angular/core';
import {
  OrderItemListItemDTO,
  StatusValues,
  OrderItemDTO,
  OrderItemSubsetDTO,
  OrderService,
  OrderItemProcessingStatusValue,
} from '@swagger/oms';
import { Observable, concat } from 'rxjs';
import * as actions from './details.actions';
import * as selectors from './details.selectors';
import { Store } from '@ngrx/store';
import { shareReplay, bufferCount, finalize, map } from 'rxjs/operators';

@Injectable({ providedIn: 'root' })
export class DetailsFacade {
  constructor(private store: Store<any>, private orderService: OrderService) {}

  getOrderItemsByOrderNumber$(orderNumber: string): Observable<OrderItemListItemDTO[]> {
    this.fetchOrderItemsByOrderNumber(orderNumber);
    return this.store.select(selectors.selectOrderItemsByOrderNumber, orderNumber).pipe(shareReplay());
  }

  getOrderItemsByOrderNumberAndProcessingStatus$(orderNumber: string, processingStatus: OrderItemProcessingStatusValue) {
    return this.getOrderItemsByOrderNumber$(orderNumber).pipe(
      map((items) => items.filter((item) => item.processingStatus === processingStatus))
    );
  }

  getOrderItemsByCompartmentCode$(compartmentCode: string): Observable<OrderItemListItemDTO[]> {
    this.fetchOrderItemsByCompartmentCode(compartmentCode);
    return this.store.select(selectors.selectOrderItemsByCompartmentCode, compartmentCode).pipe(shareReplay());
  }

  getOrderItemsByCompartmentCodeAndProcessingStatus$(compartmentCode: string, processingStatus: OrderItemProcessingStatusValue) {
    return this.getOrderItemsByCompartmentCode$(compartmentCode).pipe(
      map((items) => items.filter((item) => item.processingStatus === processingStatus))
    );
  }

  fetchOrderItemsByOrderNumber(orderNumber: string) {
    this.store.dispatch(actions.fetchDetailsByOrderNumber({ orderNumber }));
  }

  fetchOrderItemsByCompartmentCode(orderNumber: string) {
    this.store.dispatch(actions.fetchDetailsByOrderNumber({ orderNumber }));
  }

  changeStatus(
    changes: {
      orderItemSubsetId: number;
      orderItemId: number;
      orderId: number;
      data: StatusValues;
    }[]
  ) {
    const changeStatusRequests = changes.map(({ data, orderId, orderItemId, orderItemSubsetId }) =>
      this.orderService.OrderChangeStatus({
        data,
        orderId,
        orderItemId,
        orderItemSubsetId,
      })
    );

    return concat(...changeStatusRequests)
      .pipe(
        bufferCount(changeStatusRequests.length),
        finalize(() => {
          const orderItemSubsetIds = changes.reduce((itemIds, change) => {
            if (itemIds.some((id) => change.orderItemSubsetId === id)) {
              return itemIds;
            }
            return [...itemIds, change.orderItemSubsetId];
          }, [] as number[]);

          this.store.dispatch(actions.fetchDetailsOrderItemSubsetIds({ orderItemSubsetIds }));
        })
      )
      .toPromise();
  }

  patchOrderItems(
    changes: {
      orderItemId: number;
      orderId: number;
      orderItem: Partial<OrderItemDTO>;
    }[]
  ) {
    const requests = changes.map((payload) => this.orderService.OrderPatchOrderItem(payload));

    return concat(...requests)
      .pipe(
        bufferCount(changes.length),
        finalize(() => {
          const orderItemIds = changes.reduce((itemIds, change) => {
            if (itemIds.some((id) => change.orderItemId === id)) {
              return itemIds;
            }
            return [...itemIds, change.orderItemId];
          }, [] as number[]);

          this.store.dispatch(actions.fetchDetailsOrderItemIds({ orderItemIds }));
        })
      )
      .toPromise();
  }

  patchOrderItemSubsets(
    changes: {
      orderItemSubsetId: number;
      orderItemId: number;
      orderId: number;
      orderItemSubset: Partial<OrderItemSubsetDTO>;
    }[]
  ) {
    const requests = changes.map((payload) => this.orderService.OrderPatchOrderItemSubset(payload));

    return concat(...requests)
      .pipe(
        bufferCount(changes.length),
        finalize(() => {
          const orderItemSubsetIds = changes.reduce((itemIds, change) => {
            if (itemIds.some((id) => change.orderItemSubsetId === id)) {
              return itemIds;
            }
            return [...itemIds, change.orderItemSubsetId];
          }, [] as number[]);

          this.store.dispatch(actions.fetchDetailsOrderItemSubsetIds({ orderItemSubsetIds }));
        })
      )
      .toPromise();
  }

  pickedUp(items: OrderItemListItemDTO[], quantities?: Map<number, number>) {
    const data = items.map(({ orderId, orderItemId, orderItemSubsetId, quantity }) => ({
      orderId,
      orderItemId,
      orderItemSubsetId,
      data: {
        processingStatus: 256,
        quantity: !!quantities && quantities.has(orderItemId) ? quantities.get(orderItemId) : quantity,
      } as StatusValues,
    }));
    return this.changeStatus(data);
  }

  reorder(items: OrderItemListItemDTO[]) {
    const data = items.map(({ orderId, orderItemId, orderItemSubsetId }) => ({
      orderId,
      orderItemId,
      orderItemSubsetId,
      data: {
        processingStatus: 8192,
        compartmentNumber: '',
        compartmentInfo: '',
      } as StatusValues,
    }));
    return this.changeStatus(data);
  }

  backToStock(items: OrderItemListItemDTO[]) {
    const data = items.map(({ orderId, orderItemId, orderItemSubsetId }) => ({
      orderId,
      orderItemId,
      orderItemSubsetId,
      data: { processingStatus: 262144 } as StatusValues,
    }));
    return this.changeStatus(data);
  }

  async arrived(items: OrderItemListItemDTO[], compartmentInfo: string) {
    const payloads = items.map(({ orderId, orderItemId, orderItemSubsetId }) => ({
      orderId,
      orderItemId,
      orderItemSubsetId,
      data: { processingStatus: 128, compartmentInfo } as StatusValues,
    }));

    const firstPayload = payloads[0];
    let pendingPayloads = payloads.filter((f) => f.orderItemSubsetId !== firstPayload.orderItemSubsetId);
    const firstPayloadResult = (await this.changeStatus([firstPayload]))[0];

    pendingPayloads = pendingPayloads.map(({ orderItemId, orderId, orderItemSubsetId, data }) => ({
      orderItemId,
      orderId,
      orderItemSubsetId,
      data: {
        ...data,
        compartmentNumber: firstPayloadResult.result.item1.compartmentCode,
      },
    }));

    const pedingPayloadResults = await this.changeStatus(pendingPayloads);

    return [firstPayloadResult, ...pedingPayloadResults];
  }

  setComment({
    item,
    comment,
  }: {
    item: {
      orderId?: number;
      orderItemId?: number;
      orderItemSubsetId?: number;
    };
    comment?: string;
  }) {
    this.store.dispatch(
      actions.patchComment({
        orderId: item.orderId,
        orderItemId: item.orderItemId,
        orderItemSubsetId: item.orderItemSubsetId,
        comment,
      })
    );
  }

  setEstimatedShippingDate({
    items,
    estimatedShippingDate,
  }: {
    items: Array<{
      orderId?: number;
      orderItemId?: number;
      orderItemSubsetId?: number;
    }>;
    estimatedShippingDate: Date | string;
  }) {
    this.patchOrderItemSubsets(
      items.map((orderItem) => ({
        orderId: orderItem.orderId,
        orderItemId: orderItem.orderItemId,
        orderItemSubsetId: orderItem.orderItemSubsetId,
        orderItemSubset: {
          estimatedShippingDate: estimatedShippingDate instanceof Date ? estimatedShippingDate.toJSON() : estimatedShippingDate,
        },
      }))
    );
  }

  setPickUpDeadline({
    items,
    pickUpDeadline,
  }: {
    items: Array<{
      orderId?: number;
      orderItemId?: number;
      orderItemSubsetId?: number;
    }>;
    pickUpDeadline: Date | string;
  }) {
    const payload: {
      orderId: number;
      orderItemSubsetId: number;
      orderItemId: number;
      orderItemSubset: Partial<OrderItemSubsetDTO>;
    }[] = items.map((item) => ({
      orderId: item.orderId,
      orderItemId: item.orderItemId,
      orderItemSubsetId: item.orderItemSubsetId,
      orderItemSubset: {
        compartmentStop: new Date(pickUpDeadline).toISOString(),
      },
    }));

    this.patchOrderItemSubsets(payload);
  }
}
