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

import type {
  SpotifyAuthResult,
  SpotifyAuthResultSuccess,
} from '~/src/pages/api/spotify/auth/callback';
import type { ServiceAuthProps, ServiceAuthStartParams } from '../types';

import { assertEnvVar } from '~/lib/utils/assert';
import { tryParseJson } from '~/lib/utils/object';
import { useAppRouter } from '~/src/lib/router2';
import { reportErrorClient } from '~/src/lib/sentry/client';
import onceIdle from '~/src/lib/utils/onceIdle';
import openPopup from '~/src/lib/utils/openPopup';
import { SPOTIFY_AUTH_PAYLOAD_PARAM } from './constants';
import { composeSpotifyAuthUrl, resolveSpotifyAppUrl } from './utils';

export * from './constants';

export const SPOTIFY_CLIENT_ID = assertEnvVar(
  process.env.NEXT_PUBLIC_SPOTIFY_CLIENT_ID,
  'NEXT_PUBLIC_SPOTIFY_CLIENT_ID'
);

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

export type SpotifyAuthProps = ServiceAuthProps<SpotifyAuthResultSuccess>;

export const useSpotifyAuth = ({ onSuccess, onError }: SpotifyAuthProps) => {
  const childWinRef = useRef<Window | null>(null);
  const appRouter = useAppRouter();

  const fallbackAuthPayloadParam =
    appRouter.getAsQuery()[SPOTIFY_AUTH_PAYLOAD_PARAM];

  const handleAuthPayload = (payload: SpotifyAuthResult) => {
    debug('handle auth payload', payload);

    switch (payload.type) {
      case 'SPOTIFY_AUTH_SUCCESS':
        onSuccess(payload);

        break;
      case 'SPOTIFY_AUTH_ERROR':
        // https://developer.spotify.com/documentation/web-api/tutorials/code-flow
        if (payload.error !== 'access_denied') {
          const message = 'Spotify Auth error';

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

          onError?.(message);
        }

        break;
    }
  };

  useEffect(() => {
    // the child popup window will postMessage this window when
    // the auth flow is complete, passing the google token payload
    const onMessage = ({ data }) => {
      debug('postMessage', data);

      const payload = tryParseJson<SpotifyAuthResultSuccess>(data);

      const shouldHandle =
        !!payload?.type && !!~payload.type.indexOf('SPOTIFY');

      // abort if payload isn't as expected
      if (!shouldHandle) return;

      // close that popup asap
      childWinRef.current?.close();

      handleAuthPayload(payload);
    };

    addEventListener('message', onMessage);

    // detach global event listener on unmount
    return () => {
      removeEventListener('message', onMessage);
    };
  }, []);

  /**
   * When oauth popup flow fails to postMessage back to the parent window
   * it will redirect back to the `fallbackPath` passing the auth payload
   * as a query param. This can happen when app is running inside a webview.
   */
  useEffect(() => {
    if (!fallbackAuthPayloadParam) return;

    const authPayload = tryParseJson<SpotifyAuthResultSuccess>(
      fallbackAuthPayloadParam
    );

    if (!authPayload) {
      return;
    }

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

    handleAuthPayload(authPayload);
  }, [fallbackAuthPayloadParam]);

  return useCallback(({ user }: ServiceAuthStartParams = {}) => {
    const url = new URL('https://accounts.spotify.com/authorize');
    const deepLink = resolveSpotifyAppUrl(navigator.userAgent);
    const fallbackUrl = location.href;

    const composedUrl = composeSpotifyAuthUrl({
      clientId: SPOTIFY_CLIENT_ID,
      url,
      fallbackUrl,
      // NOTE: In deeplink case this url used as a fallback to open auth page in browser in case app is not installed
      // In this case we want to always show dialog to avoid race condition when user already authorized
      // Songwhip and both (app and browser) automatically authed user in the same time
      showDialog: Boolean(deepLink),
      userData: user,
    });

    if (deepLink) {
      const composedDeepLink = composeSpotifyAuthUrl({
        clientId: SPOTIFY_CLIENT_ID,
        url: deepLink,
        fallbackUrl,
        userData: user,
      });

      openPopup({
        url: composedDeepLink.toString(),
        target: '_self',
      });

      // as we cannot detect if the app is installed or not
      // we open with idle (to make sure we tried to open the app)
      // the http auth as a fallback every time
      onceIdle(() => {
        location.assign(composedUrl.toString());
      });
    } else {
      childWinRef.current = openPopup({
        url: composedUrl.toString(),

        width: 480,
        height: 640,
      });
    }
  }, []);
};
