import { State, Action, StateContext, Store, Selector } from '@ngxs/store';
import * as actions from '../actions/cart.actions';
import { AddCartToProcess, SetFinishedOrderData, ChangeCurrentRoute } from '../actions/process.actions';
import { DeleteCartEntry } from '../actions/cart-entry.actions';
import { ProcessSelectors } from '../selectors/process.selectors';
import { CheckoutService } from '../../services/checkout.service';
import { tap, catchError } from 'rxjs/operators';
import { ShoppingCartDTO, PaymentDTO, CheckoutDTO, BuyerDTO, PayerDTO, DestinationDTO } from '@swagger/checkout';
import { DisplayOrderDTO, EnvironmentChannel } from '@swagger/oms';
import { User } from '../../models/user.model';
import { CustomerMapping } from '../../mappings/customer.mapping';
import { OrganisationDTO } from '@swagger/crm';
import { UserStateSyncData } from '../../models/user-state-sync.model';
import { AppUserDataSync } from '../actions/app.actions';
import { of } from 'rxjs';
import { ErrorService } from '../../error/component/error.service';
import { Router } from '@angular/router';
import { Breadcrumb } from '../../models/breadcrumb.model';
import { AddBreadcrumb } from '../actions/breadcrumb.actions';
import { CartService } from '../../../modules/cart/pages/services/cart.service';
import { Injectable } from '@angular/core';

export class CartStateModel {
  carts: { [key: number]: number[] };
}

@State<CartStateModel>({
  name: 'cart',
  defaults: {
    carts: {},
  },
})
@Injectable()
export class CartState {
  constructor(
    private store: Store,
    private checkoutService: CheckoutService,
    private customerMapper: CustomerMapping,
    private errorService: ErrorService,
    private router: Router,
    private cartService: CartService
  ) {}

  @Selector()
  static getCartEntries(cartState: CartStateModel) {
    return (cartId: number) => {
      return cartState.carts[cartId];
    };
  }

  @Action(actions.SetCartToProcess)
  setCartToProcess(ctx: StateContext<CartStateModel>, {}: actions.SetCartToProcess) {
    return this.checkoutService.createCart().pipe(
      tap((cart: ShoppingCartDTO) => {
        const state = ctx.getState();
        const carts = { ...state.carts };
        carts[cart.id] = [];
        ctx.patchState({ carts });
        this.store.dispatch(new AddCartToProcess(cart.id));
        this.syncApiState(carts);
      })
    );
  }

  @Action(actions.SetCartData)
  addItemToCart(ctx: StateContext<CartStateModel>, { cartEntryId }: actions.SetCartData) {
    const state = ctx.getState();
    const currentProcess = this.store.selectSnapshot(ProcessSelectors.getCurrentProcess);
    if (!currentProcess) {
      return;
    }

    const carts = { ...state.carts };
    carts[currentProcess.cartId] = [...carts[currentProcess.cartId], cartEntryId];

    ctx.patchState({ carts });
    this.syncApiState(carts);
  }

  @Action(actions.DeleteProductFromCart)
  deleteProductFromCart(ctx: StateContext<CartStateModel>, { cartId, cartEntryId }: actions.DeleteProductFromCart) {
    const state = ctx.getState();
    const allCarts = { ...state.carts };
    const cartEntries = allCarts[cartId];

    // Remove product from
    const productToDelete = cartEntries.find((entryId: number) => entryId === cartEntryId);
    if (productToDelete) {
      this.store.dispatch(new DeleteCartEntry(cartEntryId, true));
    }

    // Remove id from list of entries in cart
    allCarts[cartId] = cartEntries.filter((id: number) => id !== cartEntryId);

    ctx.patchState({
      carts: allCarts,
    });

    this.syncApiState(allCarts);
  }

  @Action(actions.DeleteCart)
  deleteCart(ctx: StateContext<CartStateModel>, { cartId }: actions.DeleteCart) {
    const state = ctx.getState();
    const carts = { ...state.carts };
    const cartEntries = carts[cartId];

    // Remove all items which are in cart
    if (cartEntries) {
      cartEntries.forEach((cartEntryId: number) => {
        this.store.dispatch(new DeleteCartEntry(cartEntryId, false));
      });
    }

    // Remove cart
    delete carts[cartId];

    ctx.patchState({
      carts,
    });

    this.syncApiState(carts);
  }

  @Action(actions.CreateOrder)
  createOrder(ctx: StateContext<CartStateModel>, { checkoutId }: actions.CreateOrder) {
    return this.checkoutService.createOrder(checkoutId).pipe(
      tap((order: { failedItemIds: { id: number; type: number }[]; displayOrder: DisplayOrderDTO[] }) => {
        this.store.dispatch(new SetFinishedOrderData(order));
      }),
      catchError((error) => {
        const status = error.status;
        const message = error && error.error ? error.error.message : undefined;
        const invalidProperties =
          error && error.error && error.error.invalidProperties ? JSON.stringify(error.error.invalidProperties) : undefined;
        this.errorService.addErrors(status, message, invalidProperties);
        this.openCart();
        return of(undefined);
      })
    );
  }

  openCart() {
    const newBread: Breadcrumb = {
      name: 'Warenkorb',
      path: '/cart/review',
    };

    this.store.dispatch(new AddBreadcrumb(newBread, 'shoppingCart'));
    this.store.dispatch(new ChangeCurrentRoute('/cart/review'));
    this.router.navigate(['/cart/review']);
  }

  @Action(actions.SetPaymentTypeToCheckout)
  getPaymentToCheckout(ctx: StateContext<CartStateModel>, { checkoutId, customer, isDelivery }: actions.SetPaymentTypeToCheckout) {
    return this.checkoutService.getPayments(checkoutId).pipe(
      tap((payment: PaymentDTO) => {
        const type = payment.availablePaymentTypes[0].key;
        this.store.dispatch(new actions.SetPaymentToCheckout(checkoutId, type, customer, isDelivery));
      })
    );
  }

  @Action(actions.SetPaymentToCheckout)
  setPaymentToCheckout(ctx: StateContext<CartStateModel>, { checkoutId, type, customer, isDelivery }: actions.SetPaymentToCheckout) {
    return this.checkoutService.setPaymentType(checkoutId, type).pipe(
      tap((checkout: CheckoutDTO) => {
        this.store.dispatch(new actions.SetBuyerToCheckout(checkout.id, customer, isDelivery));
      })
    );
  }

  @Action(actions.SetBuyerToCheckout)
  setBuyerToCheckout(ctx: StateContext<CartStateModel>, { checkoutId, customer, isDelivery }: actions.SetBuyerToCheckout) {
    return this.checkoutService.setBuyer(checkoutId, this.buyerObject(customer)).pipe(
      tap((checkout: CheckoutDTO) => {
        if (!isDelivery) {
          this.store.dispatch(new actions.CreateOrder(checkout.id));
        } else {
          this.store.dispatch(new actions.SetPayerToCheckout(checkout.id, customer));
        }
      })
    );
  }

  @Action(actions.SetPayerToCheckout)
  setPayerToCheckout(ctx: StateContext<CartStateModel>, { checkoutId, customer }: actions.SetPayerToCheckout) {
    return this.checkoutService.setPayer(checkoutId, this.payerObject(customer)).pipe(
      tap((checkout: CheckoutDTO) => {
        this.store.dispatch(new actions.CreateOrder(checkout.id));
      })
    );
  }

  @Action(actions.SetDestinationToCheckout)
  setDestinationToCheckout(
    ctx: StateContext<CartStateModel>,
    { checkoutId, destinationId, customer, target }: actions.SetDestinationToCheckout
  ) {
    const dest = this.destinationObject(customer, destinationId, target);
    return this.checkoutService.setDestination(checkoutId, destinationId, dest);
  }

  @Action(actions.ReloadCartData)
  reloadCarts(ctx: StateContext<CartStateModel>, { carts }: actions.ReloadCartData) {
    ctx.setState({ carts: { ...carts } });
  }

  @Action(actions.SetNotificationChannel)
  setNotificationChannel(ctx: StateContext<CartStateModel>, { checkoutId, notificationChannel }: actions.SetNotificationChannel) {
    return this.checkoutService.setNotificationChannel(checkoutId, notificationChannel);
  }

  private syncApiState(carts: { [key: number]: number[] }) {
    const userSyncData: UserStateSyncData = {
      carts,
    };
    const userData = JSON.stringify(userSyncData);
    this.store.dispatch(new AppUserDataSync(userData));
  }

  private buyerObject(customer: User) {
    const transformed = this.customerMapper.fromUser(customer);
    const buyerDTO: BuyerDTO = {
      address: transformed.address,
      buyerNumber: transformed.customerNumber,
      buyerType: 8, // B2C
      communicationDetails: transformed.communicationDetails,
      dateOfBirth: transformed.dateOfBirth,
      firstName: transformed.firstName,
      gender: transformed.gender,
      isTemporaryAccount: transformed.isGuestAccount,
      lastName: transformed.lastName,
      organisation: transformed.organisation,
      source: transformed.id,
      title: transformed.title,
    };

    return buyerDTO;
  }

  private payerObject(customer: User) {
    const invoice = customer.invoice_address ? customer.invoice_address : customer.delivery_addres;
    const transformed = this.customerMapper.fromUser(customer);
    let organisation: OrganisationDTO = {};

    if (invoice && invoice.company_name) {
      organisation = {
        name: invoice.company_name,
        department: invoice.company_department,
        vatId: invoice.company_tax_number,
      };
    }

    const payerDTO: PayerDTO = {
      address: {
        city: invoice.city,
        country: invoice.country,
        zipCode: invoice.zip + '',
        street: invoice.street,
        info: invoice.note ? invoice.note : null,
      },
      communicationDetails: transformed.communicationDetails,
      firstName: invoice.first_name,
      gender: this.customerMapper.getGender(invoice.gender),
      lastName: invoice.last_name,
      organisation: organisation ? organisation : null,
      payerNumber: invoice.payer_number,
      payerStatus: invoice.payer_status,
      payerType: 8, // B2C
      title: invoice.title,
      source: invoice.id,
    };

    return payerDTO;
  }

  private destinationObject(customer: User, destinationId: number, target?: EnvironmentChannel) {
    const delivery = customer.delivery_addres;
    if (!delivery) {
      this.cartService.addOrderErrorEvent();
      throw new Error(
        `Delvery address is missing for customer: ${customer.first_name + ' ' + customer.last_name} when trying to set destination object.
         Source => cart.state.ts.destinationObject. Line 238`
      );
    }
    const shippingAddress = this.customerMapper.fromUserToShippingAddressDTO(customer);

    const destinationDTO: DestinationDTO = {
      shippingAddress,
      target: target ? target : 2,
      id: destinationId,
    };

    return destinationDTO;
  }
}
