import {
  createContext,
  useCallback,
  useEffect,
  useMemo,
  useState
} from 'react';
import { Outlet } from 'react-router-dom';
import useSWR from 'swr';

import { channelAPI } from '../api';
import { getAvatarSrc } from '../helpers';
import { getCurrentSession } from '../api/utils';
import { pack, unpack } from '../helpers/streamActionHelpers';
import useContextHook from './useContextHook';
import useCurrentPage from '../hooks/useCurrentPage';
import useLocalStorage from '../hooks/useLocalStorage';
import usePrevious from '../hooks/usePrevious';

const Context = createContext(null);
Context.displayName = 'User';

const getCurrentSessionFetcher = async () => {
  const { result: data, error } = await getCurrentSession();

  if (error) throw error;

  return data;
};

export const Provider = () => {
  const [isCreatingResources, setIsCreatingResources] = useState(false);
  const [hasErrorCreatingResources, setHasErrorCreatingResources] =
    useState(false);
  const [userData, setUserData] = useState();
  const [hasFetchedInitialUserData, setHasFetchedInitialUserData] =
    useState(false);
  const [isSessionValid, setIsSessionValid] = useState();
  const [logOutAction, setLogOutAction] = useState('');
  const [hasErrorFetchingFollowingList, setHasErrorFetchingFollowingList] =
    useState(false);
  const prevIsSessionValid = usePrevious(isSessionValid);
  const avatarSrc = getAvatarSrc(userData);

  const currentPage = useCurrentPage();
  const hasUserData = !!userData;

  const { remove: removeStoredUserData } = useLocalStorage({
    key: userData?.username,
    options: { keyPrefix: 'user', serialize: pack, deserialize: unpack }
  });

  const {
    data: session,
    mutate: checkSessionStatus, // mutate forces refetching the data, we use it after login and after logout
    error
  } = useSWR('getCurrentSession', getCurrentSessionFetcher);

  const fetchUserData = useCallback(async () => {
    const { result } = await channelAPI.getUserData();
    setHasFetchedInitialUserData(true);

    if (result) {
      setUserData(
        (prevUserData) =>
          JSON.stringify(result) === JSON.stringify(prevUserData)
            ? prevUserData // userData is the same, no need to re-render any downstream context subscribers
            : result // userData changed, so we must re-render all downstream context subscribers
      );
    }

    return result;
  }, []);

  const fetchUserFollowingList = useCallback(async () => {
    setHasErrorFetchingFollowingList(false);
    setUserData((prevUserData) => ({
      ...prevUserData,
      followingList: undefined
    }));
    const { result, error } = await channelAPI.getUserFollowingListData();

    if (result) {
      setUserData((prevUserData) => ({
        ...prevUserData,
        followingList: result.channels
      }));
    }
    if (error) setHasErrorFetchingFollowingList(true);
  }, []);

  // Initialize user resources
  const initUserResources = useCallback(async () => {
    if (await fetchUserData()) return;

    setIsCreatingResources(true);
    setHasErrorCreatingResources(false);
    const { result, error } = await channelAPI.createResources();

    if (result) await fetchUserData();
    if (error) setHasErrorCreatingResources(true);

    setIsCreatingResources(false);
  }, [fetchUserData]);

  const logOut = useCallback(
    (action) => {
      setLogOutAction(action);
      channelAPI.signOut();
      checkSessionStatus();
      setUserData(null);
      setHasFetchedInitialUserData(false);
    },
    [checkSessionStatus]
  );

  // Initial session check on page load
  useEffect(() => {
    if (error) {
      setIsSessionValid(false);
    } else if (session !== undefined) {
      setIsSessionValid(!!session);
    }
  }, [error, session]);

  // Initial fetch of the user data
  useEffect(() => {
    if (!userData && isSessionValid) {
      fetchUserData();
    }
  }, [fetchUserData, isSessionValid, userData]);

  // Remove all stored user data when the session becomes invalid
  // (i.e. user logs out, user session expires, etc.)
  useEffect(() => {
    if (isSessionValid === false) {
      removeStoredUserData(true);
    }
  }, [isSessionValid, removeStoredUserData]);

  useEffect(() => {
    if (
      hasUserData &&
      ['channel_directory', 'following'].includes(currentPage)
    ) {
      fetchUserFollowingList();
    }
  }, [currentPage, fetchUserFollowingList, hasUserData]);

  const value = useMemo(
    () => ({
      checkSessionStatus,
      fetchUserData,
      fetchUserFollowingList,
      hasErrorCreatingResources,
      hasErrorFetchingFollowingList,
      hasFetchedInitialUserData,
      initUserResources,
      isCreatingResources,
      isSessionValid,
      logOut,
      logOutAction,
      prevIsSessionValid,
      userData: userData && { ...userData, avatarSrc }
    }),
    [
      avatarSrc,
      checkSessionStatus,
      fetchUserData,
      fetchUserFollowingList,
      hasErrorCreatingResources,
      hasErrorFetchingFollowingList,
      hasFetchedInitialUserData,
      initUserResources,
      isCreatingResources,
      isSessionValid,
      logOut,
      logOutAction,
      prevIsSessionValid,
      userData
    ]
  );

  return (
    <Context.Provider value={value}>
      <Outlet />
    </Context.Provider>
  );
};

export const useUser = () => useContextHook(Context);
