import { useCallback, useEffect, useRef } from 'react';
import Debug from 'debug';

import type { QueryParams } from '~/lib/utils/url';
import type { ServiceAuthProps, ServiceAuthStartParams } from '../types';
import type { MusicKitInstance } from './loadMusicKit';

import { tryParseJson } from '~/lib/utils/object';
import {
  useAppAlert,
  useAppLoading,
} from '~/src/components/NextApp/lib/CoreUi';
import useHash from '~/src/hooks/useHash';
import { useAppRouter } from '~/src/lib/router2';
import { reportErrorClient } from '~/src/lib/sentry/client';
import loadMusicKit from './loadMusicKit';

const debug = Debug('songwhip/useAppleMusicAuth');

export type AppleMusicAuthResultSuccess = {
  type: 'APPLE_MUSIC_AUTH_SUCCESS';
  email?: string;
  token: string;
  dob?: string;
  country?: string;
};

export type AppleMusicAuthProps = ServiceAuthProps<AppleMusicAuthResultSuccess>;

export const useAppleMusicAuth = ({
  enabled = true,

  onSuccess,
  onError,
}: AppleMusicAuthProps & {
  /**
   * When 'enabled' (default) MusicKit.js will be loaded
   * and token fetched from our backend. Means you can use
   * the hook but conditionally enable it. Basically a
   * workaround as you can't put hooks in `if` blocks.
   */
  enabled?: boolean;
}) => {
  const userTokenFromUrl = useResolveAppleMusicUserTokenFromUrl();
  const musicKitRef = useRef<MusicKitInstance>(null);

  const appRouter = useAppRouter();
  const setAppLoading = useAppLoading();
  const appAlert = useAppAlert();

  const onUserToken = useCallback(
    async ({
      userData,
      userToken,
    }: {
      userData?: ServiceAuthStartParams['user'];
      userToken: string;
    }) => {
      debug('got user token', userToken);

      onSuccess({
        type: 'APPLE_MUSIC_AUTH_SUCCESS',
        email: userData?.email,
        token: userToken,
        dob: userData?.dob,
        country: userData?.country,
      });
    },
    []
  );

  // we must preload musicKit so it's ready when user triggers .start()
  // using `useMemo` to kick-off request, asap before mount
  useEffect(() => {
    // don't run if caller has disabled
    if (!enabled || typeof window === 'undefined') {
      return;
    }

    // show the app loading bar to indicate stuff is loading behind the scenes
    setAppLoading(true);

    const load = async () => {
      musicKitRef.current = await loadMusicKit();
      setAppLoading(false);
    };

    void load();
  }, [enabled]);

  useEffect(() => {
    if (!userTokenFromUrl) return;

    debug('got auth callback result from hash');

    onUserToken({
      // we supposed to add email to hash before starting the auth flow
      // to handle webview edge case when songwhip page is reloaded
      userData: resolveUserDataFromQuery(appRouter.getQuery()),
      userToken: userTokenFromUrl,
    });
  }, [userTokenFromUrl]);

  return useCallback(
    async ({ user }: ServiceAuthStartParams = {}) => {
      if (user) {
        // keep user data in query to preserve across potential page reloads
        const url = new URL(location.href);

        url.searchParams.set(
          'userData',
          encodeURIComponent(JSON.stringify(user))
        );

        history.replaceState(history.state, '', url.toString());
      }

      try {
        // COMPLEX: we may want to improve this to retry loading the library until we can
        // succeed. Although be aware that Apple only opens the popup if invoked
        // synchronously from a 'trusted' click (ie. directly in a UI event handler).
        if (!musicKitRef.current) {
          appAlert({
            content: 'Apple Music SDK not loaded, please retry',
          });

          return;
        }

        // NOTE: if the popup window is manually closed this promise never resolves
        // UPDATE: this is now handled in the MusicKit.js library
        const userToken = await musicKitRef.current?.authorize();

        onUserToken({
          userData: user,
          userToken,
        });
      } catch (error) {
        if (shouldHandleAppleMusicError(error)) {
          const message = 'Apple Music error';

          reportErrorClient({
            error: new Error(message),
            extras: { error },
          });

          onError?.(message);
        }
      }
    },
    [enabled]
  );
};

/**
 * When in android/ios webview MusicKit.js will navigate away from songwhip.com
 * to apple.com and then back to original page appending the auth result in
 * the url hash fragment. This hook handles this case and parses the user token
 * from the hash.
 */
export const useResolveAppleMusicUserTokenFromUrl = () => {
  const { initialDupeFragment, hash } = useHash();
  const potentialAuthResult = initialDupeFragment || hash;

  return tryParseUserToken(potentialAuthResult);
};

const tryParseUserToken = (string: string | undefined) => {
  try {
    if (string) {
      return tryParseJson<{
        itre?: '0';
        musicUserToken?: string;
      }>(atob(string))?.musicUserToken;
    }
  } catch (e) {}
};

const shouldHandleAppleMusicError = (error: unknown) => {
  if (error instanceof Error) {
    // https://js-cdn.music.apple.com/musickit/v3/docs/index.html?path=/story/reference-javascript-mkerror--page#authorization_error
    const rejectedAuthErrorCode = 'AUTHORIZATION_ERROR';
    return !error.name.includes(rejectedAuthErrorCode);
  }

  return true;
};

const resolveUserDataFromQuery = ({
  userData,
}: QueryParams): ServiceAuthStartParams['user'] => {
  if (!userData) return;

  // Remove userData param from URL
  const parsedUrl = new URL(location.href);
  parsedUrl.searchParams.delete('userData');
  history.replaceState(history.state, '', parsedUrl.toString());

  try {
    const decodedUserData = decodeURIComponent(userData);
    return tryParseJson<ServiceAuthStartParams['user']>(decodedUserData);
  } catch (e) {
    return;
  }
};
