Source: app/actions/resources/migrationActions.js

import { push as historyPush } from 'react-router-redux';
import createAction from 'redux-actions/lib/createAction';
import { createPath } from '../../util/routeUtils';
import { apiPost, apiGet, apiPut } from './apiActions/apiRequest';
import {
  GATEWAY_ACCOUNT_MIGRATION,
  GATEWAY_MIGRATION_AUTH,
  AUTHENTICATION_MANAGER,
} from '../../data/Injectables';
import SortType from '../../data/enum/SortType';
import Pages, { PARAM_WAIT_FOR_MIGRATION_CREATION } from '../../data/enum/Pages';
import { pollApi } from '../../util/pollUtils';
import { getValue } from '../../util/injector';
import {
  MigrationState,
  MigrationErrorStateToString,
  MigrationStaticError,
} from '../../data/enum/MigrationState';
import { ResourceType, ResourceTypeString } from '../../data/enum/ResourceType';
import WebHost from '../../data/enum/WebHost';
import RedirectError from '../../util/RedirectError';
import LoginErrorCode from '../../data/enum/LoginErrorCode';
import LegacyDataObjectName from '../../data/enum/LegacyDataObjectName';
import StatusCode from '../../data/enum/StatusCode';
import getValuesFromArray from '../../util/getValuesFromArray';
import { getUrlParameter } from '../../util/urlUtils';
import authenticate from '../../util/auth/authenticate';

export const MIGRATION_SIGN_UP = 'migrationActions/SIGN_UP';
export const signUp = ({ newPassword, legacyPassword }) => async (dispatch, getState) => {
  try {
    const { requireNewPassword, id } = await dispatch(
      apiPost(MIGRATION_SIGN_UP, GATEWAY_ACCOUNT_MIGRATION, '/migration/sign-up', {
        legacyPassword,
        newPassword,
      }),
    );

    if (!requireNewPassword && id) {
      setMigrationUserId(id);
    }

    if (!requireNewPassword) {
      const returnHost = getState().config.environmentConfig.web[WebHost.MIGRATION].host;
      document.location.href = `${returnHost}${Pages.MIGRATION_STATE_CHECK_WAIT_FOR_MIGRATION_CREATION}`;
      return true;
    }

    return dispatch(historyPush(Pages.MIGRATION_UPDATE_PASSWORD));
  } catch (error) {
    const { status, text } = error?.response;
    const { code } = text && JSON.parse(text)?.error;

    if (status === StatusCode.STATUS_401 && code === LoginErrorCode.UNAUTHORIZED) {
      dispatch(setMigrationIsUnauthorized(true));
      return true;
    }

    throw error;
  }
};

export const MIGRATION_SET_SECURITY_ANSWER_STATE =
  'migrationActions/MIGRATION_SET_SECURITY_ANSWER_STATE';
export const setSecurityAnswerState = () => dispatch =>
  dispatch(
    apiPost(
      MIGRATION_SET_SECURITY_ANSWER_STATE,
      GATEWAY_ACCOUNT_MIGRATION,
      '/migration/update-state-to-security-answer-set',
    ),
  );

export const SET_MIGRATION_USER_ID = 'migrationActions/SET_MIGRATION_USER_ID';
export const setMigrationUserId = createAction(SET_MIGRATION_USER_ID, userId => ({ userId }));

export const SET_MIGRATION_IS_UNAUTHORIZED = 'migrationActions/SET_MIGRATION_IS_UNAUTHORIZED';
export const setMigrationIsUnauthorized = createAction(
  SET_MIGRATION_IS_UNAUTHORIZED,
  isUnauthorized => ({ isUnauthorized }),
);

/**
 * Returns the migration id/state and userId for the migrating account
 *
 * @type {string}
 */
export const GET_MIGRATIONS = 'migrationActions/GET_MIGRATIONS';
export const getMigrations = () => (dispatch, getState) => {
  const userId = getState().authentication?.userInfo?.sub;

  return dispatch(
    apiGet(
      GET_MIGRATIONS,
      GATEWAY_MIGRATION_AUTH,
      '/migrations',
      {
        userId,
        limit: 1,
        sort: SortType.NEWEST,
      },
      {
        headers: {
          'Cache-Control': 'no-cache',
          Pragma: 'no-cache',
        },
      },
    ),
  ).catch(error => {
    // Logged in migration user not found, so let's redirect to the not-allowed page
    const { status, text } = error?.response;
    const { code } = text && JSON.parse(text)?.error;

    if (status === StatusCode.STATUS_404 && code === LoginErrorCode.NOT_FOUND) {
      throw new RedirectError(
        `${createPath(Pages.MIGRATION_ERROR, { errorState: MigrationStaticError.NOT_ALLOWED })}`,
      );
    }
    throw error;
  });
};

/**
 * Redirect the user to the specific error content page
 *
 * @returns {Function}
 */
const redirectToMigrationErrorPage = () => (dispatch, getState) => {
  const migrationState = getState().migration?.migrationState;

  if (!Object.keys(MigrationErrorStateToString).includes(migrationState)) return true;

  return dispatch(
    historyPush(createPath(Pages.MIGRATION_ERROR, { errorState: MigrationStaticError.DEFAULT })),
  );
};

/**
 * User accepts to lock his state to load the legacy data
 *
 * @type {string}
 */
export const ACCEPT_LOCK = 'migrationActions/ACCEPT_LOCK';
export const acceptLock = () => async (dispatch, getState) => {
  const state = getState();
  const id = state.migration?.id;

  await dispatch(apiPost(ACCEPT_LOCK, GATEWAY_MIGRATION_AUTH, `/migrations/${id}/accept-lock`));

  // We need to poll the state till it's LOCKING_LEGACY_MEMBER || LOADING_LEGACY_DATA || ErrorsState
  await dispatch(
    pollingCurrentMigrationState([
      MigrationState.LOCKING_LEGACY_MEMBER,
      MigrationState.LOADING_LEGACY_DATA,
      MigrationState.LEGACY_DATA_LOADED,
      MigrationState.LEGACY_MEMBER_UNLOCKED_DUE_TO_FAILURE,
      MigrationState.LOCK_LEGACY_MEMBER_FAILED,
      MigrationState.UNLOCKING_LEGACY_MEMBER_FAILED,
      MigrationState.FAILED,
      MigrationState.ROLLING_BACK,
      MigrationState.IMPORT_FAILING,
    ]),
  );
};

/**
 * Retrieves the summary for the weight/ goals/ awards page
 *
 * @type {string}
 */
export const GET_MIGRATIONS_SUMMARY = 'migrationActions/GET_MIGRATIONS_SUMMARY';
export const getMigrationsSummary = () => (dispatch, getState) => {
  const id = getState().migration?.id;

  return dispatch(
    apiGet(GET_MIGRATIONS_SUMMARY, GATEWAY_MIGRATION_AUTH, `/migrations/${id}/resources/summary`),
  );
};

/**
 * Returns boolean whether the user is a group account or not
 *
 * @type {string}
 */
export const GET_IS_GROUP_MEMBER = 'migrationActions/GET_IS_GROUP_MEMBER';
export const getIsGroupMember = () => async (dispatch, getState) => {
  await authenticate();
  const userId = getState().authentication?.userInfo?.sub;
  return dispatch(
    apiGet(GET_IS_GROUP_MEMBER, GATEWAY_MIGRATION_AUTH, `/users/${userId}/is-group-member`),
  );
};

/**
 * Returns boolean whether the user has a beta account or not
 *
 * @type {string}
 */
export const GET_IS_BETA_MEMBER = 'migrationActions/GET_IS_BETA_MEMBER';
export const getIsBetaMember = () => async (dispatch, getState) => {
  await authenticate();
  const userId = getState().authentication?.userInfo?.sub;
  return dispatch(
    apiGet(GET_IS_BETA_MEMBER, GATEWAY_MIGRATION_AUTH, `/users/${userId}/is-beta-member`),
  );
};

/**
 * Returns legacy data url
 *
 * @type {string}
 */
export const GET_LEGACY_SITE_URL = 'migrationActions/GET_LEGACY_SITE_URL';
export const getLegacySiteUrl = () => async (dispatch, getState) => {
  await authenticate();
  const userId = getState().authentication?.userInfo?.sub;
  return dispatch(
    apiGet(GET_LEGACY_SITE_URL, GATEWAY_MIGRATION_AUTH, `/users/${userId}/legacy-site-url`),
  );
};

/**
 * Returns the data for a specific resource (personalDetails || addresses)
 *
 * @type {string}
 */
export const GET_VERIFIABLE_RESOURCE = 'migrationActions/GET_VERIFIABLE_RESOURCE';
export const getVerifiableResource = type => (dispatch, getState) => {
  const migrationId = getState().migration?.id;

  return dispatch(
    apiGet(GET_VERIFIABLE_RESOURCE, GATEWAY_MIGRATION_AUTH, '/verifiable-resources', {
      migrationId,
      type,
    }),
  );
};

/**
 * Update the data in the redux state for a specific resource
 *
 * @type {string}
 */
export const SET_VERIFIABLE_RESOURCE = 'migrationActions/SET_VERIFIABLE_RESOURCE';
export const setVerifiableResource = createAction(
  SET_VERIFIABLE_RESOURCE,
  (values, updateAtIndex, isArray) => ({
    ...values,
    updateAtIndex,
    isArray,
  }),
);

/**
 * Update the verified redux data into the database
 *
 * @type {string}
 */
export const PUT_VERIFIABLE_RESOURCE = 'migrationActions/PUT_VERIFIABLE_RESOURCE';
export const putVerifiableResource = (type, dataFields) => (dispatch, getState) => {
  const { id: resourceId, data } = getState().migration?.[ResourceTypeString[type]];
  let resources = data;

  if (type === ResourceType.ADDRESSES) {
    resources = [...data];
  } else if (dataFields) {
    // Get only the fields that we want to submit
    resources = getValuesFromArray(dataFields, data);
  }

  return dispatch(
    apiPut(
      PUT_VERIFIABLE_RESOURCE,
      GATEWAY_MIGRATION_AUTH,
      `/verifiable-resources/${resourceId}/verified-data`,
      resources,
    ),
  );
};

export const putVerifiableResources = () => async dispatch => {
  await Promise.all([
    dispatch(putVerifiableResource(ResourceType.WEIGHTS)),
    dispatch(putVerifiableResource(ResourceType.TARGETS)),
    dispatch(putVerifiableResource(ResourceType.HEIGHTS)),
    dispatch(
      putVerifiableResource(ResourceType.PERSONAL_DETAILS, Object.values(LegacyDataObjectName)),
    ),
    dispatch(putVerifiableResource(ResourceType.ADDRESSES)),
  ]);

  return dispatch(historyPush(Pages.MIGRATION_TRANSFER));
};

/**
 * returns the current migration state
 *
 * @type {string}
 */
export const GET_MIGRATIONS_STATE = 'migrationActions/GET_MIGRATIONS_STATE';
export const getMigrationsState = () => (dispatch, getState) => {
  const id = getState().migration?.id;

  return dispatch(
    apiGet(
      GET_MIGRATIONS_STATE,
      GATEWAY_MIGRATION_AUTH,
      `/migrations/${id}/state`,
      {},
      {
        headers: {
          'Cache-Control': 'no-cache',
          Pragma: 'no-cache',
        },
      },
    ),
  );
};

const TIMES_IN_MIN = 60 * 1000 * 10;
const TIMES_INTERVAL_MS = 5000;

/**
 * This method is only used after the signUp call when the requireNewPassword = false
 *
 * location = location object
 *
 * @param arrayValuesToCheck
 * @returns {function(*, *): Promise<any>}
 */
const useForWmfcPollingStatesArray = [
  MigrationState.INITIAL,
  MigrationState.SECURITY_ANSWER_NOT_SET,
  MigrationState.SECURITY_ANSWER_SET,
  MigrationState.LOCKING_LEGACY_MEMBER,
  MigrationState.LOADING_LEGACY_DATA,
  MigrationState.LEGACY_DATA_LOADED,
  MigrationState.IMPORTING,
  MigrationState.COMPLETING,
];

const waitForMigrationCreationPolling = location => dispatch => {
  if (location) {
    const hasParamWfmc = getUrlParameter(location, PARAM_WAIT_FOR_MIGRATION_CREATION) === 'true';
    if (hasParamWfmc) {
      // Infinite polling until the migration user is created with a correct state
      return pollApi(
        () => dispatch(getMigrations()),
        response =>
          // Let's poll the state until it has one of the states in the array
          response?.data?.[0] && useForWmfcPollingStatesArray.includes(response?.data?.[0].state),
        TIMES_INTERVAL_MS,
      );
    }
  }

  return Promise.resolve();
};

/**
 * Poll the current migration state and redirect the user to the correct page according to the state
 *
 * arrayValuesToCheck = optional array that you wanted to check until the polling stops
 * gotoRedirectPage = When you wanted to redirect to a page according to a state
 *
 * @param arrayValuesToCheck
 * @returns {function(*, *): Promise<any>}
 */
export const pollingCurrentMigrationState = (arrayValuesToCheck, location = null) => async (
  dispatch,
  getState,
) => {
  await dispatch(waitForMigrationCreationPolling(location));
  return pollApi(
    () => dispatch(getMigrationsState()),
    response => {
      // This response will determine when the polling needs to stop
      const arrayValues = !arrayValuesToCheck ? Object.values(MigrationState) : arrayValuesToCheck;

      return arrayValues.includes(response?.data);
    },
    TIMES_INTERVAL_MS,
    TIMES_IN_MIN,
  )
    .then(() => {
      const migrationState = getState().migration?.migrationState;

      switch (migrationState) {
        case MigrationState.SECURITY_ANSWER_NOT_SET: {
          const returnHost = getState().config.environmentConfig.web[WebHost.ACCOUNT].host;
          window.location.href = `${returnHost}${Pages.MIGRATION_SET_SECURITY_QUESTION}`;
          return true;
        }

        case MigrationState.SECURITY_ANSWER_SET: {
          return dispatch(historyPush(Pages.MIGRATION_CONFIRM));
        }

        case MigrationState.LOCKING_LEGACY_MEMBER:
        case MigrationState.LOADING_LEGACY_DATA: {
          return dispatch(historyPush(Pages.MIGRATION_LOADING));
        }

        case MigrationState.LEGACY_DATA_LOADED: {
          return dispatch(historyPush(Pages.MIGRATION_DATA_CHECK));
        }

        case MigrationState.IMPORTING:
        case MigrationState.COMPLETING: {
          return dispatch(historyPush(Pages.MIGRATION_TRANSFER));
        }

        case MigrationState.COMPLETED: {
          return dispatch(historyPush(Pages.MIGRATION_COMPLETED));
        }

        case MigrationState.LEGACY_MEMBER_UNLOCKED_DUE_TO_FAILURE:
        case MigrationState.LOCK_LEGACY_MEMBER_FAILED:
        case MigrationState.UNLOCKING_LEGACY_MEMBER_FAILED:
        case MigrationState.FAILED:
        case MigrationState.ROLLING_BACK:
        case MigrationState.IMPORT_FAILING: {
          return dispatch(
            historyPush(
              createPath(Pages.MIGRATION_ERROR, { errorState: MigrationStaticError.DEFAULT }),
            ),
          );
        }

        default: {
          return dispatch(historyPush(Pages.MIGRATION_CONFIRM));
        }
      }
    })
    .catch(() => dispatch(redirectToMigrationErrorPage()));
};

export const refreshLogin = () => () => getValue(AUTHENTICATION_MANAGER).refreshLogin();

export const SET_RETURN_STATE_URL = 'migrationActions/SET_RETURN_STATE_URL';
export const setReturnStateUrl = createAction(SET_RETURN_STATE_URL, returnUrl => ({
  returnUrl,
}));