import { createPortal } from 'react-dom';
import { useCreation, useMemoizedFn, useReactive, useDebounce } from 'ahooks';
import { use, useImperativeHandle, useRef, useState } from 'react';
import noop from 'lodash/noop';
import cloneDeep from 'lodash/cloneDeep';
import isFunction from 'lodash/isFunction';
import isUndefined from 'lodash/isUndefined';
import { joinURL, withQuery } from 'ufo';
import PQueue from 'p-queue';
import { useTheme } from '@arcblock/ux/lib/Theme';
import { getCookieOptions } from '@arcblock/ux/lib/Util';
import Cookie from 'js-cookie';
import { useBrowser } from '@arcblock/react-hooks';
import { getFederatedEnabled, getMaster } from '@arcblock/ux/lib/Util/federated';

import { SessionContext } from '../Session/context';
import { runPopup, openPopup, updateConnectedInfo, getAppId } from '../utils';
import { API_DID_PREFIX, BLOCKLET_SERVICE_PATH_PREFIX } from '../constant';
import DidConnect from './index';

const CLOSE_TIMEOUT = 2000;

// eslint-disable-next-line react/prop-types
function ConnectHolder({ ref }) {
  const { api, session } = use(SessionContext);
  const [targetMessages, setTargetMessages] = useState({
    title: 'DID Connect',
    scan: 'Use follwing methods to complete this action',
    confirm: 'Confirm in your account',
    success: 'Success!',
  });
  const theme = useTheme();
  const defaultValue = {
    containerEl: null,
    popup: true,
    checkFn: api?.get || noop,
  };
  const leavingScreenDelay = theme?.transitions?.duration?.leavingScreen || 500; // 默认值是 195
  const state = useReactive({
    show: false,
    ...cloneDeep(defaultValue),
  });
  const debouncedShow = useDebounce(state.show, {
    wait: leavingScreenDelay,
  });

  const [restParams, setRestParams] = useState({});

  /**
   * 重置 use-connect 中的数据
   */
  const reset = useMemoizedFn(() => {
    const cloneValue = cloneDeep(defaultValue);
    Object.keys(cloneValue).forEach((key) => {
      state[key] = cloneValue[key];
    });
  });

  /**
   * 关闭 did-connect 连接
   * @type {import('../types').CloseConnect}
   */
  const close = useMemoizedFn(() => {
    state.show = false;
    // HACK: 这里不能增加延时，否则会导致某些情况下，赋值的数据被 reset（比如页面中检测到退出后立即唤起登录功能）
    setTimeout(() => {
      reset();
    }, leavingScreenDelay);
  });

  /**
   * 唤起一个 did-connect 连接
   * @type {import('../types').OpenConnect}
   */
  const open = useMemoizedFn((params) => {
    const { containerEl, checkFn, popup, messages, ...rest } = cloneDeep(params);
    if (typeof checkFn === 'function') state.checkFn = checkFn;
    if (popup !== undefined) state.popup = popup;
    if (messages !== undefined) setTargetMessages(messages);
    if (containerEl !== undefined) state.containerEl = containerEl;

    setRestParams(rest);
    state.show = true;
  });
  const oauth = session.useOAuth();

  const loginOAuth = useMemoizedFn(async ({ provider, action, extraParams, onLogin }) => {
    const loginResult = await oauth.loginOAuth(
      { provider },
      {
        action,
        ...extraParams,
      }
    );
    const cookieOptions = getCookieOptions({ returnDomain: false });
    Cookie.remove('connected_did', cookieOptions);
    Cookie.remove('connected_pk', cookieOptions);
    Cookie.remove('connected_wallet_os', cookieOptions);
    onLogin(
      {
        ...loginResult,
        encrypted: false,
      },
      (val) => val
    );
  });

  useImperativeHandle(ref, () => {
    return {
      open,
      loginOAuth,
      close,
    };
  }, [open, close, loginOAuth]);

  const content = (
    <DidConnect
      open={state.show}
      popup={state.popup}
      messages={targetMessages}
      checkFn={state.checkFn}
      // 如果采用 state.restParams 的方式来传值，会导致 DidConnect 组件接收的值是旧的
      {...restParams}
    />
  );

  if (state.containerEl) {
    return createPortal(content, state.containerEl);
  }

  if (state.show) {
    return content;
  }
  if (debouncedShow) {
    return content;
  }
  return null;
}

export default function useConnect() {
  const connectRef = useRef(null);
  const browser = useBrowser();
  const queue = useCreation(() => {
    const queueOptions = { concurrency: 1 };
    // HACK: ArcSphere 中的 DID Connect 不需要排队，可以并发，交给客户端自己来处理
    if (browser.arcSphere) {
      delete queueOptions.concurrency;
    }
    return new PQueue(queueOptions);
  });

  /**
   * 关闭 did-connect 连接
   * @type {import('../types').CloseConnect}
   */
  const close = useMemoizedFn((...args) => {
    connectRef.current?.close(...args);
  });
  /**
   * 唤起一个 did-connect 连接
   * @type {import('../types').OpenConnect}
   */
  const open = useMemoizedFn(
    ({ onSuccess, onClose, onEnd, closeTimeout = CLOSE_TIMEOUT, ...restParams }, ...restargs) => {
      const openFn = () => {
        return new Promise((resolve) => {
          if (typeof onSuccess === 'function') {
            restParams.onSuccess = (...args) => {
              onSuccess(...args);
              setTimeout(() => {
                close();
                resolve();
              }, closeTimeout);
            };
          } else {
            restParams.onSuccess = () => {
              setTimeout(() => {
                close();
                resolve();
              }, closeTimeout);
            };
          }
          if (typeof onClose === 'function') {
            restParams.onClose = (...args) => {
              onClose(...args);
              close();
              resolve();
              queue.clear();
            };
          } else {
            restParams.onClose = () => {
              close();
              resolve();
              queue.clear();
            };
          }
          // HACK: 需要让 react 先清理掉旧的 DidConnect 组件实例，再打开一个新的，否则会造成 did-connect 中的 state 未被重新初始化
          setTimeout(() => {
            connectRef.current?.open(restParams, ...restargs);
            // FIXME: @zhanghan 弄清楚需要延迟的原因，目前不设置 setTimeout 延时会造成 state 未被正确赋值
          }, 300);
        });
      };
      queue.add(openFn);
    }
  );

  /**
   * @type {import('../types').OpenConnectPopup}
   */
  const _openPopup = useMemoizedFn(
    (
      _params = {},
      { baseUrl, locale = 'en', blocklet = globalThis?.blocklet, closeTimeout = CLOSE_TIMEOUT, popupOptions } = {}
    ) => {
      return new Promise((resolveRoot, rejectRoot) => {
        const openPopupFn = () => {
          // eslint-disable-next-line no-async-promise-executor
          return new Promise((resolveChild) => {
            const innerFn = async () => {
              if (isUndefined(baseUrl)) {
                const federatedEnabled = getFederatedEnabled();
                const masterSite = getMaster();
                // eslint-disable-next-line no-param-reassign
                baseUrl = federatedEnabled ? masterSite.appUrl : window.location.origin;
              }
              const { onSuccess, onClose, onError, custom = false, ...params } = _params;
              if (!params?.extraParams?.provider) {
                if (isFunction(onError)) {
                  onError('Must have extraParams.provider');
                }
                throw new Error('Must have extraParams.provider');
              }
              if (!params?.prefix) {
                params.prefix = window.location.origin + joinURL(blocklet?.prefix, API_DID_PREFIX);
              }

              const popupUrlParams = {
                params: btoa(encodeURIComponent(JSON.stringify(params))),
                appPid: blocklet?.appPid,
                locale,
                disableClose: true, // 弹窗模式默认不允许关闭 DID Connect
              };

              const popup = openPopup(
                custom
                  ? withQuery(joinURL(baseUrl, params.prefix), popupUrlParams)
                  : withQuery(joinURL(baseUrl || '/', BLOCKLET_SERVICE_PATH_PREFIX, '/connect'), popupUrlParams),
                popupOptions
              );

              try {
                const data = await runPopup({ popup, closeTimeout });

                updateConnectedInfo(
                  {
                    connected_app: getAppId(),
                    ...data?.options,
                  },
                  true
                );

                if (isFunction(onSuccess)) {
                  onSuccess({ ...data.response, encrypted: false });
                  close();
                  resolveChild();
                } else {
                  close();
                  resolveChild();
                }
                resolveRoot({ ...data.response, encrypted: false });
              } catch (err) {
                console.error(err);
                if (err.message === 'Popup closed') {
                  if (isFunction(onClose)) {
                    onClose();
                    resolveChild();
                    queue.clear();
                  } else {
                    resolveChild();
                    queue.clear();
                  }
                } else if (isFunction(onError)) {
                  onError(err);
                }
                throw err;
              }
            };
            innerFn().catch((error) => {
              resolveChild();
              rejectRoot(error);
            });
          });
        };
        queue.add(openPopupFn);
      });
    }
  );

  const loginOAuth = (options) => {
    connectRef.current.loginOAuth(options);
  };

  const connectHolder = useCreation(() => {
    return <ConnectHolder ref={connectRef} />;
  });
  const connectApi = useCreation(() => {
    return {
      open,
      close,
      loginOAuth,
      openPopup: _openPopup,
      queue,
    };
  });

  return {
    connectHolder,
    connectApi,
  };
}
