import {
  Component,
  OnInit,
  Input,
  Output,
  EventEmitter,
  ViewChild,
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  OnDestroy,
  ViewRef,
} from '@angular/core';
import { ModalService } from '@libs/ui';
import { Store, Select } from '@ngxs/store';
import { ProductAvailabilityService } from '../../../../core/services/product-availability.service';
import { DatePipe } from '@angular/common';
import { BranchSelectors } from '../../../../core/store/selectors/branch.selector';
import { Observable, Subject, of, forkJoin } from 'rxjs';
import { BranchDTO } from '@swagger/checkout';
import { DeliveryOption } from '../../../../core/models/delivery-option.model';
import { AvailabilityDTO } from '@swagger/availability';
import { ProductAvailability } from '../../../../core/models/product-availability.model';
import { CheckoutType } from '../../../../core/models/checkout-type.enum';
import { takeUntil, map, filter, tap, take } from 'rxjs/operators';
import { mapToIterable, distance, objectNotNull } from '../../../../core/utils/app.utils';
import { UpdateCartEntryType } from '../../../../core/store/actions/cart-entry.actions';
import { CartReviewItem } from '../..';
import { isNullOrUndefined } from 'util';
import { BranchService } from '../../../../core/services/branch.service';
import { CheckoutService } from 'apps/sales/src/app/core/services/checkout.service';
import { SearchDropdownComponent } from '@libs/ui/lib/search-dropdown';
import { ItemDTO } from '@swagger/cat';
import { allowedAvailabilityStatusCodes } from '../../../../core/utils/product.util';

export interface ProcessAvailibilityMapper {
  branchId: number;
  type: CheckoutType;
  av: AvailabilityDTO[];
}

@Component({
  selector: 'app-change-order-type',
  templateUrl: './change-order-type.component.html',
  styleUrls: ['./change-order-type.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ChangeOrderTypeComponent implements OnInit, OnDestroy {
  @Select(BranchSelectors.getBranches) branches$: Observable<{ [key: number]: BranchDTO }>;
  @ViewChild('branchesdropdown') branchesdd: SearchDropdownComponent;

  private _item: CartReviewItem;
  @Input() set item(val: CartReviewItem) {
    if (val) {
      this._item = val;
    }
  }
  @Input() id: string;

  @Output() closed: EventEmitter<boolean> = new EventEmitter();
  @Output() loaded = new EventEmitter();

  deliveryType = '';
  deliveryOptions = {
    delivery: DeliveryOption.DELIVERY,
    now: DeliveryOption.TAKE_NOW,
    pick_up: DeliveryOption.PICK_UP,
  };

  branches: string[] = [];
  filteredBranches: string[] = [];
  branchesData: BranchDTO[] = [];
  currentLocation = '';
  selectedBranch: BranchDTO;

  destroy$ = new Subject();

  currentAvailability: AvailabilityDTO;
  availabilityStatusText = '';
  currentPickUpDate = '';
  currentDeliveryDate = '';
  availability: ProductAvailability[] = [];
  currency = '';
  takeNowSuplierId: number;
  branchLoad = false;
  searchingNewBranch = false;
  _pikUpPrice: number;
  _deliveryPrice: number;

  get takeNowAvailable() {
    if (this.availability.filter((t) => t.type === CheckoutType.takeNow).length > 0) {
      return of(true);
    }
    return of(false);
  }

  get storeAvailable() {
    if (
      this.availability.filter(
        (t) =>
          t.type === CheckoutType.store &&
          (allowedAvailabilityStatusCodes(t.status) || (t.av && t.av.ssc === '830' && t.av.supplier === 'G'))
      ).length > 0
    ) {
      return of(true);
    }
    return of(false);
  }

  get shippingAvailable() {
    if (this.availability.filter((t) => t.type === CheckoutType.delivery && allowedAvailabilityStatusCodes(t.status)).length > 0) {
      return of(true);
    }
    return of(false);
  }

  get book() {
    return this._item.book;
  }

  get price(): number {
    return this._item.book.catalogAvailability.price.value.value;
  }

  get pickUpPrice(): number {
    if (this._pikUpPrice) {
      return this._pikUpPrice;
    }
    return this.price;
  }

  get deliverPrice(): number {
    if (this._deliveryPrice) {
      return this._deliveryPrice;
    }
    return this.price;
  }

  constructor(
    private modalService: ModalService,
    private store: Store,
    private productAvailabilityService: ProductAvailabilityService,
    private datePipe: DatePipe,
    private cdr: ChangeDetectorRef,
    private branchService: BranchService,
    private checkoutService: CheckoutService
  ) {}

  ngOnInit() {
    this.currency = this.book.catalogAvailability.price.value.currency;
    this.branches$
      .pipe(
        filter((branchData) => objectNotNull(branchData)),
        takeUntil(this.destroy$),
        take(1)
      )
      .subscribe((branchData: { [key: number]: BranchDTO }) => {
        this.processBranchData(branchData);
      });

    this.checkoutService
      .getSuppliers()
      .pipe(
        map((suppliers) => suppliers.find((supplier) => supplier.supplierNumber === 'F').id),
        takeUntil(this.destroy$)
      )
      .subscribe((suplierId: number) => {
        this.takeNowSuplierId = suplierId;
      });

    this.loadAvailability$(this.book, this.book.product.ean);
  }

  private processBranchData(branchData: { [key: number]: BranchDTO }) {
    const branches: BranchDTO[] = mapToIterable(branchData);
    if (!branches || branches.length < 1) {
      return;
    }
    if (!this._item || !this._item.branch) {
      return;
    }
    const userBranch = branches.find((t) => t.id === this._item.branch.id);
    if (!!userBranch) {
      this.selectedBranch = userBranch;
      this.currentLocation = userBranch.name;
    } else {
      this.selectedBranch = branches[0];
      this.currentLocation = branches[0].name;
    }
    // Dropdown options
    this.branches.push(this.currentLocation);
    this.branchesData.push(this.selectedBranch);
    branches.forEach((_branches: BranchDTO) => {
      if (_branches.id === this.selectedBranch.id) {
        return;
      }
      this.branches.push(_branches.name);
      this.branchesData.push(_branches);
    });
    this.filteredBranches = this.branches;
    if (!!userBranch) {
      this.filteredBranches = this.branchesData
        .sort((a: BranchDTO, b: BranchDTO) => this.branchSorterFn(a, b, userBranch))
        .map((t) => t.name);
    }
  }

  loadAvailability$(item: ItemDTO, ean: string) {
    forkJoin(this.availibilityFetch$(item, ean))
      .pipe(takeUntil(this.destroy$))
      .subscribe((availibilities: ProcessAvailibilityMapper[]) => {
        availibilities.forEach((item: ProcessAvailibilityMapper) => {
          if (item) {
            this.processAvailibilityData(item);
          }
        });

        this.loaded.emit(true);
        this.detectChanges();
      });
  }

  private availibilityFetch$(item: ItemDTO, ean: string): Observable<ProcessAvailibilityMapper>[] {
    const availabilityObservables: Observable<{ branchId: number; type: CheckoutType; av: AvailabilityDTO[] }>[] = [];
    const branches = this.store.selectSnapshot(BranchSelectors.getBranchesIterable);
    if (!branches || branches.length < 1) {
      return;
    }
    if (!this._item || !this._item.branch) {
      return;
    }
    const branch = branches.find((t) => t.id === this._item.branch.id);

    // Make call to API for all availabilities
    const storeAvailability = this.productAvailabilityService.getStoreAvailabilityWithCheck(item, ean, branch.id).pipe(
      takeUntil(this.destroy$),
      map((resp: any) => {
        if (resp.error) {
          return undefined;
        }
        return resp as {
          branchId: number;
          type: CheckoutType;
          av: AvailabilityDTO[];
        };
      })
    );
    const shippingAvailability = this.productAvailabilityService.getShippingAvailabilityWithCheck(item, ean, branch.id).pipe(
      takeUntil(this.destroy$),
      map((resp: any) => {
        if (resp.error) {
          return undefined;
        }
        return resp as {
          branchId: number;
          type: CheckoutType;
          av: AvailabilityDTO[];
        };
      })
    );

    // Map data to be the same
    const userBranchNumber = this.store.selectSnapshot(BranchSelectors.getUserBranch);
    const userBranch = branches.find((t) => t.branchNumber === userBranchNumber);
    if (!userBranch) {
      return;
    }
    const takeNowAvailability = this.productAvailabilityService.getStockInfo([userBranch.id], [item.id]).pipe(
      takeUntil(this.destroy$),
      map((av) => {
        const takeNowAv: AvailabilityDTO[] = [];
        av.forEach((availibility) => {
          const quantity = availibility.inStock.toString();
          if (+quantity > 0) {
            for (let x = 0; x < +quantity; x++) {
              takeNowAv.push({
                itemId: item.id,
                status: 1024,
                qty: +quantity,
                ssc: '999',
                price: this._item.book.catalogAvailability.price,
                supplierId: this.takeNowSuplierId,
              });
            }
          }
        });

        return {
          branchId: userBranch.id,
          type: CheckoutType.takeNow,
          av: takeNowAv,
        };
      })
    );

    availabilityObservables.push(storeAvailability);
    availabilityObservables.push(shippingAvailability);
    availabilityObservables.push(takeNowAvailability);

    return availabilityObservables;
  }

  private processAvailibilityData(item: ProcessAvailibilityMapper) {
    const preferredAvailability = this.getPreferedAvailability(item.av);
    if (!preferredAvailability) {
      return;
    }
    if (item.type === CheckoutType.store) {
      this.availabilityStatusText = preferredAvailability.sscText;
      this.currentPickUpDate = preferredAvailability.at ? this.datePipe.transform(new Date(preferredAvailability.at), 'dd.MM.yy') : null;
      if (preferredAvailability.price && preferredAvailability.price.value && preferredAvailability.price.value.value) {
        this._pikUpPrice = preferredAvailability.price.value.value;
      }
    }
    if (item.type === CheckoutType.delivery && preferredAvailability.at) {
      this.currentDeliveryDate = preferredAvailability.at ? this.datePipe.transform(new Date(preferredAvailability.at), 'dd.MM.yy') : null;
    }
    if (item.type === CheckoutType.delivery) {
      if (preferredAvailability.price && preferredAvailability.price.value && preferredAvailability.price.value.value) {
        this._deliveryPrice = preferredAvailability.price.value.value;
      }
    }

    item.av.forEach((av) => {
      this.availability.push({
        itemId: av.itemId,
        branchId: item.branchId,
        quantity: item.av.length,
        type: item.type,
        av: preferredAvailability,
        status: av.status,
      });
    });
  }

  branchSearch(term: string) {
    const userBranchNumber = this.store.selectSnapshot(BranchSelectors.getUserBranch);
    const userBranch = this.branchesData.find((t) => t.branchNumber === userBranchNumber);
    if (isNullOrUndefined(term) || term.length < 1) {
      this.filteredBranches = this.branchesData
        .sort((a: BranchDTO, b: BranchDTO) => this.branchSorterFn(a, b, userBranch))
        .map((t) => t.name);
      return;
    }
    this.branchService
      .searchBranches(term, 0, null)
      .pipe(
        takeUntil(this.destroy$),
        map((branches) => {
          // this.store.dispatch(new AddBranchesIfNotLoaded(branches));
          return branches.map((t) => t.name);
        }),
        tap((branchName) => (this.filteredBranches = branchName))
      )
      .subscribe();
  }

  private branchSorterFn(a: BranchDTO, b: BranchDTO, userBranch: BranchDTO) {
    const userLat = userBranch.address && userBranch.address.geoLocation ? userBranch.address.geoLocation.latitude : 0;
    const userLong = userBranch.address && userBranch.address.geoLocation ? userBranch.address.geoLocation.longitude : 0;
    const aLat = a.address && a.address.geoLocation ? a.address.geoLocation.latitude : 0;
    const aLong = a.address && a.address.geoLocation ? a.address.geoLocation.longitude : 0;
    const bLat = b.address && b.address.geoLocation ? b.address.geoLocation.latitude : 0;
    const bLong = b.address && b.address.geoLocation ? b.address.geoLocation.longitude : 0;
    const aDist = distance(userLat, userLong, aLat, aLong);
    const bDist = distance(userLat, userLong, bLat, bLong);
    return aDist - bDist;
  }

  storeBranches(): Observable<number[]> {
    return of(this.availability.filter((t) => t.type === CheckoutType.store).map((t) => t.branchId));
  }

  selectLocation(location: string) {
    this.branchLoad = true;
    this.searchingNewBranch = true;
    const branch = this.branchesData.find((b: BranchDTO) => b.name === location);
    // reset store availability
    this.availability = [...this.availability.filter((t) => t.type !== CheckoutType.store)];
    this.productAvailabilityService
      .getStoreAvailability(this.book, this.book.product.ean, branch.id)
      .pipe(takeUntil(this.destroy$))
      .subscribe((response) => {
        if (response.av.length > 0) {
          this.currentLocation = location;
          this.filteredBranches = [location, ...this.branches.filter((t) => t !== location)];
          this.selectedBranch = branch;
          const preferedAv = response.av.find((t) => t.preferred === 1);
          this.currentPickUpDate = this.datePipe.transform(new Date(preferedAv.at), 'dd.MM.yy');
          response.av.forEach((t) => {
            this.availability.push({
              itemId: t.itemId,
              branchId: branch.id,
              quantity: response.av.length,
              type: CheckoutType.store,
              av: preferedAv ? preferedAv : response.av[0],
              status: t.status,
            });
          });
        }
        this.branchesdd.stopLoading();
        this.branchLoad = false;
        this.detectChanges();
        this.searchingNewBranch = false;
      });
  }

  selectedAction(action: string) {
    if (this.branchLoad) {
      return;
    }
    if (action === DeliveryOption.TAKE_NOW) {
      this.currentAvailability = this.availability.find((t) => t.type === CheckoutType.takeNow).av;
      this.deliveryType = DeliveryOption.TAKE_NOW;
      // when on Rucklage the branche should be the user branch
      const userBranchNumber = this.store.selectSnapshot(BranchSelectors.getUserBranch);
      if (this.branchesData) {
        const userBranch = this.branchesData.find((t) => t.branchNumber === userBranchNumber);
        if (userBranch) {
          this.selectedBranch = userBranch;
        }
      }
    } else if (action === DeliveryOption.PICK_UP) {
      this.currentAvailability = this.availability.find((t) => t.type === CheckoutType.store).av;
      this.deliveryType = DeliveryOption.PICK_UP;
    } else {
      this.currentAvailability = this.availability.find((t) => t.type === CheckoutType.delivery).av;
      this.deliveryType = DeliveryOption.DELIVERY;
    }

    this.store.dispatch(
      new UpdateCartEntryType(
        this.deliveryType,
        this._item.book.id,
        this.currentAvailability,
        this.selectedBranch.id,
        this._item.deliveryType,
        this._item.branch.id
      )
    );

    this.closed.emit(true);
  }

  getPreferedAvailability(availability: AvailabilityDTO[]) {
    if (!availability) {
      return;
    }
    const preferredAvailability = availability.find((ava: AvailabilityDTO) => ava.preferred === 1);
    return preferredAvailability ? preferredAvailability : availability[0];
  }

  openDialog() {
    this.modalService.open(this.id);
  }

  closeModal(dialogSubmited: boolean = false) {
    this.closed.emit(dialogSubmited);
    this.modalService.close(this.id);
  }

  ngOnDestroy() {
    this.destroy$.next();
  }

  detectChanges() {
    setTimeout(() => {
      if (this.cdr !== null && this.cdr !== undefined && !(this.cdr as ViewRef).destroyed) {
        this.cdr.detectChanges();
      }
    }, 0);
  }
}
