import {
  createContext,
  useEffect,
  useState,
  useMemo,
  useCallback,
  useContext,
} from 'react';
import { Auth } from '@aws-amplify/auth';
import { Hub, HubCallback } from '@aws-amplify/core';
import { useApolloClient } from '@apollo/client';
import { setSentryUser } from 'lib/sentry';
import { setIntercomUser } from 'lib/intercom';
import { CognitoUserInterface } from 'lib/amplify/types';
import { cognitoUserVar } from 'lib/appsync/variables';

interface UserContextState {
  user: CognitoUserInterface | null;
  checked: boolean;
}

interface UserContextValue extends UserContextState {
  signOut: () => Promise<any>;
}

function currentAuthenticatedUser() {
  return Auth.currentAuthenticatedUser({ bypassCache: true }).then(
    (data: CognitoUserInterface) => data
  );
}

export const UserContext = createContext<UserContextValue | null>(null);

export const useUserContext = () => {
  const context = useContext(UserContext);
  if (context === null) {
    throw new Error('useUserContext must be used within a UserContext');
  }
  return context;
};

export const UserProvider = (props: { children: React.ReactNode }) => {
  const client = useApolloClient();

  const [state, setState] = useState<UserContextState>({
    user: null,
    checked: false,
  });

  const checkUser = useCallback(async function checkUser() {
    try {
      const user = await currentAuthenticatedUser();
      setState({ user, checked: true });
    } catch (error) {
      setState({ user: null, checked: true });
    }
  }, []);

  useEffect(() => {
    checkUser();
  }, [checkUser]);

  useEffect(() => {
    const callback: HubCallback = async ({ payload }) => {
      if (payload.event === 'signIn') {
        const user = await currentAuthenticatedUser();
        setState({ user, checked: true });
      }
      if (payload.event === 'signOut') {
        client.clearStore();
        setState({ user: null, checked: true });
      }
    };
    Hub.listen('auth', callback);
  }, [client]);

  useEffect(() => {
    if (state.user) {
      setIntercomUser({
        user_id: state.user.username,
        email: state.user.attributes?.email,
      });
      setSentryUser({
        id: state.user.username,
        username: state.user.username,
        email: state.user.attributes?.email,
      });
      cognitoUserVar(state.user.username);
    } else {
      setIntercomUser(null);
      setSentryUser(null);
      cognitoUserVar(null);
    }
  }, [state.user]);

  const signOut = async () => await Auth.signOut();

  const value = useMemo(
    () => ({
      ...state,
      signOut,
    }),
    [state]
  );

  return <UserContext.Provider value={value} {...props} />;
};

export default UserProvider;
