import { Injectable } from '@angular/core';
import { ItemDTO, SearchService } from '@swagger/cat';
import { AvailabilityDTO, BranchDTO, OLAAvailabilityDTO, StoreCheckoutService, SupplierDTO } from '@swagger/checkout';
import { combineLatest, Observable, of } from 'rxjs';
import {
  AvailabilityRequestDTO,
  AvailabilityService as SwaggerAvailabilityService,
  AvailabilityDTO as SwaggerAvailabilityDTO,
  AvailabilityType,
} from '@swagger/availability';
import { AvailabilityDTO as CatAvailabilityDTO } from '@swagger/cat';
import { map, shareReplay, switchMap, withLatestFrom, mergeMap, timeout } from 'rxjs/operators';
import { isArray, memorize } from '@utils/common';
import { OrderService } from '@swagger/oms';
import { ResponseArgsOfIEnumerableOfStockInfoDTO, StockDTO, StockInfoDTO, StockService } from '@swagger/remi';
import { ItemData } from './defs/item-data.model';
import { PriceDTO } from '@swagger/availability';
import { AvailabilityByBranchDTO } from './defs/availability-by-branch-dto.model';

@Injectable()
export class DomainAvailabilityService {
  constructor(
    private swaggerAvailabilityService: SwaggerAvailabilityService,
    private storeCheckoutService: StoreCheckoutService,
    private orderService: OrderService,
    private _stock: StockService
  ) {}

  @memorize()
  getSuppliers(): Observable<SupplierDTO[]> {
    return this.storeCheckoutService.StoreCheckoutGetSuppliers({}).pipe(
      map((response) => response.result),
      shareReplay()
    );
  }

  @memorize()
  getTakeAwaySupplier(): Observable<SupplierDTO> {
    return this.storeCheckoutService.StoreCheckoutGetSuppliers({}).pipe(
      map(({ result }) => result.find((supplier) => supplier?.supplierNumber === 'F')),
      shareReplay()
    );
  }

  @memorize()
  getBranches(): Observable<BranchDTO[]> {
    return this.storeCheckoutService.StoreCheckoutGetBranches({}).pipe(
      map((response) => response.result),
      shareReplay()
    );
  }

  @memorize()
  getCurrentStock(): Observable<StockDTO> {
    return this._stock.StockCurrentStock().pipe(
      map((response) => response.result),
      shareReplay()
    );
  }

  @memorize()
  getCurrentBranch(): Observable<BranchDTO> {
    return this._stock.StockCurrentBranch().pipe(
      map((response) => ({
        id: response.result.id,
        name: response.result.name,
        address: response.result.address,
        branchType: response.result.branchType,
        branchNumber: response.result.branchNumber,
        changed: response.result.changed,
        created: response.result.created,
        isDefault: response.result.isDefault,
        isOnline: response.result.isOnline,
        key: response.result.key,
        label: response.result.label,
        pId: response.result.pId,
        shortName: response.result.shortName,
        status: response.result.status,
        version: response.result.version,
      })),
      shareReplay()
    );
  }

  @memorize({})
  getLogisticians() {
    return this.orderService.OrderGetLogisticians({}).pipe(
      map((response) => response.result?.find((l) => l.logisticianNumber === '2470')),
      shareReplay()
    );
  }

  getTakeAwayAvailabilityByBranches({
    branchIds,
    itemId,
    price,
    quantity,
  }: {
    branchIds: number[];
    itemId: number;
    price: PriceDTO;
    quantity: number;
  }): Observable<AvailabilityByBranchDTO[]> {
    return this._stock.StockStockRequest({ stockRequest: { branchIds, itemId } }).pipe(
      map((response) => response.result),
      withLatestFrom(this.getTakeAwaySupplier()),
      map(([result, supplier]) => {
        const availabilities: AvailabilityByBranchDTO[] = result.map((stockInfo) => {
          return {
            availableQuantity: stockInfo.availableQuantity,
            availabilityType: quantity <= stockInfo.inStock ? 1024 : 1, // 1024 (=Available)
            inStock: stockInfo.inStock,
            supplierSSC: quantity <= stockInfo.inStock ? '999' : '',
            supplierSSCText: quantity <= stockInfo.inStock ? 'Filialentnahme' : '',
            price,
            supplier: { id: supplier?.id },
            branchId: stockInfo.branchId,
          };
        });
        return availabilities;
      }),
      shareReplay()
    );
  }

  getTakeAwayAvailability({ item, quantity }: { item: ItemData; quantity: number }): Observable<AvailabilityDTO> {
    return this.getCurrentStock().pipe(
      switchMap((s) => this._stock.StockInStock({ articleIds: [item.itemId], stockId: s.id })),
      withLatestFrom(this.getTakeAwaySupplier(), this.getCurrentBranch()),
      map(([response, supplier, branch]) => {
        const price = item?.price;
        return this._mapToTakeAwayAvailability({ response, supplier, branch, quantity, price });
      }),
      shareReplay()
    );
  }

  @memorize({ ttl: 10000 })
  getTakeAwayAvailabilityByBranch({
    branch,
    itemId,
    price,
    quantity,
  }: {
    branch: BranchDTO;
    itemId: number;
    price: PriceDTO;
    quantity: number;
  }): Observable<AvailabilityDTO> {
    return combineLatest([
      this._stock.StockStockRequest({ stockRequest: { branchIds: [branch.id], itemId } }),
      this.getTakeAwaySupplier(),
    ]).pipe(
      map(([response, supplier]) => {
        return this._mapToTakeAwayAvailability({ response, supplier, branch, quantity, price });
      }),
      shareReplay()
    );
  }

  getTakeAwayAvailabilityByEan({
    eans,
    price,
    quantity,
  }: {
    eans: string[];
    price: PriceDTO;
    quantity: number;
  }): Observable<AvailabilityDTO> {
    return this.getCurrentStock().pipe(
      switchMap((s) => this._stock.StockInStockByEAN({ eans, stockId: s.id })),
      withLatestFrom(this.getTakeAwaySupplier(), this.getCurrentBranch()),
      map(([response, supplier, branch]) => {
        return this._mapToTakeAwayAvailability({ response, supplier, branch, quantity, price });
      }),
      shareReplay()
    );
  }

  getTakeAwayAvailabilitiesByEans({ eans }: { eans: string[] }): Observable<StockInfoDTO[]> {
    const eansFiltered = Array.from(new Set(eans));
    return this.getCurrentStock().pipe(
      switchMap((s) => this._stock.StockInStockByEAN({ eans: eansFiltered, stockId: s.id })),
      withLatestFrom(this.getTakeAwaySupplier(), this.getCurrentBranch()),
      map((response) => response[0].result),
      shareReplay()
    );
  }

  @memorize({ ttl: 10000 })
  getPickUpAvailability({ item, branch, quantity }: { item: ItemData; quantity: number; branch: BranchDTO }): Observable<AvailabilityDTO> {
    return this.swaggerAvailabilityService
      .AvailabilityStoreAvailability([
        {
          qty: quantity,
          ean: item?.ean,
          itemId: item?.itemId ? String(item?.itemId) : null,
          shopId: branch?.id,
          price: item?.price,
        },
      ])
      .pipe(
        map((r) => this._mapToPickUpAvailability(r.result)?.find((_) => true)),
        shareReplay()
      );
  }

  @memorize({ ttl: 10000 })
  getDeliveryAvailability({ item, quantity }: { item: ItemData; quantity: number }): Observable<AvailabilityDTO> {
    return this.swaggerAvailabilityService
      .AvailabilityShippingAvailability([
        {
          ean: item?.ean,
          itemId: item?.itemId ? String(item?.itemId) : null,
          price: item?.price,
          qty: quantity,
        },
      ])
      .pipe(
        timeout(5000),
        map((r) => this._mapToShippingAvailability(r.result)?.find((_) => true)),
        shareReplay()
      );
  }

  @memorize({ ttl: 10000 })
  getDigDeliveryAvailability({ item, quantity }: { item: ItemData; quantity: number }): Observable<AvailabilityDTO> {
    return this.swaggerAvailabilityService
      .AvailabilityShippingAvailability([
        {
          qty: quantity,
          ean: item?.ean,
          itemId: item?.itemId ? String(item?.itemId) : null,
          price: item?.price,
        },
      ])
      .pipe(
        timeout(5000),
        map((r) => {
          const availabilities = r.result;
          const preferred = availabilities?.find((f) => f.preferred === 1);

          const availability: AvailabilityDTO = {
            availabilityType: preferred?.status,
            ssc: preferred?.ssc,
            sscText: preferred?.sscText,
            supplier: { id: preferred?.supplierId },
            isPrebooked: preferred?.isPrebooked,
            estimatedShippingDate: preferred?.requestStatusCode === '32' ? preferred?.altAt : preferred?.at,
            estimatedDelivery: preferred?.estimatedDelivery,
            price: preferred?.price,
            logistician: { id: preferred?.logisticianId },
            supplierProductNumber: preferred?.supplierProductNumber,
            supplierInfo: preferred?.requestStatusCode,
            lastRequest: preferred?.requested,
          };
          return availability;
        }),
        shareReplay()
      );
  }

  @memorize({ ttl: 10000 })
  getB2bDeliveryAvailability({ item, quantity }: { item: ItemData; quantity: number }): Observable<AvailabilityDTO> {
    const logistician$ = this.getLogisticians();

    const currentBranch$ = this.getCurrentBranch();

    return currentBranch$.pipe(
      timeout(5000),
      mergeMap((branch) =>
        this.getPickUpAvailability({ item, quantity, branch }).pipe(
          mergeMap((availability) => logistician$.pipe(map((logistician) => ({ ...availability, logistician: { id: logistician.id } })))),
          shareReplay()
        )
      )
    );
  }

  @memorize({ ttl: 10000 })
  getDownloadAvailability({ item }: { item: ItemData }): Observable<AvailabilityDTO> {
    return this.swaggerAvailabilityService
      .AvailabilityShippingAvailability([
        {
          ean: item?.ean,
          itemId: item?.itemId ? String(item?.itemId) : null,
          price: item?.price,
          qty: 1,
        },
      ])
      .pipe(
        map((r) => {
          const availabilities = r.result;
          const preferred = availabilities?.find((f) => f.preferred === 1);

          const availability: AvailabilityDTO = {
            availabilityType: preferred?.status,
            ssc: preferred?.ssc,
            sscText: preferred?.sscText,
            supplier: { id: preferred?.supplierId },
            isPrebooked: preferred?.isPrebooked,
            estimatedShippingDate: preferred?.requestStatusCode === '32' ? preferred?.altAt : preferred?.at,
            price: preferred?.price,
            supplierProductNumber: preferred?.supplierProductNumber,
            logistician: { id: preferred?.logisticianId },
            supplierInfo: preferred?.requestStatusCode,
            lastRequest: preferred?.requested,
          };
          return availability;
        }),
        shareReplay()
      );
  }

  @memorize({ ttl: 10000 })
  getTakeAwayAvailabilities(items: { id: number; price: PriceDTO }[], branchId: number) {
    return this._stock.StockGetStocksByBranch({ branchId }).pipe(
      map((req) => req.result?.find((_) => true)?.id),
      switchMap((stockId) =>
        stockId
          ? this._stock.StockInStock({ articleIds: items.map((i) => i.id), stockId })
          : of({ result: [] } as ResponseArgsOfIEnumerableOfStockInfoDTO)
      ),
      timeout(20000),
      withLatestFrom(this.getTakeAwaySupplier()),
      map(([response, supplier]) => {
        return response.result?.map((stockInfo) =>
          this._mapToTakeAwayAvailabilities({
            stockInfo,
            supplier,
            quantity: 1,
            price: items?.find((i) => i.id === stockInfo.itemId)?.price,
          })
        );
      }),
      shareReplay()
    );
  }

  @memorize({ ttl: 10000 })
  getPickUpAvailabilities(payload: AvailabilityRequestDTO[], preferred?: boolean) {
    return this.swaggerAvailabilityService.AvailabilityStoreAvailability(payload).pipe(
      timeout(20000),
      map((response) => (preferred ? this._mapToPickUpAvailability(response.result) : response.result))
    );
  }

  @memorize({ ttl: 10000 })
  getDeliveryAvailabilities(payload: AvailabilityRequestDTO[]) {
    return this.swaggerAvailabilityService.AvailabilityShippingAvailability(payload).pipe(
      timeout(20000),
      map((response) => this._mapToShippingAvailability(response.result))
    );
  }

  @memorize({ ttl: 10000 })
  getDigDeliveryAvailabilities(payload: AvailabilityRequestDTO[]) {
    return this.swaggerAvailabilityService.AvailabilityShippingAvailability(payload).pipe(
      timeout(20000),
      map((response) => this._mapToShippingAvailability(response.result))
    );
  }

  @memorize({ ttl: 10000 })
  getB2bDeliveryAvailabilities(payload: AvailabilityRequestDTO[]) {
    const logistician$ = this.getLogisticians();

    return this.getPickUpAvailabilities(payload, true).pipe(
      timeout(20000),
      switchMap((availability) =>
        logistician$.pipe(map((logistician) => ({ availability: [...availability], logistician: { id: logistician.id } })))
      ),
      shareReplay()
    );
  }

  getPriceForAvailability(
    purchasingOption: string,
    catalogAvailability: CatAvailabilityDTO | AvailabilityDTO,
    availability: AvailabilityDTO
  ): PriceDTO {
    switch (purchasingOption) {
      case 'take-away':
        return availability?.price || availability?.retailPrice;
      case 'delivery':
      case 'dig-delivery':
        if (catalogAvailability?.price?.value?.value < availability?.price?.value?.value) {
          return catalogAvailability?.price;
        }
        return availability?.price || catalogAvailability?.price;
    }
    return availability?.price;
  }

  isAvailable({ availability }: { availability: AvailabilityDTO }) {
    return [2, 32, 256, 1024, 2048, 4096].some((code) => availability?.availabilityType === code);
  }

  mapToOlaAvailability({
    availability,
    item,
    quantity,
  }: {
    availability: AvailabilityDTO;
    item: ItemDTO;
    quantity: number;
  }): OLAAvailabilityDTO {
    return {
      status: availability?.availabilityType,
      at: availability?.estimatedShippingDate,
      ean: item?.product?.ean,
      itemId: item?.id,
      format: item?.product?.format,
      isPrebooked: availability?.isPrebooked,
      logisticianId: availability?.logistician?.id,
      price: availability?.price,
      qty: quantity,
      ssc: availability?.ssc,
      sscText: availability?.sscText,
      supplierId: availability?.supplier?.id,
      supplierProductNumber: availability?.supplierProductNumber,
    };
  }

  private _mapToTakeAwayAvailability({
    response,
    supplier,
    branch,
    quantity,
    price,
  }: {
    response: ResponseArgsOfIEnumerableOfStockInfoDTO;
    supplier: SupplierDTO;
    branch: BranchDTO;
    quantity: number;
    price: PriceDTO;
  }): AvailabilityDTO {
    const stockInfo = response.result?.find((si) => si.branchId === branch.id);
    const inStock = stockInfo?.inStock ?? 0;
    const availability: AvailabilityDTO = {
      availabilityType: quantity <= inStock ? 1024 : 1, // 1024 (=Available)
      inStock: inStock,
      supplierSSC: quantity <= inStock ? '999' : '',
      supplierSSCText: quantity <= inStock ? 'Filialentnahme' : '',
      price,
      supplier: { id: supplier?.id },
      retailPrice: (stockInfo as any)?.retailPrice, // TODO: Change after API Update
    };
    return availability;
  }

  private _mapToTakeAwayAvailabilities({
    stockInfo,
    quantity,
    price,
    supplier,
  }: {
    stockInfo: StockInfoDTO;
    quantity: number;
    price: PriceDTO;
    supplier: SupplierDTO;
  }) {
    const inStock = stockInfo?.inStock ?? 0;

    const availability = {
      itemId: stockInfo.itemId,
      availabilityType: quantity <= inStock ? (1024 as AvailabilityType) : (1 as AvailabilityType), // 1024 (=Available)
      inStock: inStock,
      supplierSSC: quantity <= inStock ? '999' : '',
      supplierSSCText: quantity <= inStock ? 'Filialentnahme' : '',
      price,
      supplier: { id: supplier?.id },
    };
    return availability;
  }

  private _mapToPickUpAvailability(availabilities: SwaggerAvailabilityDTO[]) {
    if (isArray(availabilities)) {
      const preferred = availabilities.filter((f) => f.preferred === 1);
      const totalAvailable = availabilities.reduce((sum, av) => sum + (av?.qty || 0), 0);

      return preferred.map((p) => {
        return {
          availabilityType: p?.status,
          ssc: p?.ssc,
          sscText: p?.sscText,
          supplier: { id: p?.supplierId },
          isPrebooked: p?.isPrebooked,
          estimatedShippingDate: p?.requestStatusCode === '32' ? p?.altAt : p?.at,
          price: p?.price,
          inStock: totalAvailable,
          supplierProductNumber: p?.supplierProductNumber,
          supplierInfo: p?.requestStatusCode,
          lastRequest: p?.requested,
          itemId: p.itemId,
        };
      });
    }
  }

  private _mapToShippingAvailability(availabilities: SwaggerAvailabilityDTO[]) {
    const preferred = availabilities.filter((f) => f.preferred === 1);

    return preferred.map((p) => {
      return {
        availabilityType: p?.status,
        ssc: p?.ssc,
        sscText: p?.sscText,
        isPrebooked: p?.isPrebooked,
        estimatedShippingDate: p?.requestStatusCode === '32' ? p?.altAt : p?.at,
        estimatedDelivery: p?.estimatedDelivery,
        price: p?.price,
        supplierProductNumber: p?.supplierProductNumber,
        supplierInfo: p?.requestStatusCode,
        lastRequest: p?.requested,
        itemId: p.itemId,
      };
    });
  }
}
