import { State, Action, StateContext, Store } from '@ngxs/store';
import * as actions from '../actions/customer.actions';
import { tap, map, catchError } from 'rxjs/operators';
import { User } from '../../models/user.model';
import { AddBreadcrumb } from '../actions/breadcrumb.actions';
import {
  UpdateProcessName,
  AddCustomerIds,
  SetActiveCustomer,
  RemoveCustomerId,
  ChangeCurrentRoute,
  SetDetailsCustomer,
  SetOnlineCustomerCreationStatus,
} from '../actions/process.actions';
import { patch, append } from '@ngxs/store/operators';
import { Observable, of } from 'rxjs';
import { CustomerService } from '../../services/customer.service';
import { CustomerSearchResponse } from '../../models/customer-search-response.model';
import { ProcessSelectors } from '../selectors/process.selectors';
import { isNullOrUndefined } from 'util';
import { UserStateSyncData } from '../../models/user-state-sync.model';
import { AppUserDataSync } from '../actions/app.actions';
import { AppState } from './app.state';
import { Injectable } from '@angular/core';

export class CustomerStateModel {
  customers: { [id: number]: User };
  lastCreatedCustomerId: number;
  cachedCustomerSearch: { [id: number]: CustomerSearchResponse };
}

@State<CustomerStateModel>({
  name: 'customers',
  defaults: {
    customers: {},
    lastCreatedCustomerId: undefined,
    cachedCustomerSearch: {},
  },
})
@Injectable()
export class CustomerState {
  constructor(private store: Store, private customerService: CustomerService) {}

  @Action(actions.SearchUser)
  searchUser(ctx: StateContext<CustomerStateModel>, { payload }: actions.SearchUser): Observable<User[]> {
    return this.customerService.searchCustomer(payload).pipe(
      map((response: CustomerSearchResponse) => response.customers),
      tap((_customers: User[]) => {
        const state = ctx.getState();
        let customers = {};
        let customerIds: number[] = [];
        _customers.forEach((t) => {
          if (state.customers[t.id] && customers[t.id]) {
            return;
          }
          customerIds = append([t.id])(customerIds);
          customers = { ...customers, [t.id]: t };
        });
        ctx.patchState({ customers });
        this.syncApiState(customers, state.lastCreatedCustomerId, state.cachedCustomerSearch);
        this.store.dispatch(new AddCustomerIds(customerIds));
      })
    );
  }

  @Action(actions.AddCustomers)
  addCustomers(ctx: StateContext<CustomerStateModel>, { payload }: actions.AddCustomers) {
    const state = ctx.getState();
    if (!state) {
      return;
    }
    const currentCustomers = state.customers;
    const newCustomers = payload.filter((t) => t !== undefined);
    const currentCustomerIds = this.store.selectSnapshot(ProcessSelectors.getCustomerIds);
    const customerIds = [];
    let customers = currentCustomers;
    if (!!currentCustomerIds) {
      currentCustomerIds.forEach((t) => customerIds.push(t));
    }
    newCustomers.forEach((customer: User) => {
      if (currentCustomers[customer.id] === undefined) {
        customers = { ...customers, [customer.id]: customer };
      }
      if (customerIds.find((t) => t === customer.id) === undefined) {
        customerIds.push(customer.id);
      }
    });
    ctx.patchState({ customers });
    this.syncApiState(customers, state.lastCreatedCustomerId, state.cachedCustomerSearch);
    this.store.dispatch(new AddCustomerIds(customerIds));
  }

  @Action(actions.SetActiveUser)
  setActiveUser(ctx: StateContext<CustomerStateModel>, { payload, cartHasItemForDownload, cartEntriesIds }: actions.SetActiveUser) {
    const state = ctx.getState();
    if (!state) {
      return;
    }
    return this.customerService.getCustomerById(payload.id).pipe(
      tap((_customer: User) => {
        if (_customer) {
          const customers = { ...state.customers, [_customer.id]: _customer };
          ctx.patchState({ customers });
          this.store.dispatch(new SetActiveCustomer(_customer.id));
          this.store.dispatch(new UpdateProcessName(_customer.last_name));
          this.syncApiState(customers, state.lastCreatedCustomerId, state.cachedCustomerSearch);
        }
      })
    );
  }

  @Action(actions.AddUser)
  addUser(ctx: StateContext<CustomerStateModel>, { payload, setActiveUser }: actions.AddUser) {
    const state = ctx.getState();
    let customerApi$: Observable<User>;
    let isOnline = false;
    if (payload) {
      payload.features.forEach((feature) => {
        if (feature.key === 'onlineshop' || feature.key === 'guestaccount') {
          isOnline = true;
        }
      });
    }
    if (isOnline) {
      customerApi$ = this.customerService.createCustomerOnline(payload);
    } else {
      customerApi$ = this.customerService.createCustomer(payload);
    }
    return customerApi$.pipe(
      tap((addedCustomer: User) => {
        this.store.dispatch(
          new SetOnlineCustomerCreationStatus({
            error: false,
            invalidProperties: null,
          })
        );
        if (state.customers[addedCustomer.id] === undefined) {
          const customers = {
            ...state.customers,
            [addedCustomer.id]: addedCustomer,
          };
          ctx.patchState({
            customers,
            lastCreatedCustomerId: addedCustomer.id,
          });
        }

        if (setActiveUser) {
          this.store.dispatch(new SetActiveCustomer(addedCustomer.id));
          this.store.dispatch(new UpdateProcessName(addedCustomer.last_name));
        }

        this.store.dispatch(new SetDetailsCustomer(addedCustomer.id));

        this.store.dispatch(
          new AddBreadcrumb(
            {
              name: 'Kundendetails',
              path: '/customer/edit/' + addedCustomer.id,
            },
            'customer'
          )
        );
      }),
      catchError((error) => {
        this.store.dispatch(
          new SetOnlineCustomerCreationStatus({
            error: true,
            invalidProperties: error && error.error ? error.error.invalidProperties : undefined,
          })
        );
        console.error(error);
        return of(error);
      })
    );
  }

  @Action(actions.SetUserDetails)
  editUser(ctx: StateContext<CustomerStateModel>, { payload, addBreadcrumb }: actions.SetUserDetails) {
    const state = ctx.getState();
    const id = +payload.id;
    if (!state) {
      return;
    }

    if (payload.features && payload.features.findIndex((v) => v.key === 'onlineshop') >= 0) {
      const customers = {
        ...state.customers,
        [id]: { ...payload, id: id, error: null },
      };
      ctx.patchState({ customers });
      this.syncApiState(customers, state.lastCreatedCustomerId, state.cachedCustomerSearch);
      return;
    }

    return this.customerService.updateCustomer(payload).pipe(
      catchError((error) => {
        console.error(error);
        const customers = {
          ...state.customers,
          [id]: { ...state.customers[id], id: id, error: error },
        };
        ctx.patchState({ customers });
        return of(undefined);
      }),
      tap((addedCustomer) => {
        if (!addedCustomer) {
          return;
        }
        const customers = {
          ...state.customers,
          [id]: { ...payload, id: id, error: null },
        };
        ctx.patchState({ customers });
        if (addBreadcrumb) {
          this.store.dispatch(
            new AddBreadcrumb(
              {
                name: 'Kundendetails',
                path: '/customer/edit/' + payload.id,
              },
              'customer'
            )
          );
        }
        this.syncApiState(customers, state.lastCreatedCustomerId, state.cachedCustomerSearch);
      })
    );
  }

  @Action(actions.SetUserDetailsById)
  getUserDetails(ctx: StateContext<CustomerStateModel>, { payload, addBreadcrumb }: actions.SetUserDetailsById) {
    const state = ctx.getState();
    if (!state) {
      return;
    }
    return this.customerService.getCustomerById(payload).pipe(
      tap((user: User) => {
        if (user) {
          ctx.setState(this.updateCustomer({ ...user, id: +payload }, state.lastCreatedCustomerId));
        }
      })
    );
  }

  updateCustomer<T extends User>(customer: T, lastCreatedCustomerId: number) {
    return patch({
      customers: patch({ [customer.id]: customer }),
      lastCreatedCustomerId: lastCreatedCustomerId,
    });
  }

  @Action(actions.DeleteCustomerForProcess)
  deleteCustomerForProcess(ctx: StateContext<CustomerStateModel>, { payload }: actions.DeleteCustomerForProcess) {
    const state = ctx.getState();
    if (!state) {
      return;
    }
    const customers = state.customers;
    delete customers[payload];
    ctx.patchState({ customers });
    this.store.dispatch(new RemoveCustomerId(payload));
    this.syncApiState(customers, state.lastCreatedCustomerId, state.cachedCustomerSearch);

    return this.customerService.deleteCustomer(payload);
  }

  @Action(actions.SetDefaultShippingAddress)
  setDefaultShippingAddress(ctx: StateContext<CustomerStateModel>, { customerId, shippingAddress }: actions.SetDefaultShippingAddress) {
    const state = ctx.getState();
    if (!state) {
      return;
    }
    const currentCustomers = state.customers;
    const shipping$ = this.customerService.setShippingAddressAsDefault(shippingAddress, customerId);
    if (!shipping$) {
      return;
    }
    return shipping$.pipe(
      tap((response) => {
        const customer = currentCustomers[customerId];
        const updatedCustomer = {
          ...customer,
          delivery_addres: customer.poossible_delivery_addresses.find((t) => t.id === shippingAddress.id),
        };
        const customers = {
          ...currentCustomers,
          [updatedCustomer.id]: updatedCustomer,
        };
        ctx.patchState({ customers });
        this.syncApiState(customers, state.lastCreatedCustomerId, state.cachedCustomerSearch);
      })
    );
  }

  @Action(actions.SetDefaultInvoiceAddress)
  setDefaultInvoiceAddress(ctx: StateContext<CustomerStateModel>, { customerId, invoiceAddress }: actions.SetDefaultInvoiceAddress) {
    const state = ctx.getState();
    if (!state) {
      return;
    }
    const currentCustomers = state.customers;
    const invoice$ = this.customerService.setInvoiceAddressAsDefault(invoiceAddress, customerId);
    if (!invoice$) {
      return;
    }
    return invoice$.pipe(
      tap((response) => {
        const customer = currentCustomers[customerId];
        const updatedCustomer = {
          ...customer,
          invoice_address: customer.poossible_invoice_addresses.find((t) => t.id === invoiceAddress.id),
        };
        const customers = {
          ...currentCustomers,
          [updatedCustomer.id]: updatedCustomer,
        };
        ctx.patchState({ customers });
        this.syncApiState(customers, state.lastCreatedCustomerId, state.cachedCustomerSearch);
      })
    );
  }

  @Action(actions.AddNewShippingAddress)
  addNewShippingAddress(ctx: StateContext<CustomerStateModel>, { customerId, shippingAddress }: actions.AddNewShippingAddress) {
    const state = ctx.getState();
    if (!state) {
      return;
    }
    const currentCustomers = state.customers;
    const address = { ...shippingAddress, synced: true };
    return this.customerService.addShippingAddress(customerId, address).pipe(
      tap((response) => {
        const isUpdate = !isNullOrUndefined(address.id);
        const customer = currentCustomers[customerId];
        let updatedCustomer: User;
        if (isUpdate === true) {
          updatedCustomer = <User>{
            ...customer,
            delivery_addres: address,
            poossible_delivery_addresses: [...(customer.poossible_delivery_addresses.filter((t) => t.id !== address.id) || []), address],
            poossible_invoice_addresses: [...(customer.poossible_invoice_addresses.filter((t) => t.id !== address.id) || []), address],
          };
        } else {
          updatedCustomer = <User>{
            ...customer,
            delivery_addres: address,
            poossible_delivery_addresses: [...(customer.poossible_delivery_addresses || []), address],
            poossible_invoice_addresses: [...(customer.poossible_invoice_addresses || []), address],
          };
        }
        this.store.dispatch(new actions.SetUserDetailsById(customerId));
        const customers = {
          ...currentCustomers,
          [updatedCustomer.id]: updatedCustomer,
        };
        ctx.patchState({ customers });
        this.syncApiState(customers, state.lastCreatedCustomerId, state.cachedCustomerSearch);
      })
    );
  }

  @Action(actions.AddNewInvoiceAddress)
  addNewInvoiceAddress(ctx: StateContext<CustomerStateModel>, { customerId, invoiceAddress }: actions.AddNewInvoiceAddress) {
    const state = ctx.getState();
    if (!state) {
      return;
    }
    const currentCustomers = state.customers;
    const address = { ...invoiceAddress, synced: true };
    return this.customerService.addInvoiceAddress(customerId, address).pipe(
      tap((response) => {
        const isUpdate = !isNullOrUndefined(address.id);
        const customer = currentCustomers[customerId];
        let updatedCustomer: User;
        if (isUpdate === true) {
          updatedCustomer = <User>{
            ...customer,
            invoice_address: address,
            poossible_delivery_addresses: [
              ...(customer.poossible_delivery_addresses.filter((t) => t && t.id !== address.id) || []),
              address,
            ],
            poossible_invoice_addresses: [...(customer.poossible_invoice_addresses.filter((t) => t && t.id !== address.id) || []), address],
          };
        } else {
          updatedCustomer = <User>{
            ...customer,
            invoice_address: address,
            poossible_delivery_addresses: [...(customer.poossible_delivery_addresses || []), address],
            poossible_invoice_addresses: [...(customer.poossible_invoice_addresses || []), address],
          };
        }
        this.store.dispatch(new actions.SetUserDetailsById(customerId));
        const customers = {
          ...currentCustomers,
          [updatedCustomer.id]: updatedCustomer,
        };
        ctx.patchState({ customers });
        this.syncApiState(customers, state.lastCreatedCustomerId, state.cachedCustomerSearch);
      })
    );
  }

  @Action(actions.SetLastCreatedCustomerId)
  setLastCreatedCustomerId(ctx: StateContext<CustomerStateModel>, { customerId }: actions.SetLastCreatedCustomerId) {
    const state = ctx.getState();
    if (!state) {
      return;
    }
    ctx.patchState({ lastCreatedCustomerId: customerId });
    this.syncApiState(state.customers, customerId, state.cachedCustomerSearch);
  }

  @Action(actions.SetCachedCustomers)
  setCachedCustomers(ctx: StateContext<CustomerStateModel>, { cachedCustomerSearch }: actions.SetCachedCustomers) {
    const state = ctx.getState();
    const currentProcessId = this.store.selectSnapshot(AppState.getCurrentProcessId);

    if (currentProcessId) {
      const cachedCustomers = { ...state.cachedCustomerSearch };
      cachedCustomers[currentProcessId] = cachedCustomerSearch;

      ctx.patchState({ cachedCustomerSearch: cachedCustomers });
      this.syncApiState(state.customers, state.lastCreatedCustomerId, cachedCustomers);
    }
  }

  @Action(actions.ClearCachedCustomers)
  clearCachedCustomers(ctx: StateContext<CustomerStateModel>) {
    const state = ctx.getState();
    const currentProcessId = this.store.selectSnapshot(AppState.getCurrentProcessId);

    if (currentProcessId) {
      const cachedCustomers = { ...state.cachedCustomerSearch };
      cachedCustomers[currentProcessId] = undefined;

      ctx.patchState({ cachedCustomerSearch: cachedCustomers });
      this.syncApiState(state.customers, state.lastCreatedCustomerId, cachedCustomers);
    }
  }

  @Action(actions.ClearLastCustomerCreatedId)
  clearLastCustomerCreatedId(ctx: StateContext<CustomerStateModel>) {
    const state = ctx.getState();

    ctx.patchState({ lastCreatedCustomerId: undefined });
    this.syncApiState(state.customers, undefined, state.cachedCustomerSearch);
  }

  @Action(actions.ReloadCustomersData)
  reloadProcesses(
    ctx: StateContext<CustomerStateModel>,
    { customers, lastCreatedCustomerId, cachedCustomerSearch }: actions.ReloadCustomersData
  ) {
    ctx.patchState({ customers, lastCreatedCustomerId, cachedCustomerSearch });
  }

  private syncApiState(
    customers: { [id: number]: User },
    lastCreatedCustomerId: number,
    cachedCustomerSearch: { [id: number]: CustomerSearchResponse }
  ) {
    const userSyncData: UserStateSyncData = {
      customers: customers,
      lastCreatedCustomerId: lastCreatedCustomerId,
      cachedCustomerSearch: cachedCustomerSearch,
    };
    const userData = JSON.stringify(userSyncData);
    this.store.dispatch(new AppUserDataSync(userData));
  }
}
