import { State, Action, StateContext, Selector, Store } from '@ngxs/store';
import {
  AppSetCurrentProcess,
  AppAddProcess,
  AppDeleteProcess,
  AppUserDataSync,
  AppSwitchModule,
  ReloadSavedState,
} from '../actions/app.actions';
import { LoadBranches, LoadUserBranch } from '../actions/branch.actions';
import { BranchSelectors } from '../selectors/branch.selector';
import { isNullOrUndefined } from 'util';
import { RemoveProcessNewState, ReloadProcessData } from '../actions/process.actions';
import { UserStateService } from '../../services/user-state.service';
import { UserStateSyncData } from '../../models/user-state-sync.model';
import { ReloadCustomersData } from '../actions/customer.actions';
import { ReloadCartData } from '../actions/cart.actions';
import { ReloadCartEntryData } from '../actions/cart-entry.actions';
import { ReloadProductsData } from '../actions/product.actions';
import { ReloadBreadcrumbsData } from '../actions/breadcrumb.actions';
import { Router } from '@angular/router';
import { Subject } from 'rxjs';
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
import { ModuleSwitcher } from '../../models/app-switcher.enum';
import { ModuleSwitcherService } from '../../services/module-switcher.service';
import { ReloadFiltersData } from '../actions/filter.actions';
import { ReloadCollectingShelfData } from '../actions/collecting-shelf.action';
import { ReloadGoodsIn } from '../actions/goods-in.actions';
import { ReloadBranchProcess } from '../actions/branch-process.actions';
import { ReloadRemission } from '../actions/remission.actions';
import { ReloadFormState } from '../actions/forms.actions';
import { FILIALE_LANDING_PAGE } from '../../utils/app.constants';
import { Injectable } from '@angular/core';

export const SYNC_DATA_VERSION = 219;

export class AppStateModel {
  currentProcesssId: number;
  processIds: number[];
  activeModule: ModuleSwitcher;
  synced: boolean;
  syncedUserData: string;
}

@State<AppStateModel>({
  name: 'app',
  defaults: {
    currentProcesssId: null,
    processIds: [],
    activeModule: ModuleSwitcher.Customer,
    synced: false,
    syncedUserData: '',
  },
})
@Injectable()
export class AppState {
  userStateService$ = new Subject<string>();

  @Selector()
  static getProcessCount(state: AppStateModel) {
    return state.processIds ? state.processIds.length : 0;
  }

  @Selector()
  static getCurrentProcessId(state: AppStateModel) {
    const activeModule = state.activeModule;
    if (activeModule === ModuleSwitcher.Branch) {
      return -1;
    }
    return state.currentProcesssId;
  }

  @Selector()
  static getProcessIds(state: AppStateModel) {
    return state.processIds;
  }

  @Selector()
  static isSynced(state: AppStateModel) {
    return state.synced;
  }

  @Selector()
  static activeModule(state: AppStateModel) {
    return state.activeModule;
  }

  constructor(
    private store: Store,
    private userStateService: UserStateService,
    private router: Router,
    private moduleSwitcherService: ModuleSwitcherService
  ) {
    this.userStateService$
      .pipe(
        debounceTime(500),
        distinctUntilChanged((prev, next) => prev && next && prev === next),
        switchMap((state) => {
          return this.userStateService.saveUserState(state);
        })
      )
      .subscribe();
  }

  @Action(AppSetCurrentProcess)
  appSetCurrentProcess(ctx: StateContext<AppStateModel>, { payload }: AppSetCurrentProcess) {
    const state = ctx.getState();
    const processExists = state.processIds.findIndex((t) => t === payload) !== -1;
    if (processExists) {
      ctx.patchState({
        currentProcesssId: payload,
      });
      this.store.dispatch(new RemoveProcessNewState());
    }
  }

  @Action(AppAddProcess)
  appAddProcess(ctx: StateContext<AppStateModel>, { payload }: AppAddProcess) {
    const state = ctx.getState();
    const processExists = state.processIds.findIndex((t) => t === payload) !== -1;
    if (!processExists) {
      const processIds = [...state.processIds, payload];
      const currentProcesssId = payload;
      ctx.patchState({ currentProcesssId, processIds });

      const branchesLoaded = this.store.selectSnapshot(BranchSelectors.getBranches);
      if (!branchesLoaded || Object.keys(branchesLoaded).length === 0) {
        this.store.dispatch(new LoadBranches());
      }

      const userBranch = this.store.selectSnapshot(BranchSelectors.getUserBranch);
      if (isNullOrUndefined(userBranch)) {
        this.store.dispatch(new LoadUserBranch());
      }

      this.syncApiState(processIds, state.activeModule);
    }
  }

  @Action(AppDeleteProcess)
  appDeleteProcess(ctx: StateContext<AppStateModel>, { payload }: AppDeleteProcess) {
    const state = ctx.getState();
    const currentIds = state.processIds;
    if (currentIds) {
      const processIds = currentIds.filter((t) => t && t !== payload);
      ctx.patchState({ processIds });
      this.syncApiState(processIds, state.activeModule);
    }
  }

  @Action(AppSwitchModule)
  appSwitchModule(ctx: StateContext<AppStateModel>, { payload }: AppSwitchModule) {
    const state = ctx.getState();
    ctx.patchState({ activeModule: payload });
    this.syncApiState(state.processIds, payload);
  }

  /**
   * Save store data on backend
   */
  @Action(AppUserDataSync)
  appUserDataSynced(ctx: StateContext<AppStateModel>, { data, sync }: AppUserDataSync) {
    const state = ctx.getState();

    let currentUserData: UserStateSyncData = {};
    if (state.syncedUserData) {
      currentUserData = JSON.parse(state.syncedUserData);
    }

    let serverData: UserStateSyncData = {};
    if (data) {
      serverData = JSON.parse(data);
    }

    const syncedData = { ...currentUserData, ...serverData };
    const updatedUserDataString = JSON.stringify(syncedData);

    ctx.patchState({
      synced: true,
      syncedUserData: updatedUserDataString,
    });
    this.userStateService$.next(updatedUserDataString);
  }

  /**
   * Initial store loading from API, triggered once on page load
   */
  @Action(ReloadSavedState)
  reloadSavedState(ctx: StateContext<AppStateModel>, { data, sync }: ReloadSavedState) {
    const state = ctx.getState();

    let currentUserData: UserStateSyncData = {};
    if (state.syncedUserData) {
      currentUserData = JSON.parse(state.syncedUserData);
    }

    let serverData: UserStateSyncData = {};
    if (data) {
      serverData = JSON.parse(data);
    }

    const syncedData = { ...currentUserData, ...serverData };
    const updatedUserDataString = JSON.stringify(syncedData);

    if (!syncedData.version) {
      this.router.navigate(['/dashboard']);
      return;
    }

    if (sync && syncedData.version === SYNC_DATA_VERSION) {
      this.reloadDataFromAPI(syncedData);

      if (syncedData.currentProcesssId || syncedData.activeModule === ModuleSwitcher.Branch) {
        ctx.patchState({
          ...state,
          synced: true,
          syncedUserData: updatedUserDataString,
          currentProcesssId: serverData.currentProcesssId,
          processIds: serverData.processIds,
          activeModule: serverData.activeModule,
        });
      }
    } else {
      ctx.patchState({
        ...state,
        synced: true,
        syncedUserData: updatedUserDataString,
      });
    }

    this.userStateService$.next(updatedUserDataString);
  }

  private reloadDataFromAPI(data: UserStateSyncData) {
    if (!data) {
      this.router.navigate(['/dashboard']);
      return;
    }
    if (data.customers) {
      this.store.dispatch(new ReloadCustomersData(data.customers, data.lastCreatedCustomerId, data.cachedCustomerSearch));
    }

    if (data.products) {
      this.store.dispatch(new ReloadProductsData(data.products, data.cachedProductResults));
    }

    if (data.carts) {
      this.store.dispatch(new ReloadCartData(data.carts));
    }

    if (data.cartEntries) {
      this.store.dispatch(new ReloadCartEntryData(data.cartEntries));
    }

    if (data.goodsIn) {
      this.store.dispatch(new ReloadGoodsIn(data.goodsIn));
    }

    if (data.branchProcess) {
      this.store.dispatch(new ReloadBranchProcess(data.branchProcess));
    }

    if (data.processes) {
      this.store.dispatch(new ReloadProcessData(data.processes, data.recentArticles));
    }

    if (data.formsState) {
      this.store.dispatch(new ReloadFormState(data.formsState));
    }

    if (data.activeModule === ModuleSwitcher.Customer) {
      if (data.processes && data.currentProcesssId && data.processes[data.currentProcesssId]) {
        const currentProcesssId = data.currentProcesssId;
        const currentRoute = data.processes[currentProcesssId].currentRoute;
        if (currentRoute && currentRoute.length > 0) {
          this.routingAvailableAction(data, currentProcesssId, currentRoute, data.activeModule);
        } else {
          this.router.navigate(['/dashboard']);
        }
      } else {
        this.router.navigate(['/dashboard']);
      }
    } else if (data.activeModule === ModuleSwitcher.Branch) {
      if (data.branchProcess) {
        const currentProcesssId = -1;
        const currentRoute = data.branchProcess.currentRoute;
        if (currentRoute && currentRoute.length > 0) {
          this.routingAvailableAction(data, currentProcesssId, currentRoute, data.activeModule);
        } else {
          this.router.navigate([FILIALE_LANDING_PAGE]);
        }
      } else {
        this.router.navigate([FILIALE_LANDING_PAGE]);
      }
    }

    if (data.remission) {
      this.store.dispatch(new ReloadRemission(data.remission));
    }

    if (data.activeModule === ModuleSwitcher.Branch) {
      this.moduleSwitcherService.switch(ModuleSwitcher.Branch, data.branchProcess);
    }

    if (data.processesBreadcrumbs && data.activeCrumbs && (data.currentProcesssId || data.activeModule === ModuleSwitcher.Branch)) {
      this.store.dispatch(new ReloadBreadcrumbsData(data.processesBreadcrumbs, data.activeCrumbs, data.previusMenuPath));
    }

    if (data.filters && data.processesSelectedFilters && data.dropdownFilters) {
      this.store.dispatch(
        new ReloadFiltersData(
          data.filters,
          data.processesSelectedFilters,
          data.negativeProcessesSelectedFilters,
          data.dropdownFilters,
          data.negativeDropdownFilters,
          data.filterType
        )
      );
    }

    this.store.dispatch(
      new ReloadCollectingShelfData(
        data.shelfOrders ? data.shelfOrders : {},
        data.editOrder ? data.editOrder : {},
        data.cachedSearchResults ? data.cachedSearchResults : {},
        data.useCache
      )
    );
  }

  private routingAvailableAction(data: UserStateSyncData, currentProcesssId: number, currentRoute: string, module: ModuleSwitcher) {
    const hasActiveCrumbsAvailableForProcess = data.activeCrumbs && data.activeCrumbs[currentProcesssId];
    const activeCrumbs = data.activeCrumbs[currentProcesssId];
    const hasProcessBreadcrumbsAvailableForProcess = data.processesBreadcrumbs && data.processesBreadcrumbs[activeCrumbs];

    const breadcrumb = hasProcessBreadcrumbsAvailableForProcess
      ? data.processesBreadcrumbs[activeCrumbs].find((t) => t.processId === currentProcesssId)
      : null;

    const breadcrumbPath = breadcrumb && breadcrumb.breadcrumbs ? breadcrumb.breadcrumbs.find((t) => t && t.path === currentRoute) : null;

    if (
      hasActiveCrumbsAvailableForProcess &&
      hasProcessBreadcrumbsAvailableForProcess &&
      breadcrumb &&
      breadcrumb.breadcrumbs &&
      breadcrumbPath
    ) {
      this.router.navigate([breadcrumbPath.path], {
        queryParams: breadcrumbPath.queryParams,
      });
    } else {
      this.navigateToCourrentRoute(
        currentRoute,
        module === ModuleSwitcher.Customer
          ? data.processes[currentProcesssId].currentRouteQueryParams
          : data.branchProcess.currentRouteQueryParams
      );
    }
  }

  private navigateToCourrentRoute(currentRoute: string, queryParams: any) {
    if (queryParams) {
      this.router.navigate([currentRoute], { queryParams: queryParams });
    } else {
      this.router.navigate([currentRoute]);
    }
  }

  private syncApiState(processIds: number[], activeModule: ModuleSwitcher) {
    const userSyncData: UserStateSyncData = {
      version: SYNC_DATA_VERSION,
      processIds: processIds,
      activeModule: activeModule,
    };
    const userData = JSON.stringify(userSyncData);
    this.store.dispatch(new AppUserDataSync(userData));
  }
}
