import api from '../api';

import type { AppDispatch, RootState } from 'soapbox/store';

/**
 * LocalStorage 'soapbox:verification'
 *
 * {
 *   token: String,
 *   challenges: {
 *     email: Number (0 = incomplete, 1 = complete),
 *     sms: Number,
 *     age: Number
 *   }
 * }
 */
const LOCAL_STORAGE_VERIFICATION_KEY = 'soapbox:verification';

const PEPE_FETCH_INSTANCE_SUCCESS = 'PEPE_FETCH_INSTANCE_SUCCESS';
const FETCH_CHALLENGES_SUCCESS = 'FETCH_CHALLENGES_SUCCESS';
const FETCH_TOKEN_SUCCESS = 'FETCH_TOKEN_SUCCESS';

const SET_NEXT_CHALLENGE = 'SET_NEXT_CHALLENGE';
const SET_CHALLENGES_COMPLETE = 'SET_CHALLENGES_COMPLETE';
const SET_LOADING = 'SET_LOADING';

const EMAIL: Challenge = 'email';
const SMS: Challenge = 'sms';
const AGE: Challenge = 'age';

export type Challenge = 'age' | 'sms' | 'email'

type Challenges = {
  email?: 0 | 1,
  sms?: 0 | 1,
  age?: 0 | 1,
}

type Verification = {
  token?: string,
  challenges?: Challenges,
  challengeTypes?: Array<'age' | 'sms' | 'email'>
};

/**
 * Fetch the state of the user's verification in local storage.
 */
const fetchStoredVerification = (): Verification | null => {
  try {
    return JSON.parse(localStorage.getItem(LOCAL_STORAGE_VERIFICATION_KEY) as string);
  } catch {
    return null;
  }
};

/**
 * Remove the state of the user's verification from local storage.
 */
const removeStoredVerification = () => {
  localStorage.removeItem(LOCAL_STORAGE_VERIFICATION_KEY);
};

/**
 * Fetch and return the Registration token for Pepe.
 */
const fetchStoredToken = () => {
  try {
    const verification: Verification | null = fetchStoredVerification();
    return verification!.token;
  } catch {
    return null;
  }
};

/**
 * Fetch and return the state of the verification challenges.
 */
const fetchStoredChallenges = () => {
  try {
    const verification: Verification | null = fetchStoredVerification();
    return verification!.challenges;
  } catch {
    return null;
  }
};

/**
 * Fetch and return the state of the verification challenge types.
 */
const fetchStoredChallengeTypes = () => {
  try {
    const verification: Verification | null = fetchStoredVerification();
    return verification!.challengeTypes;
  } catch {
    return null;
  }
};

/**
 * Update the verification object in local storage.
 *
 * @param {*} verification object
 */
const updateStorage = ({ ...updatedVerification }: Verification) => {
  const verification = fetchStoredVerification();

  localStorage.setItem(
    LOCAL_STORAGE_VERIFICATION_KEY,
    JSON.stringify({ ...verification, ...updatedVerification }),
  );
};

/**
 * Fetch Pepe challenges and registration token
 */
const fetchVerificationConfig = () =>
  async(dispatch: AppDispatch) => {
    await dispatch(fetchPepeInstance());

    dispatch(fetchRegistrationToken());
  };

/**
 * Save the challenges in localStorage.
 *
 * - If the API removes a challenge after the client has stored it, remove that
 *    challenge from localStorage.
 * - If the API adds a challenge after the client has stored it, add that
 *    challenge to localStorage.
 * - Don't overwrite a challenge that has already been completed.
 * - Update localStorage to the new set of challenges.
 */
function saveChallenges(challenges: Array<'age' | 'sms' | 'email'>) {
  const currentChallenges: Challenges = fetchStoredChallenges() || {};

  const challengesToRemove = Object.keys(currentChallenges).filter((currentChallenge) => !challenges.includes(currentChallenge as Challenge)) as Challenge[];
  challengesToRemove.forEach((challengeToRemove) => delete currentChallenges[challengeToRemove]);

  for (let i = 0; i < challenges.length; i++) {
    const challengeName = challenges[i];

    if (typeof currentChallenges[challengeName] !== 'number') {
      currentChallenges[challengeName] = 0;
    }
  }

  updateStorage({
    challenges: currentChallenges,
    challengeTypes: challenges,
  });
}

/**
 * Finish a challenge.
 */
function finishChallenge(challenge: Challenge) {
  const currentChallenges: Challenges = fetchStoredChallenges() || {};
  // Set challenge to "complete"
  currentChallenges[challenge] = 1;

  updateStorage({ challenges: currentChallenges });
}

/**
 * Fetch the next challenge
 */
const fetchNextChallenge = (): Challenge => {
  const currentChallenges: Challenges = fetchStoredChallenges() || {};
  return Object.keys(currentChallenges).find((challenge) => currentChallenges[challenge as Challenge] === 0) as Challenge;
};

/**
 * Dispatch the next challenge or set to complete if all challenges are completed.
 */
const dispatchNextChallenge = (dispatch: AppDispatch) => {
  const nextChallenge = fetchNextChallenge();

  if (nextChallenge) {
    dispatch({ type: SET_NEXT_CHALLENGE, challenge: nextChallenge });
  } else {
    dispatch({ type: SET_CHALLENGES_COMPLETE });
  }
};

/**
 * Fetch the challenges and age mininum from Pepe
 */
const fetchPepeInstance = () =>
  (dispatch: AppDispatch, getState: () => RootState) => {
    dispatch({ type: SET_LOADING });

    return api(getState).get('/api/v1/pepe/instance').then(response => {
      const { challenges, age_minimum: ageMinimum } = response.data;
      saveChallenges(challenges);
      const currentChallenge = fetchNextChallenge();

      dispatch({ type: PEPE_FETCH_INSTANCE_SUCCESS, instance: { isReady: true, ...response.data } });

      dispatch({
        type: FETCH_CHALLENGES_SUCCESS,
        ageMinimum,
        currentChallenge,
        isComplete: !currentChallenge,
      });
    })
      .finally(() => dispatch({ type: SET_LOADING, value: false }));
  };

/**
 * Fetch the regristration token from Pepe unless it's already been stored locally
 */
const fetchRegistrationToken = () =>
  (dispatch: AppDispatch, getState: () => RootState) => {
    dispatch({ type: SET_LOADING });

    const token = fetchStoredToken();
    if (token) {
      dispatch({
        type: FETCH_TOKEN_SUCCESS,
        value: token,
      });
      return null;
    }

    return api(getState).post('/api/v1/pepe/registrations')
      .then(response => {
        updateStorage({ token: response.data.access_token });

        return dispatch({
          type: FETCH_TOKEN_SUCCESS,
          value: response.data.access_token,
        });
      })
      .finally(() => dispatch({ type: SET_LOADING, value: false }));
  };

const checkEmailAvailability = (email: string) =>
  (dispatch: AppDispatch, getState: () => RootState) => {
    dispatch({ type: SET_LOADING });

    const token = fetchStoredToken();

    return api(getState).get(`/api/v1/pepe/account/exists?email=${email}`, {
      headers: { Authorization: `Bearer ${token}` },
    })
      .catch(() => {})
      .then(() => dispatch({ type: SET_LOADING, value: false }));
  };

/**
 * Send the user's email to Pepe to request confirmation
 */
const requestEmailVerification = (email: string) =>
  (dispatch: AppDispatch, getState: () => RootState) => {
    dispatch({ type: SET_LOADING });

    const token = fetchStoredToken();

    return api(getState).post('/api/v1/pepe/verify_email/request', { email }, {
      headers: { Authorization: `Bearer ${token}` },
    })
      .finally(() => dispatch({ type: SET_LOADING, value: false }));
  };

const checkEmailVerification = () =>
  (dispatch: AppDispatch, getState: () => RootState) => {
    const token = fetchStoredToken();

    return api(getState).get('/api/v1/pepe/verify_email', {
      headers: { Authorization: `Bearer ${token}` },
    });
  };

/**
 * Confirm the user's email with Pepe
 */
const confirmEmailVerification = (emailToken: string) =>
  (dispatch: AppDispatch, getState: () => RootState) => {
    dispatch({ type: SET_LOADING });

    const token = fetchStoredToken();

    return api(getState).post('/api/v1/pepe/verify_email/confirm', { token: emailToken }, {
      headers: { Authorization: `Bearer ${token}` },
    })
      .then((response) => {
        updateStorageFromEmailConfirmation(dispatch, response.data.token);
      })
      .finally(() => dispatch({ type: SET_LOADING, value: false }));
  };

const updateStorageFromEmailConfirmation = (dispatch: AppDispatch, token: string) => {
  const challengeTypes = fetchStoredChallengeTypes();
  if (!challengeTypes) {
    return;
  }

  const indexOfEmail = challengeTypes.indexOf('email');
  const challenges: Challenges = {};
  challengeTypes?.forEach((challengeType, idx) => {
    const value = idx <= indexOfEmail ? 1 : 0;
    challenges[challengeType] = value;
  });

  updateStorage({ token, challengeTypes, challenges });
  dispatchNextChallenge(dispatch);
};

const postEmailVerification = () =>
  (dispatch: AppDispatch) => {
    finishChallenge(EMAIL);
    dispatchNextChallenge(dispatch);
  };

/**
 * Send the user's phone number to Pepe to request confirmation
 */
const requestPhoneVerification = (phone: string) =>
  (dispatch: AppDispatch, getState: () => RootState) => {
    dispatch({ type: SET_LOADING });

    const token = fetchStoredToken();

    return api(getState).post('/api/v1/pepe/verify_sms/request', { phone }, {
      headers: { Authorization: `Bearer ${token}` },
    })
      .finally(() => dispatch({ type: SET_LOADING, value: false }));
  };

/**
 * Send the user's phone number to Pepe to re-request confirmation
 */
const reRequestPhoneVerification = (phone: string) =>
  (dispatch: AppDispatch, getState: () => RootState) => {
    dispatch({ type: SET_LOADING });

    return api(getState).post('/api/v1/pepe/reverify_sms/request', { phone })
      .finally(() => dispatch({ type: SET_LOADING, value: false }));
  };

/**
 * Confirm the user's phone number with Pepe
 */
const confirmPhoneVerification = (code: string) =>
  (dispatch: AppDispatch, getState: () => RootState) => {
    dispatch({ type: SET_LOADING });

    const token = fetchStoredToken();

    return api(getState).post('/api/v1/pepe/verify_sms/confirm', { code }, {
      headers: { Authorization: `Bearer ${token}` },
    })
      .then(() => {
        finishChallenge(SMS);
        dispatchNextChallenge(dispatch);
      })
      .finally(() => dispatch({ type: SET_LOADING, value: false }));
  };

/**
 * Re-Confirm the user's phone number with Pepe
 */
const reConfirmPhoneVerification = (code: string) =>
  (dispatch: AppDispatch, getState: () => RootState) => {
    dispatch({ type: SET_LOADING });

    return api(getState).post('/api/v1/pepe/reverify_sms/confirm', { code })
      .finally(() => dispatch({ type: SET_LOADING, value: false }));
  };

/**
 * Confirm the user's age with Pepe
 */
const verifyAge = (birthday: Date) =>
  (dispatch: AppDispatch, getState: () => RootState) => {
    dispatch({ type: SET_LOADING });

    const token = fetchStoredToken();

    return api(getState).post('/api/v1/pepe/verify_age/confirm', { birthday }, {
      headers: { Authorization: `Bearer ${token}` },
    })
      .then(() => {
        finishChallenge(AGE);
        dispatchNextChallenge(dispatch);
      })
      .finally(() => dispatch({ type: SET_LOADING, value: false }));
  };

/**
 * Create the user's account with Pepe
 */
const createAccount = (username: string, password: string) =>
  (dispatch: AppDispatch, getState: () => RootState) => {
    dispatch({ type: SET_LOADING });

    const token = fetchStoredToken();

    return api(getState).post('/api/v1/pepe/accounts', { username, password }, {
      headers: { Authorization: `Bearer ${token}` },
    }).finally(() => dispatch({ type: SET_LOADING, value: false }));
  };

export {
  PEPE_FETCH_INSTANCE_SUCCESS,
  FETCH_CHALLENGES_SUCCESS,
  FETCH_TOKEN_SUCCESS,
  LOCAL_STORAGE_VERIFICATION_KEY,
  SET_CHALLENGES_COMPLETE,
  SET_LOADING,
  SET_NEXT_CHALLENGE,
  checkEmailAvailability,
  confirmEmailVerification,
  confirmPhoneVerification,
  createAccount,
  fetchStoredChallenges,
  fetchVerificationConfig,
  fetchRegistrationToken,
  removeStoredVerification,
  requestEmailVerification,
  checkEmailVerification,
  postEmailVerification,
  reConfirmPhoneVerification,
  requestPhoneVerification,
  reRequestPhoneVerification,
  verifyAge,
};
