Source: app/config/routeRequirements.js

/* global WP_DEFINE_IS_NODE */
import moment from 'moment';
import { formValueSelector } from 'redux-form';
import { journeyFields } from 'common/src/app/data/enum/FieldNames/AccountFieldNames';
import AccountState from 'common/src/app/data/enum/AccountState';
import MemberType, { MemberTypeStrings } from 'common/src/app/data/enum/MemberType';
import FormNames from 'common/src/app/data/enum/FormNames';
import { subscriptionTypeSelector } from 'common/src/app/selectors/accountStateSelectors';
import marketConfig from 'common/src/app/config/market/market.configdefinitions';

import { userProfileSelector } from '../selectors/userProfileSelectors';
import Pages from '../data/enum/Pages';
import { hasFlag } from '../util/bitwiseUtils';
import { hasValidSubscriptionSelector } from '../selectors/registrationSelector';
import RouteRequirement from '../util/route-requirements/RouteRequirement';
import authenticate from '../util/auth/authenticate';
import { createPath } from '../util/routeUtils';
import RedirectError from '../util/RedirectError';
import AdyenStatus from '../data/enum/AdyenStatus';
import {
  userAccountSelector,
  userGroupIdsSelector,
  isUserLoggedInSelector,
  memberTypeSelector,
} from '../selectors/userAccountSelectors';
import { formIsSubmitted } from '../util/wizardStepDisable';
import UserRole from '../data/enum/UserRole';
import WebHost from '../data/enum/WebHost';
import { getUserPermissionStoredData } from '../util/userPermissionStateUtil';

/** @module */

/**
 * Requires that the user is logged in __if on the server__. On the client, the check
 * for authentication will happen in ClientAuthenticationManager. This is because
 * the page render can start before authentication happens.
 * @type {RouteRequirement}
 */
export const authenticated = new RouteRequirement(
  'authenticated',
  [],
  ({ getState }) =>
    !WP_DEFINE_IS_NODE ||
    typeof getUserPermissionStoredData({ getState }).accountState === 'number',
  ({}, { redirectToLogin }) => redirectToLogin(),
);

/**
 * Requires that the user has completed migration flow.
 *
 * Note: On the client side, this RouteRequirement will wait until authentication is
 * handled before checking for a migration status
 * @type {RouteRequirement}
 */
export const isAuthenticatedAndNotMigrating = new RouteRequirement(
  'isAuthenticatedAndNotMigrating',
  [authenticated],
  ({ accountState }) =>
    authenticate().then(() => {
      showAccountStateWarning(accountState);

      // return false if the user is still in Migration flow
      return !hasFlag(accountState, AccountState.ONLINE_IS_MIGRATING);
    }),
  ({}, { redirect }) => redirect(Pages.HOME, WebHost.MIGRATION),
);

/**
 * Requires that a valid payment response has NOT parsed from the query string. If
 * there is a valid payment response, redirect to the payment finished route.
 * @type {RouteRequirement}
 */
export const noValidPaymentResponse = new RouteRequirement(
  'noValidPaymentResponse',
  [],
  ({ getState }) => {
    const state = getState();
    if (typeof state.payment.authResult === 'undefined') {
      return true;
    }
    const authResult = parseInt(state.payment.authResult, 10);
    return authResult !== AdyenStatus.SUCCESS;
  },
  ({ renderProps }, { redirect }) =>
    // keep query in url so the finished page can show the bought package
    redirect(`${Pages.REGISTRATION_FINISHED}${renderProps.location.search}`),
);

/**
 * Requires that the user has a payment response (including a non-success response) in the
 * redux state
 * @type {RouteRequirement}
 */
export const hasPaymentResponse = new RouteRequirement(
  'hasPaymentResponse',
  [],
  ({ getState }) => {
    const state = getState();
    return typeof state.payment.authResult !== 'undefined';
  },
  ({}, { redirect }) => redirect('/'),
);

/**
 * Requires that the user has a developer role
 * @type {RouteRequirement}
 */
export const isDeveloper = new RouteRequirement(
  'isDeveloper',
  [isAuthenticatedAndNotMigrating],
  ({ getState, accountState }) =>
    authenticate().then(() => {
      showAccountStateWarning(accountState);

      const userRoles = getUserPermissionStoredData({ getState }).roles;
      return userRoles.includes(UserRole.DEVELOPER);
    }),
  ({}, { redirect }) => redirect('/'),
);

/**
 * @type function that @returns @type {RouteRequirement}
 * Check group or online sub or just group if @param checkOnlyGroup is true
 * Note: On the client side, this RouteRequirement will wait until authentication is
 * handled before checking for a subscription
 */
export const subscriptionValid = new RouteRequirement(
  'subscriptionValid',
  [isAuthenticatedAndNotMigrating],
  ({ accountState }) =>
    authenticate().then(() => {
      showAccountStateWarning(accountState);

      const onlineSubscriptionValid = hasFlag(accountState, AccountState.ONLINE_SUBSCRIPTION_VALID);

      const groupSubscriptionvalid = hasFlag(accountState, AccountState.GROUP_MEMBERSHIP_VALID);

      return onlineSubscriptionValid || groupSubscriptionvalid; // return true if we have a valid group or online subscripion
    }),
  ({}, { redirect }) => {
    redirect(Pages.ACCOUNT_SETTINGS_SUBSCRIPTION_STATUS, WebHost.MEMBER);
  },
);

/**
 * Requires that the user has a completed profile. If no completed profile is present,
 * we will redirect the user to finish their registration. If the user has not payed yet, they will
 * be redirected to account to finish payment. If a valid subscription is already present, the
 * user will be redirected to the profile builder.
 *
 * Note: On the client side, this RouteRequirement will wait until authentication is
 * handled before checking for a completed profile
 * @type {RouteRequirement}
 */
export const profileCompleted = new RouteRequirement(
  'profileCompleted',
  [isAuthenticatedAndNotMigrating],
  ({ accountState }) =>
    authenticate().then(() => {
      showAccountStateWarning(accountState);

      const onlineProfileComplete = hasFlag(accountState, AccountState.ONLINE_PROFILE_COMPLETE);
      const groupProfileComplete = hasFlag(accountState, AccountState.GROUP_PROFILE_COMPLETE);

      return onlineProfileComplete || groupProfileComplete; // return true if we have a completed group or online profile
    }),
  ({ accountState }, { redirect }) => {
    const onlineSubscriptionIsValid = hasFlag(accountState, AccountState.ONLINE_SUBSCRIPTION_VALID);
    const groupSubscriptionIsValid = hasFlag(accountState, AccountState.GROUP_MEMBERSHIP_VALID);

    if (!onlineSubscriptionIsValid && !groupSubscriptionIsValid) {
      // payment should be completed before going to the online profile builder
      return redirect(Pages.REGISTRATION_CHECKOUT, WebHost.ACCOUNT);
    }

    if (onlineSubscriptionIsValid) {
      return redirect(Pages.PROFILE_BUILDER_ONLINE, WebHost.MEMBER);
    }
    if (groupSubscriptionIsValid) {
      return redirect(Pages.PROFILE_BUILDER_GROUP, WebHost.MEMBER);
    }

    return redirect(Pages.PROFILE_BUILDER_ONLINE, WebHost.MEMBER);
  },
);

/**
 * Requires that the user does not have a completed profile
 *
 * Note: On the client side, this RouteRequirement will wait until authentication is
 * handled before checking for a completed profile
 * @type {RouteRequirement}
 */
export const profileNotCompleted = new RouteRequirement(
  'profileNotComplete',
  [isAuthenticatedAndNotMigrating],
  ({ accountState }) =>
    authenticate().then(() => {
      showAccountStateWarning(accountState);

      const onlineProfileComplete = hasFlag(accountState, AccountState.ONLINE_PROFILE_COMPLETE);

      const groupProfileComplete = hasFlag(accountState, AccountState.GROUP_PROFILE_COMPLETE);

      // return false if the member has completed group or online profile builder and redirect the user to the homepage
      return !(onlineProfileComplete || groupProfileComplete);
    }),
  ({}, { redirect }) => redirect(`${Pages.HOME}`),
);

/**
 * You cannot have an account during registration.
 * If you do, when you don't have a subscription, you are redirected to the payment page.
 * Otherwise you are redirected to the member site, or to the finished page when doing client-side
 * routing.
 *
 * @type {RouteRequirement}
 */
export const noAccount = new RouteRequirement(
  'noAccount',
  [],
  ({ getState }) => !isUserLoggedInSelector(getState()),
  ({ getState }, { redirect }) => {
    if (hasValidSubscriptionSelector(getState())) {
      redirect(
        WP_DEFINE_IS_NODE
          ? getState().config.environmentConfig.web.member.host
          : Pages.REGISTRATION_FINISHED,
      );
    } else {
      redirect(Pages.REGISTRATION_CHECKOUT);
    }
  },
);

export const weighInAllowed = new RouteRequirement(
  'weighInAllowed',
  [],
  ({ getState, accountState }) =>
    authenticate().then(() => {
      showAccountStateWarning(accountState);

      const state = getState();
      const profile = userProfileSelector(state);

      // Weigh in is complete, see if the commitment was not set to allow access
      if (profile?.isCurrentWeighInFinished === true) {
        return !profile?.commitments?.[0]?.weight;
      }

      return profile?.isWeighInAllowed || profile?.isCurrentWeighInFinished !== true;
    }),
  ({ getState }) => {
    const state = getState();
    const currentHost = state.config.environmentConfig.web.member.host;
    const returnPath = `${currentHost}${Pages.HOME}`;

    // Must use window location or it'll navigate to relative within /?=modal/
    if (typeof window !== 'undefined') {
      window.location = returnPath;
      return null;
    }
    // for server side rendering
    throw new RedirectError(returnPath);
  },
);

/**
 * A user hasn't confirmed their initial weights and heights before attempting
 * the first weigh in of a journey, send them to do so.
 * We also let them through if they've got more than one weigh in already
 * as when this work is released we don't want to prompt all users
 */
export const hasConfirmedFirstWeighIn = new RouteRequirement(
  'hasConfirmedFirstWeighIn',
  [weighInAllowed],
  ({ getState, accountState }) =>
    authenticate().then(() => {
      showAccountStateWarning(accountState);

      const state = getState();
      const profile = userProfileSelector(state);

      if (memberTypeSelector(state) === MemberType.GROUP) {
        return true;
      }

      return profile?.currentJourney?.startDetailsConfirmed || profile?.weighIns?.length !== 0;
    }),
  ({}, { redirect }) => redirect(Pages.FIRST_WEIGH_CONFIRMATION),
);

/**
 * Same as hasConfirmedFirstWeighIn above but used for disabling wizard steps
 */
export const firstWeighInConfirmed = getState => {
  const state = getState();
  const profile = userProfileSelector(state);

  return profile?.currentJourney?.startDetailsConfirmed;
};

/**
 * Allow access to the initial height and weight confirmation process if on the first weigh in of a journey
 * we also allow access if the form is in progress, because on step 3 we set the journey confirmation state,
 * breaking the unconfirmed requirement
 */
export const allowFirstWeighInConfirmation = new RouteRequirement(
  'allowFirstWeighInConfirmation',
  [weighInAllowed],
  ({ getState, accountState }) =>
    authenticate().then(() => {
      showAccountStateWarning(accountState);

      const state = getState();
      const profile = userProfileSelector(state);

      if (memberTypeSelector(state) === MemberType.GROUP) {
        return false;
      }

      return (
        (profile?.weighIns.length < 1 && !profile?.currentJourney?.startDetailsConfirmed) ||
        state.routing.locationBeforeTransitions.query.modal === Pages.FIRST_WEIGH_BMI_OK_PROCEED
      );
    }),
  ({}, { redirect }) => redirect(Pages.WEIGHIN),
);

/**
 * When a user has completed the initial checks but their BMI was too low
 * and we don't want them to proceed with weigh in
 */
export const firstWeighInConfirmFailed = new RouteRequirement(
  'firstWeighInConfirmFailed',
  [],
  ({ getState, accountState }) =>
    authenticate().then(() => {
      showAccountStateWarning(accountState);

      const state = getState();
      const profile = userProfileSelector(state);

      return profile?.currentJourney?.startDetailsConfirmed === false;
    }),
  ({}, { redirect }) => redirect(Pages.WEIGHIN),
);

/**
 * For weigh in, if you've confirmed weight you shouldn't be able
 * to go back to that screen again as the BE will block you anyway
 */
export const hasNoWeightEntry = new RouteRequirement(
  'hasNoWeightEntry',
  [],
  ({ getState }) => !formIsSubmitted(getState, FormNames.WI_CONFIRM_WEIGHT),
  ({}, { redirect }) => redirect(Pages.WEIGHIN_MOOD),
);

export const isPregnant = new RouteRequirement(
  'isPregnant',
  [],
  ({ getState }) => {
    const state = getState();
    const profile = userProfileSelector(state);
    return profile && profile.isPregnant;
  },
  ({}, { redirect }) => redirect(Pages.WEIGHIN_AWARD),
);

/**
 * Redirects the user to the correct page when going to the my slimming group
 * @type {RouteRequirement}
 */
export const requireNoGroupId = new RouteRequirement(
  'requireNoGroupId',
  [isAuthenticatedAndNotMigrating],
  ({ getState, accountState }) => {
    showAccountStateWarning(accountState);

    const state = getState();
    const account = userAccountSelector(state);
    return !account.groupId;
  },
  ({ getState }, { redirect }) => {
    const groupId = userGroupIdsSelector(getState())[0];

    return redirect(createPath(Pages.MY_SLIMMING_GROUP_OVERVIEW, { containerId: groupId }));
  },
);

/**
 * UK group registration - ensure that we have an inviteId to procceeed through group registration
 * - check if /group-register/ has an inviteId
 */
export const inviteRequiredForGroupRegistration = new RouteRequirement(
  'inviteRequiredForGroupRegistration',
  [],
  ({ getState }) => getState().registration.invite?.id || null,
  ({}, { redirect }) => redirect(Pages.LOGIN),
);

export const marketHasCyprusGroups = new RouteRequirement(
  'marketHasCyprusGroups',
  [],
  () => marketConfig.hasCyprusGroups,
  ({}, { redirect }) => redirect(Pages.UK_PUBLIC_NEAREST_GROUP_LANDING),
);

/**
 * TODO: GR-336 can this be replaced entirely by the definitive check (e.g. memberType)
 * Requires that a user has a valid subscription or is migrating
 * by checking accountState flags.
 */

export const isOnlineMember = new RouteRequirement(
  'isNotGroupMember',
  [isAuthenticatedAndNotMigrating],
  ({ accountState }) =>
    authenticate().then(() => {
      showAccountStateWarning(accountState);

      return subscriptionTypeSelector(accountState) === MemberTypeStrings.ONLINE;
    }),
  ({ renderProps }, { redirect }) =>
    redirect(
      `${Pages.ACCESS_DENIED}${
        renderProps?.location?.pathname === Pages.COMMUNITY ? '?from=community' : ''
      }`,
      WebHost.MEMBER,
    ),
);

/**
 * Requires that a user has a valid group membership
 *
 * Note: On the client side, this RouteRequirement will wait until authentication is
 * handled before checking for a subscription
 * @param returnPath {String}
 * @type {RouteRequirement}
 */
// TODO: TAT-586 redirect user to access denied page once it is role aware
export const isGroupMember = new RouteRequirement(
  'isNotOnlineMember',
  [isAuthenticatedAndNotMigrating],
  ({ accountState }) =>
    authenticate().then(() => {
      showAccountStateWarning(accountState);

      return subscriptionTypeSelector(accountState) === MemberTypeStrings.GROUP;
    }),
  ({}, { redirect }) => redirect(Pages.ACCOUNT_SETTINGS, WebHost.MEMBER),
);

/**
 * As above, but using memberType rather than account state flags
 * GR-336 to review and see if this can become the principal way to determine group or online
 */
export const memberTypeIsOnline = new RouteRequirement(
  'memberTypeIsOnline',
  [],
  ({ getState }) => memberTypeSelector(getState()) === MemberType.ONLINE,
  ({}, { redirect }) => redirect(Pages.ACCESS_DENIED, WebHost.MEMBER),
);

export const isOnAJourney = new RouteRequirement(
  'isOnAJourney',
  [isAuthenticatedAndNotMigrating],
  getState =>
    authenticate(getState).then(() => {
      const state = getState();
      const profile = userProfileSelector(state);
      const currentJourney = profile?.currentJourney;
      return !!currentJourney; // return true if we have a current journey
    }),
  (getState, renderProps, { redirect }) =>
    redirect(`/?modal=${Pages.NEW_GROUP_JOURNEY_DATE}`, WebHost.MEMBER),
);

// Disable step if journey started today
export const journeyBeforeToday = state => {
  const formSelector = formValueSelector(FormNames.NEW_GROUP_JOURNEY_START_DATE);
  const theDate = formSelector(state, journeyFields.JOURNEY_START_DATE);
  const dateFormat = 'DD,MM,YYYY';
  return moment(theDate).format(dateFormat) === moment().format(dateFormat);
};

/**
 * Requires that the user does NOT have the Free2Go role
 * If they do have the Free2Go role - (redirect them to the Free2Go landing page)
 * @type {RouteRequirement}
 */
export const isNotAFree2GoMember = new RouteRequirement(
  'isNotAFree2GoMember',
  [isAuthenticatedAndNotMigrating],
  async ({ getState }) =>
    await authenticate().then(() => {
      const userRoles = getUserPermissionStoredData({ getState }).roles;
      return userRoles && !userRoles.includes(UserRole.FREE2GO); // Return true if the user does not have the Free2Go role
    }),
  ({}, { redirect }) => redirect(Pages.FREE_2_GO_LANDING, WebHost.ACCOUNT),
);

export const showAccountStateWarning = accountState => {
  if (typeof accountState !== 'number') {
    // the presence of the access token is already checked by the authenticated RouteRequirement
    throw new ReferenceError('Expected access token to be present');
  }
};