import { Injectable } from '@angular/core';
import { map, filter, switchMap, tap, catchError } from 'rxjs/operators';
import { Observable, of } from 'rxjs';
import {
  ShoppingCartDTO,
  ResponseArgsOfShoppingCartDTO,
  AddToShoppingCartDTO,
  ResponseArgsOfCheckoutDTO,
  ResponseArgsOfIEnumerableOfBranchDTO,
  CheckoutDTO,
  ResponseArgsOfPaymentDTO,
  PaymentDTO,
  SupplierDTO,
  EntityDTOContainerOfDestinationDTO,
  AvailabilityDTO,
  UpdateShoppingCartItemDTO,
} from '@swagger/checkout';
import { BranchDTO } from '@swagger/crm';
import {
  ResponseArgsOfIEnumerableOfDisplayOrderDTO,
  DisplayOrderDTO,
  ResponseArgsOfSupplierOrderResult,
  OrderCheckoutService,
} from '@swagger/oms';
import { BuyerDTO, PayerDTO, ResponseArgsOfDestinationDTO, DestinationDTO } from '@swagger/checkout';
import { NotificationChannels } from '../models/notification-channels.enum';
import { HttpClient, HttpRequest, HttpResponse } from '@angular/common/http';
import { DeliveryType } from '../models/delivery-option.model';
import { StoreCheckoutService } from '@swagger/checkout';

@Injectable({
  providedIn: 'root',
})
export class CheckoutService {
  constructor(private checkOutService: StoreCheckoutService, private orderService: OrderCheckoutService, private http: HttpClient) {}

  // SHOPPINGCART
  createCart(): Observable<ShoppingCartDTO> {
    return this.checkOutService.StoreCheckoutCreateShoppingCart().pipe(
      map((response: ResponseArgsOfShoppingCartDTO) => {
        if (response.error) {
          throw new Error(response.message);
        }

        return response.result;
      })
    );
  }

  addItemToCart(shoppingCartId: number, items: AddToShoppingCartDTO[]): Observable<ShoppingCartDTO> {
    const params: StoreCheckoutService.StoreCheckoutAddItemToShoppingCartParams = {
      shoppingCartId,
      items,
    };

    return this.checkOutService.StoreCheckoutAddItemToShoppingCart(params).pipe(
      catchError((error) => {
        return of({ error: error });
      }),
      map((response: any) => {
        if (response.error) {
          throw new Error(response.message);
        }
        return response.result;
      })
    );
  }

  updateQuantityForItemInCart(
    shoppingCartId: number,
    shoppingCartItemId: number,
    updatedShoppingCartItem: UpdateShoppingCartItemDTO
  ): Observable<ShoppingCartDTO> {
    const params: StoreCheckoutService.StoreCheckoutUpdateShoppingCartItemParams = {
      shoppingCartId,
      shoppingCartItemId,
      values: updatedShoppingCartItem,
    };

    return this.checkOutService.StoreCheckoutUpdateShoppingCartItem(params).pipe(
      map((response: ResponseArgsOfShoppingCartDTO) => {
        if (response.error) {
          throw new Error(response.message);
        }

        return response.result;
      })
    );
  }

  updateDestinationForItemInCart(
    shoppingCartId: number,
    shoppingCartItemId: number,
    destination: EntityDTOContainerOfDestinationDTO
  ): Observable<ShoppingCartDTO> {
    const params: StoreCheckoutService.StoreCheckoutUpdateShoppingCartItemParams = {
      shoppingCartId,
      shoppingCartItemId,
      values: {
        destination,
      },
    };

    return this.checkOutService.StoreCheckoutUpdateShoppingCartItem(params).pipe(
      map((response: ResponseArgsOfShoppingCartDTO) => {
        if (response.error) {
          throw new Error(response.message);
        }

        return response.result;
      })
    );
  }

  updateItemInCart(
    shoppingCartId: number,
    shoppingCartItemId: number,
    destination: EntityDTOContainerOfDestinationDTO,
    quantity: number,
    availability: AvailabilityDTO
  ): Observable<ShoppingCartDTO> {
    const params: StoreCheckoutService.StoreCheckoutUpdateShoppingCartItemParams = {
      shoppingCartId,
      shoppingCartItemId,
      values: {
        quantity,
        destination,
        availability,
      },
    };

    return this.checkOutService.StoreCheckoutUpdateShoppingCartItem(params).pipe(
      map((response: ResponseArgsOfShoppingCartDTO) => {
        if (response.error) {
          throw new Error(response.message);
        }

        return response.result;
      })
    );
  }

  getBranches(): Observable<BranchDTO[]> {
    return this.checkOutService.StoreCheckoutGetBranches({}).pipe(
      map((response: ResponseArgsOfIEnumerableOfBranchDTO) => {
        if (response.error) {
          throw new Error(response.message);
        }

        return response.result;
      })
    );
  }

  // ORDER
  createOrder(checkoutId: number): Observable<{ failedItemIds: { id: number; type: number }[]; displayOrder: DisplayOrderDTO[] }> {
    const params: OrderCheckoutService.OrderCheckoutCreateOrderParams = {
      checkoutId,
    };

    return this.orderService.OrderCheckoutCreateOrderResponse(params).pipe(
      map((_r) => {
        if (_r.status === 207) {
          const partialOrders = Object.keys(_r.body.invalidProperties).map((t) => +t);
          return { partialOrders: partialOrders, response: _r.body as ResponseArgsOfIEnumerableOfDisplayOrderDTO };
        }
        return { response: _r.body as ResponseArgsOfIEnumerableOfDisplayOrderDTO };
      }),
      map((r: any) => {
        if (r.partialOrders) {
          const orders = r.partialOrders as number[];
          const response = r.response as ResponseArgsOfIEnumerableOfDisplayOrderDTO;
          if (response.error) {
            throw new Error(response.message);
          }
          const failedItemsIds = this.reOrder(orders);
          const items: { id: number; type: number }[] = [];
          response.result.forEach((t) => {
            t.items.forEach((i) => {
              const isDelivery = t.orderType === DeliveryType['ShippingAddress'];
              const isTakeNow = i.subsetItems[0].supplierLabel === 'F' ? true : false;
              if (failedItemsIds && failedItemsIds.includes(i.id)) {
                items.push({ id: i.id, type: isDelivery ? 3 : isTakeNow ? 1 : 2 });
              }
            });
          });
          return {
            failedItemIds: items,
            displayOrder: response.result,
          };
        } else {
          const response = r.response as ResponseArgsOfIEnumerableOfDisplayOrderDTO;
          if (response.error) {
            throw new Error(response.message);
          }
          return { failedItemIds: [], displayOrder: response.result };
        }
      })
    );
  }

  // Get a partial order from testing dev API
  createPartialOrder(orderId: number): Observable<{ failedItemIds: { id: number; type: number }[]; displayOrder: DisplayOrderDTO[] }> {
    const req = new HttpRequest<any>('GET', 'http://127.0.0.1:5000/api/partialorders');
    return this.http.request<any>(req).pipe(
      filter((_r) => _r instanceof HttpResponse),
      map((_r) => {
        return _r as HttpResponse<ResponseArgsOfIEnumerableOfDisplayOrderDTO>;
      }),
      map((_r) => {
        if (_r.status === 207) {
          const partialOrders = Object.keys(_r.body.invalidProperties).map((t) => +t);
          return { partialOrders: partialOrders, response: _r.body as ResponseArgsOfIEnumerableOfDisplayOrderDTO };
        }
        return { response: _r.body as ResponseArgsOfIEnumerableOfDisplayOrderDTO };
      }),
      map((r: any) => {
        if (r.partialOrders) {
          const orders = r.partialOrders as number[];
          const response = r.response as ResponseArgsOfIEnumerableOfDisplayOrderDTO;
          if (response.error) {
            throw new Error(response.message);
          }
          const failedItemsIds = this.reOrder(orders);
          const items: { id: number; type: number }[] = [
            { id: 42216778, type: 2 },
            { id: 42216778, type: 1 },
          ];
          response.result.forEach((t) => {
            t.items.forEach((i) => {
              const isDelivery = t.orderType === DeliveryType['ShippingAddress'];
              const isTakeNow = i.subsetItems[0].supplierLabel === 'F' ? true : false;
              if (failedItemsIds && failedItemsIds.includes(i.id)) {
                items.push({ id: i.id, type: isDelivery ? 3 : isTakeNow ? 1 : 2 });
              }
            });
          });
          return {
            failedItemIds: items,
            displayOrder: response.result,
          };
        } else {
          const response = r.response as ResponseArgsOfIEnumerableOfDisplayOrderDTO;
          if (response.error) {
            throw new Error(response.message);
          }
          return { failedItemIds: [], displayOrder: response.result };
        }
      })
    );
  }

  private reOrder(orders: number[]): number[] {
    const failedItemsIds: number[] = [];
    if (!orders || orders.length < 1) {
      return failedItemsIds;
    }
    let processedOrders = 0;
    orders.forEach((order) => {
      this.orderService
        .OrderCheckoutOrderAtSupplierResponse(order)
        .pipe(
          catchError((error) => {
            const resp = new HttpResponse<ResponseArgsOfSupplierOrderResult>({ status: error.status });
            return of(resp) as Observable<any>;
          }),
          map((r) => {
            return r as HttpResponse<ResponseArgsOfSupplierOrderResult>;
          }),
          map((r) => {
            return { status: r.status, response: r.body as ResponseArgsOfSupplierOrderResult };
          })
        )
        .toPromise()
        .catch((r) => {
          return r;
        })
        .then((r) => {
          processedOrders++;
          const status = r.status;
          const reorderResponse = r.response;
          if (status === 200 || status === 207) {
            if (reorderResponse.result && reorderResponse.result.failed && reorderResponse.result.failed.length > 0) {
              const items = reorderResponse.result.failed;
              if (items) {
                items.forEach((it) => {
                  if (it && it.itemId) {
                    failedItemsIds.push(it.itemId);
                  }
                });
              }
            }
          }
        });
    });
    if (orders.length === processedOrders) {
      return failedItemsIds;
    }
  }

  // CHECKOUT
  createCheckout(shoppingCartId: number): Observable<CheckoutDTO> {
    const params: StoreCheckoutService.StoreCheckoutCreateOrRefreshCheckoutParams = {
      shoppingCartId,
    };

    return this.checkOutService.StoreCheckoutCreateOrRefreshCheckout(params).pipe(
      map((response: ResponseArgsOfCheckoutDTO) => {
        if (response.error) {
          throw new Error(response.message);
        }

        return response.result;
      })
    );
  }

  getPayments(checkoutId: number): Observable<PaymentDTO> {
    const params: StoreCheckoutService.StoreCheckoutGetCheckoutPaymentParams = {
      checkoutId,
    };

    return this.checkOutService.StoreCheckoutGetCheckoutPayment(params).pipe(
      map((response: ResponseArgsOfPaymentDTO) => {
        if (response.error) {
          throw new Error(response.message);
        }

        return response.result;
      })
    );
  }

  setPaymentType(
    checkoutId: number,
    paymentType: 0 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096 | 8192 | 16384
  ): Observable<CheckoutDTO> {
    const params: StoreCheckoutService.StoreCheckoutSetPaymentTypeParams = {
      checkoutId,
      paymentType,
    };

    return this.checkOutService.StoreCheckoutSetPaymentType(params).pipe(
      map((response: ResponseArgsOfCheckoutDTO) => {
        if (response.error) {
          throw new Error(response.message);
        }

        return response.result;
      })
    );
  }

  setBuyer(checkoutId: number, buyerDTO: BuyerDTO): Observable<CheckoutDTO> {
    const params: StoreCheckoutService.StoreCheckoutSetBuyerParams = {
      checkoutId,
      buyerDTO,
    };

    return this.checkOutService.StoreCheckoutSetBuyer(params).pipe(
      map((response: ResponseArgsOfCheckoutDTO) => {
        if (response.error) {
          throw new Error(response.message);
        }

        return response.result;
      })
    );
  }

  setPayer(checkoutId: number, payerDTO: PayerDTO): Observable<CheckoutDTO> {
    const params: StoreCheckoutService.StoreCheckoutSetPayerParams = {
      checkoutId,
      payerDTO,
    };

    return this.checkOutService.StoreCheckoutSetPayer(params).pipe(
      map((response: ResponseArgsOfCheckoutDTO) => {
        if (response.error) {
          throw new Error(response.message);
        }

        return response.result;
      })
    );
  }

  setDestination(checkoutId: number, destiantionId: number, shippingAddress: DestinationDTO): Observable<DestinationDTO> {
    const params: StoreCheckoutService.StoreCheckoutUpdateDestinationParams = {
      destinationId: destiantionId,
      destinationDTO: shippingAddress,
      checkoutId: checkoutId,
    };

    return this.checkOutService.StoreCheckoutUpdateDestination(params).pipe(
      map((response: ResponseArgsOfDestinationDTO) => {
        if (response.error) {
          throw new Error(response.message);
        }

        return response.result;
      })
    );
  }

  getSuppliers(): Observable<SupplierDTO[]> {
    return this.checkOutService.StoreCheckoutGetSuppliers({}).pipe(
      map((response) => {
        if (response.error) {
          throw new Error(response.message);
        }

        return response.result;
      })
    );
  }

  setNotificationChannel(checkoutId: number, notificationChannels: NotificationChannels) {
    const params = <StoreCheckoutService.StoreCheckoutSetNotificationChannelsParams>{
      checkoutId,
      notificationChannel: notificationChannels,
    };

    return this.checkOutService.StoreCheckoutSetNotificationChannels(params).pipe(
      map((response) => {
        if (response.error) {
          throw new Error(response.message);
        }

        return response.result;
      })
    );
  }
}
