Source: app/actions/resources/weighInActions.js

import moment from 'moment';
import isUndefined from 'lodash/isUndefined';
import Configuration from 'common/src/app/config/Configuration';
import createAction from 'redux-actions/lib/createAction';
import debugLib from 'debug';
import formValueSelector from 'redux-form/lib/formValueSelector';
import { push as historyPush } from 'react-router-redux';
import WeighInFields from '../../data/enum/FieldNames/WeighInFields';
import Pages from '../../data/enum/Pages';
import FormNames from '../../data/enum/FormNames';
import SkippingReason from '../../data/enum/SkippingReason';
import { createPath } from '../../util/routeUtils';
import { getGoals } from './goalActions';
import { getAwards } from './awardActions';
import { getProfile } from './profileActions';
import { commonTrackEvent } from '../trackingActions';
import { Category, DataLayerKeys } from '../../data/enum/Tracking';
import { WEIGH_IN_HISTORY } from '../../data/collectionPaginationViews';
import { getValue } from '../../util/injector';
import { setEntity, removeEntities } from '../entities/entityActions';
import { WEIGH_IN, USER_WEIGH_IN_HISTORY, PROFILE } from '../../data/entityTypes';
import {
  GATEWAY_COMMUNITY_AUTH,
  GATEWAY_COMMUNITY_V2_AUTH,
  GATEWAY_COMMUNITY_V3_AUTH,
  GATEWAY_CONTENT_AUTH,
} from '../../data/Injectables';
import { apiGet, apiPost, apiPatch } from './apiActions/apiRequest';
import { userIdSelector } from '../../selectors/userAccountSelectors';
import { weighInToAddSelector } from '../../selectors/userProfileSelectors';
import apiGetCollection from './apiActions/apiGetCollection';
import { userWeighInsCollectionId, userWeighInHistoryCollectionId } from '../../data/collectionIds';
import { QUERY_PARAM_USER_ID_HINT } from '../authenticationActions';
import authenticate from '../../util/auth/authenticate';

export const VALIDATE_WEIGHT = 'weighInActions/VALIDATE_WEIGHT';
const validateWeightAction = createAction(VALIDATE_WEIGHT);
export const GET_WEIGH_IN_WEEK_NUMBER = 'weighInActions/GET_WEIGH_IN_WEEK_NUMBER';
export const GET_WEIGHT_AWARDS = 'weighInActions/GET_WEIGHT_AWARDS';
export const CHANGE_MOOD = 'weighInActions/CHANGE_MOOD';
export const GET_WEIGH_IN_RESPONSE = 'weighInActions/GET_WEIGH_IN_RESPONSE';
export const GET_PERSONALIZED_SUPPORT_STATEMENTS_RESPONSE =
  'weighInActions/GET_PERSONALIZED_SUPPORT_STATEMENTS_RESPONSE';
export const GET_CURRENT_WEIGH_IN = 'weighInActions/GET_CURRENT_WEIGH_IN';
export const GET_USER_WEIGH_INS = 'weighInActions/GET_USER_WEIGH_INS';
export const GET_USER_WEIGH_IN_HISTORY = 'weighInActions/GET_USER_WEIGH_IN_HISTORY';
export const GET_USER_WEIGH_IN_BY_ID = 'weighInActions/GET_USER_WEIGH_IN_BY_ID';
export const SET_LIVE_EVENT_REDIRECT = 'weighInActions/SET_LIVE_EVENT_REDIRECT';
export const SET_WEIGH_IN_TO_ADD = 'weighInActions/SET_WEIGH_IN_TO_ADD';
export const changeMoodAction = createAction(CHANGE_MOOD);
const getWeightAwardsAction = createAction(GET_WEIGHT_AWARDS);
const getCurrentWeighInAction = createAction(GET_CURRENT_WEIGH_IN);
const getWeighInResponseAction = createAction(GET_WEIGH_IN_RESPONSE);
const getPersonalizedSupportStatementsResponseAction = createAction(
  GET_PERSONALIZED_SUPPORT_STATEMENTS_RESPONSE,
);
export const setLiveEventRedirectAction = createAction(SET_LIVE_EVENT_REDIRECT);
export const setWeighInToAddAction = createAction(SET_WEIGH_IN_TO_ADD);

// Get the list of weigh in awards
export const getWeightAwards = () => (dispatch, getState) => {
  const state = getState();
  const memberId = state.authentication.userInfo.sub;
  const currentWeight = state.weighIn?.savedWeighIn.weight;
  const weightUnit = state.profile.account.weightUnit;

  return getValue(GATEWAY_COMMUNITY_AUTH)
    .get(
      `/profiles/${memberId}/awards`,
      {
        ifWeigh: currentWeight,
        weightUnit,
      },
      { getState },
    )
    .then(result => dispatch(getWeightAwardsAction(result)));
};

// Get the current weigh in week number
export const getWeighInWeekNumber = () =>
  apiGet(GET_WEIGH_IN_WEEK_NUMBER, GATEWAY_COMMUNITY_AUTH, '/profiles/me/weigh-in-week');

// Validate the weight the member has entered
export const validateWeight = weight => (dispatch, getState) => {
  const data = {
    weight,
    weighingDateUTC: getState().weighIn.weighInToAdd?.startOfUserWeekUtc,
  };

  // Remove the weighingDateUTC key if its value is null
  if (data.weighingDateUTC === null) {
    delete data.weighingDateUTC;
  }

  return dispatch(
    apiGet(VALIDATE_WEIGHT, GATEWAY_COMMUNITY_AUTH, '/profiles/me/weigh-ins/weight', data),
  ).catch(result => dispatch(validateWeightAction(result)));
};

// Get weigh in response for the bucket
export const getWeighInResponse = () => (dispatch, getState) => {
  const state = getState();
  const savedWeighIn = state.weighIn?.savedWeighIn;
  const personalFeedback = state.weighIn?.personalFeedback;

  if (savedWeighIn && !personalFeedback) {
    // Get the weigh in bucket
    const bucket = state.weighIn?.savedWeighIn.bucket;
    return getValue(GATEWAY_CONTENT_AUTH)
      .get(`/weigh-in-responses/${bucket}`, null, { getState })
      .then(result => dispatch(getWeighInResponseAction(result)));
  }

  return null;
};

// Get personalized support statement for the virtual consultant flow
export const getPersonalizedSupportStatementsResponse = () => (dispatch, getState) => {
  const state = getState();
  // Get the weigh in bucket
  const bucket = state.weighIn?.savedWeighIn.bucket;
  return getValue(GATEWAY_CONTENT_AUTH)
    .get(`/personalized-support-responses/${bucket}`, null, { getState })
    .then(result => dispatch(getPersonalizedSupportStatementsResponseAction(result)));
};

/**
 * Gets a the user's ongoing weigh-in from this week
 *
 * @returns 404 when no weigh-in has been found
 * @returns 200 when weigh-in has been found
 *
 * @description
 * weight: 234, // If weight is present, it means first step is done
 * emotion: 1, // If emotion and bucket are present, it means mood step is done
 * bucket: 123,
 * commitment: 2, // If commitment is not null, it means commitment step is done
 * isFinished: false, // If isFinished is true, it means the weigh-in is completed and has been
 * shared
 */
export const getCurrentWeighIn = () => async (dispatch, getState) => {
  await authenticate();
  const state = getState();
  const savedWeighIn = state.weighIn?.savedWeighIn;
  if (!savedWeighIn) {
    const profileId = userIdSelector(state);
    return dispatch(
      apiGet(
        GET_CURRENT_WEIGH_IN,
        GATEWAY_COMMUNITY_V2_AUTH,
        `/profiles/${profileId}/weigh-ins/current`,
      ),
    )
      .then(result => {
        dispatch(getCurrentWeighInAction(result));
      })
      .catch(e => {
        // This is not a faulty error. This message occurs only when a user don't have any weigh-in
        const debug = debugLib('WeighInActions:getCurrentWeighIn');
        debug(e);
      });
  }

  return null;
};

// Get user weigh-in collection

export const getUserWeighIns = (limit = 12) => async (dispatch, getState) => {
  await authenticate();
  const userId = userIdSelector(getState());

  return dispatch(
    apiGetCollection(
      GET_USER_WEIGH_INS,
      GATEWAY_COMMUNITY_AUTH,
      `/profiles/${userId}/weigh-ins`,
      userWeighInsCollectionId({ userId }),
      {
        limit,
      },
      {
        entityType: WEIGH_IN,
        caching: false,
      },
    ),
  );
};

/**
 * Get the users entire weigh-in history, this is designed to be temporary until we
 * can provide weight loss calculations on the back-end (MEMEX-36). Until then we need to
 * fetch the users entire history otherwise there are circumstances where weight-loss messaging
 * will be incorrect.
 */
export const getUserFullWeighInHistory = () => async dispatch => {
  const startDate = moment(Configuration.earliestJourneyStart).format();

  const endDate = moment()
    .endOf('month')
    .format();

  return dispatch(getUserWeighInHistory(startDate, endDate));
};

// Get full weigh in history passing in a start and end date
export const getUserWeighInHistory = (startDate, endDate) => async (dispatch, getState) => {
  await authenticate();
  const userId = userIdSelector(getState());
  return dispatch(
    apiGetCollection(
      GET_USER_WEIGH_IN_HISTORY,
      GATEWAY_COMMUNITY_AUTH,
      `/profiles/${userId}/historic-weigh-ins`,
      userWeighInHistoryCollectionId({ userId }),
      {},
      {
        updatePaginationView: {
          target: WEIGH_IN_HISTORY,
          extend: true,
        },
        entityType: USER_WEIGH_IN_HISTORY,
        requestData: {
          startDate,
          endDate,
        },
      },
    ),
  );
};

const getUserWeighInByIdAction = createAction(GET_USER_WEIGH_IN_BY_ID);
export const getUserWeighInById = id => (dispatch, getState) =>
  getValue(GATEWAY_COMMUNITY_AUTH)
    .get(`/profiles/weigh-ins/${id}`, null, { getState })
    .then(result => dispatch(getUserWeighInByIdAction(result)));

// Save the members weight
export const POST_SAVE_WEIGHT = 'weighInActions/POST_SAVE_WEIGHT';

export const saveWeight = (isValidating = false, groupAwards) => (dispatch, getState) => {
  const state = getState();
  const userId = userIdSelector(state);
  const weighInWeek = state?.weighIn?.weekNumber;

  // Form values
  const currentWeightFormSelector = formValueSelector(FormNames.WI_ENTER_WEIGHT);
  const weight = currentWeightFormSelector(state, WeighInFields.CURRENT_WEIGHT);

  // set award defaults
  let slimmerOfTheWeek = false;
  let slimmerOfTheMonth = false;
  let slimmerOfLastMonth = false;

  if (!isUndefined(groupAwards[WeighInFields.SLIMMER_OF_THE_WEEK]))
    slimmerOfTheWeek = groupAwards[WeighInFields.SLIMMER_OF_THE_WEEK];

  if (!isUndefined(groupAwards[WeighInFields.SLIMMER_OF_THE_MONTH]))
    slimmerOfTheMonth = groupAwards[WeighInFields.SLIMMER_OF_THE_MONTH];

  if (!isUndefined(groupAwards[WeighInFields.SLIMMER_OF_LAST_MONTH]))
    slimmerOfLastMonth = groupAwards[WeighInFields.SLIMMER_OF_LAST_MONTH];

  const alteredMonth = slimmerOfTheMonthFlag(slimmerOfTheMonth, slimmerOfLastMonth);

  const grantedAwards = {
    slimmerOfTheWeek,
    slimmerOfTheMonth: alteredMonth,
  };

  return dispatch(
    apiPost(POST_SAVE_WEIGHT, GATEWAY_COMMUNITY_V3_AUTH, `/profiles/${userId}/weigh-ins/weight`, {
      weight,
      generateWarning: isValidating,
      ...grantedAwards,
    }),
  )
    .then(({ data: weighIn }) => {
      // delete the placeholder weighIn within the history table
      dispatch(removeEntities([weighInWeek.toString()], USER_WEIGH_IN_HISTORY));
      dispatch(setEntity(WEIGH_IN, weighIn.id, weighIn, true));
    })
    .then(() => {
      if (!isValidating) {
        dispatch(getProfile(true));
        dispatch(getUserWeighIns());
        dispatch(getAwards(true, 100));
      }
    });
};

export const slimmerOfTheMonthFlag = (thisMonth, lastMonth) => {
  let alteredMonth = 0; // not slimmer of the month nor for last month
  if (thisMonth && lastMonth) {
    alteredMonth = 3; // slimmer of the month for this month and previous month
  } else if (!thisMonth && !lastMonth) {
    alteredMonth = 0; // NOT slimmer of the month for this month or last
  } else if (thisMonth) alteredMonth = 1;
  else if (lastMonth) alteredMonth = 2; // slimmer of previous month

  return alteredMonth;
};

export const POST_UPDATE_WEIGHT = 'weighInActions/POST_UPDATE_WEIGHT';
export const updateWeight = (isValidating = false) => (dispatch, getState) => {
  const state = getState();
  const userId = userIdSelector(state);
  const currentWeightFormSelector = formValueSelector(FormNames.EWI_EDIT_WEIGHT);
  const weight = currentWeightFormSelector(state, WeighInFields.CURRENT_WEIGHT);
  const weighInId = state.weighIn?.weighInById.id;

  const week = currentWeightFormSelector(state, WeighInFields.SLIMMER_OF_THE_WEEK);
  const slimmerOfTheMonth = currentWeightFormSelector(state, WeighInFields.SLIMMER_OF_THE_MONTH);
  const slimmerOfLastMonth = currentWeightFormSelector(state, WeighInFields.SLIMMER_OF_LAST_MONTH);
  const alteredMonth = slimmerOfTheMonthFlag(slimmerOfTheMonth, slimmerOfLastMonth);

  return dispatch(
    apiPatch(
      POST_UPDATE_WEIGHT,
      GATEWAY_COMMUNITY_AUTH,
      `/profiles/${userId}/weigh-ins/${weighInId}`,
      {
        weight,
        slimmerOfTheWeek: week,
        slimmerOfTheMonth: alteredMonth,
        generateWarning: isValidating,
      },
    ),
  )
    .then(() => {
      if (!isValidating) {
        dispatch(getProfile(true));
        dispatch(getUserWeighIns());
        dispatch(getUserFullWeighInHistory());
        dispatch(getAwards(true, 100));
      }
    })
    .catch(result => dispatch(validateWeightAction(result)));
};

export const POST_ADD_WEIGHT = 'weighInActions/POST_ADD_WEIGHT';

export const addMissingWeight = () => (dispatch, getState) => {
  const state = getState();
  const userId = userIdSelector(state);
  const weighInToAdd = weighInToAddSelector(state);

  // Form values
  const currentWeightFormSelector = formValueSelector(FormNames.AWI_ADD_WEIGHT);
  const inputWeight = currentWeightFormSelector(state, WeighInFields.CURRENT_WEIGHT);
  const weighInMoment = state.weighIn?.weighInToAdd?.startOfUserWeekUtc;

  const week = currentWeightFormSelector(state, WeighInFields.SLIMMER_OF_THE_WEEK);
  const slimmerOfTheMonth = currentWeightFormSelector(state, WeighInFields.SLIMMER_OF_THE_MONTH);
  const slimmerOfLastMonth = currentWeightFormSelector(state, WeighInFields.SLIMMER_OF_LAST_MONTH);
  const alteredMonth = slimmerOfTheMonthFlag(slimmerOfTheMonth, slimmerOfLastMonth);

  return dispatch(
    apiPost(POST_ADD_WEIGHT, GATEWAY_COMMUNITY_V3_AUTH, `/profiles/${userId}/weigh-ins/weight`, {
      weighingDateUTC: weighInMoment,
      weight: inputWeight,
      slimmerOfTheWeek: week,
      slimmerOfTheMonth: alteredMonth,
    }),
  )
    .then(() => {
      // Remove placeholder entity so we don't get duplicate weigh-ins
      dispatch(removeEntities([weighInToAdd.id], USER_WEIGH_IN_HISTORY));
    })
    .then(() => {
      dispatch(getProfile(true));
      dispatch(getUserWeighIns());
      dispatch(getUserFullWeighInHistory());
    });
};

// Save the members mood
export const POST_SAVE_MOOD = 'weighInActions/POST_SAVE_MOOD';

export const saveMood = emotion => (dispatch, getState) => {
  const state = getState();
  const weighInId = state.weighIn?.savedWeighIn?.id;

  if (weighInId && typeof emotion !== 'undefined') {
    return dispatch(
      apiPost(POST_SAVE_MOOD, GATEWAY_COMMUNITY_V2_AUTH, `/profiles/weigh-ins/${weighInId}/mood`, {
        emotion,
      }),
    ).then(() =>
      dispatch(
        setEntity(
          WEIGH_IN,
          weighInId,
          {
            emotion,
          },
          true,
        ),
      ),
    );
  }
  return null;
};

// Save the members goals
export const POST_SAVE_GOALS = 'weighInActions/POST_SAVE_GOALS';

export const saveGoals = goals => (dispatch, getState) => {
  const state = getState();

  const weighInId = state.weighIn?.savedWeighIn?.id;

  return dispatch(
    apiPost(POST_SAVE_GOALS, GATEWAY_COMMUNITY_AUTH, `/profiles/weigh-ins/${weighInId}/goals`, {
      goals,
    }),
  )
    .then(() =>
      dispatch(
        setEntity(
          WEIGH_IN,
          weighInId,
          {
            goals,
          },
          true,
        ),
      ),
    )
    .then(() => dispatch(getGoals(true)));
};

// Save the members commitment
export const POST_SAVE_COMMITMENT = 'weighInActions/POST_SAVE_COMMITMENT';

export const saveCommitmentAction = createAction(POST_SAVE_COMMITMENT);

export const saveCommitment = commitment => (dispatch, getState) => {
  const isWarning = getState().weighIn?.hasBmiError?.parsed?.error.fields?.[0].exception?.data
    ?.isWarning;

  const state = getState();
  const weighInId = state.weighIn?.savedWeighIn?.id;
  return dispatch(
    apiPost(
      POST_SAVE_COMMITMENT,
      GATEWAY_COMMUNITY_AUTH,
      `/profiles/weigh-ins/${weighInId}/commitment`,
      {
        weight: commitment,
        acceptWarning: !!isWarning,
      },
    ),
  ).then(() =>
    Promise.all([
      dispatch(setEntity(WEIGH_IN, weighInId, { commitment }, true)),
      dispatch(saveCommitmentAction(commitment)),
    ]),
  );
};

export const POST_ADD_COMMITMENT = 'weighInActions/POST_ADD_COMMITMENT';

export const addCommitment = weight => (dispatch, getState) => {
  const isWarning = getState().weighIn?.hasBmiError?.parsed?.error.fields?.[0].exception?.data
    ?.isWarning;

  return dispatch(
    apiPost(POST_ADD_COMMITMENT, GATEWAY_COMMUNITY_AUTH, '/profiles/me/commitments', {
      weight,
      acceptWarning: !!isWarning,
    }),
  );
};

// Save the members personal note and share
export const POST_SHARE_WEIGH_IN = 'weighInActions/POST_SHARE_WEIGH_IN';

export const shareWeighIn = () => (dispatch, getState) => {
  const state = getState();
  const weighInId = state.weighIn?.savedWeighIn?.id;
  const userId = userIdSelector(state);

  // Form values
  const personalNoteFormSelector = formValueSelector(FormNames.WI_PERSONAL_NOTE);
  const note = personalNoteFormSelector(state, WeighInFields.PERSONAL_NOTE);
  const hideProgress = personalNoteFormSelector(state, WeighInFields.HIDE_MY_PROGRESS);

  return dispatch(
    apiPost(POST_SHARE_WEIGH_IN, GATEWAY_COMMUNITY_AUTH, `/profiles/weigh-ins/${weighInId}/share`, {
      note,
      hideProgress,
    }),
  )
    .then(() =>
      dispatch(
        setEntity(
          WEIGH_IN,
          weighInId,
          {
            note,
            hideProgress,
          },
          true,
        ),
      ),
    )
    .then(() => dispatch(getCurrentWeighIn()))
    .then(() => dispatch(setEntity(PROFILE, userId, { isCurrentWeighInFinished: true }, true)))
    .then(() => {
      const { url, groupId } = state.weighIn?.liveEventRedirect || {};
      if (url && url !== '' && url.length > 0) {
        dispatch(
          commonTrackEvent(null, {
            [DataLayerKeys.CONTENT_GROUP_NAME]: Category.LIVE_EVENT,
            [DataLayerKeys.LIVE_EVENT_GROUP_ID]: groupId,
            [DataLayerKeys.LIVE_EVENT_BUTTON]: 'Live Event: Join Event',
          }),
        );

        window.location.href = url;
      }
    });
};

export const POST_SAVE_SKIPPINGREASON = 'weighInActions/POST_SAVE_SKIPPINGREASON';

export const saveSkippingReason = (reason = null) => (dispatch, getState) => {
  const state = getState();
  const userId = userIdSelector(state);

  let skippingReason;
  if (reason) {
    skippingReason = reason;
  } else {
    // Form values
    const currentWeightFormSelector = formValueSelector(FormNames.WI_SKIP);
    skippingReason = currentWeightFormSelector(state, WeighInFields.SKIPPING_REASON);
  }

  return dispatch(
    apiPost(
      POST_SAVE_SKIPPINGREASON,
      GATEWAY_COMMUNITY_V3_AUTH,
      `/profiles/${userId}/weigh-ins/weight`,
      {
        skippingReason,
      },
    ),
  )
    .then(({ data: weighIn }) => {
      dispatch(setEntity(WEIGH_IN, weighIn.id, weighIn, true));
      // Retrieve the new weigh-in date
    })
    .then(() => dispatch(getProfile(true)));
};

export const SKIP_WEIGH_IN = 'weighInActions/SKIP_WEIGH_IN';

export const skipWeighIn = () => dispatch => {
  // Dispatch only for tracking doesn't make use of a reducer
  dispatch(createAction(SKIP_WEIGH_IN, null, null)({}));

  return dispatch(saveSkippingReason(SkippingReason.NOT_REQUESTED)).then(
    historyPush(Pages.WEIGHIN_SKIPPED),
  );
};

export const setLiveEventRedirectUrl = containerId => (dispatch, getState) => {
  const state = getState();
  const liveEventHost = state.config.environmentConfig.web.live.host;
  const userId = userIdSelector(state);
  const liveEventUrl = `${liveEventHost}${createPath(Pages.LIVE_GROUP, {
    groupId: containerId,
  })}?${QUERY_PARAM_USER_ID_HINT}=${userId}`;

  return dispatch(
    setLiveEventRedirectAction({
      url: liveEventUrl,
      groupId: containerId,
    }),
  );
};

export const CLEAR_WEIGH_IN_BY_ID = 'weighInActions/CLEAR_WEIGH_IN_BY_ID';
export const clearWeighInByIdData = createAction(CLEAR_WEIGH_IN_BY_ID);