import { use, useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import { Box, Divider, Skeleton } from '@mui/material';
import {
  useCreation,
  useDebounceFn,
  useMemoizedFn,
  useMount,
  usePrevious,
  useSize,
  useUpdate,
  useUpdateEffect,
} from 'ahooks';
import noop from 'lodash/noop';
import isUndefined from 'lodash/isUndefined';

import CloseButton from '@arcblock/ux/lib/CloseButton';
import {
  LOGIN_PROVIDER,
  LOGIN_PROVIDER_NAME,
  OAUTH_PROVIDER,
  DID_CONNECT_MEDIUM_WIDTH,
  DID_CONNECT_SMALL_WIDTH,
} from '@arcblock/ux/lib/Util/constant';
import QRCode from '@arcblock/ux/lib/QRCode';
import { DIDConnectFooter, withContainer, withUxTheme } from '@arcblock/ux/lib/DIDConnect';
import { useTheme } from '@arcblock/ux/lib/Theme';
import ProviderIcon from '@arcblock/ux/lib/DIDConnect/provider-icon';

import '@fontsource/lexend/400.css';
import '@fontsource/lexend/600.css';

import withBlocklet from './with-blocklet';
import withBridgeCall from './with-bridge-call';
import useSecurity from './hooks/security';

import { SessionContext } from '../Session/context';
import { StateProvider, useStateContext } from './contexts/state';
import ConnectChooseList from './components/login-item/connect-choose-list';
import AutoHeight from './components/auto-height';
import useToken from './hooks/token';
import { useOAuth } from '../OAuth';
import ConnectStatus from './components/connect-status';
import { usePasskey } from '../Passkey/context';
import { API_DID_PREFIX, DEFAULT_TIMEOUT, BUSY_STATUS, CHECK_STATUS_INTERVAL } from '../constant';
import { getWebWalletUrl } from '../utils';
import DIDConnectTitle from './components/did-connect-title';
import DownloadTips from './components/download-tips';
import useMethodList from './hooks/method-list';
import useAuthUrl from './hooks/auth-url';
import { getWalletDid } from '../User/use-did';

function Connect({
  hideCloseButton = false,
  mode = 'dialog',
  action,
  baseUrl = '',
  checkFn,
  checkInterval = CHECK_STATUS_INTERVAL,
  checkTimeout = DEFAULT_TIMEOUT * 1000,
  prefix = API_DID_PREFIX,
  tokenKey = '_t_',
  locale = 'en',
  encKey = '_ek_',
  autoConnect = true,
  forceConnected = true,
  saveConnect = true,
  useSocket = true,
  allowWallet = true,
  provider = '',
  messages = {},
  passkeyBehavior = 'none',
  webWalletUrl = getWebWalletUrl(),
  enabledConnectTypes = ['web', 'mobile', ...Object.keys(OAUTH_PROVIDER)],
  extraContent = null,
  disableSwitchApp = false,
  magicToken = undefined,
  customItems = [],
  onClose = noop,
  onError = noop,
  onSuccess = noop,
  onRecreateSession = noop,
  setColor = noop,
}) {
  const theme = useTheme();
  const forceUpdate = useUpdate();
  const sessionCtx = use(SessionContext);
  const currentUserDid = getWalletDid(sessionCtx?.session?.user);

  const {
    t,
    staticState,
    connectState,
    extraParams,
    currentAppInfo,
    currentAppColor,
    // 插件相关
    selectedPlugin,
    blocklet,
    masterBlocklet,
  } = useStateContext();
  const { state, generate, cancelWhenScanned } = useToken({
    action,
    baseUrl,
    checkFn,
    checkInterval,
    checkTimeout,
    extraParams,
    prefix,
    onError,
    onSuccess,
    locale,
    tokenKey,
    encKey,
    autoConnect,
    forceConnected: forceConnected === true ? currentUserDid || true : forceConnected,
    saveConnect,
    useSocket,
    allowWallet,
    provider,
  });
  const refreshingToken = useRef(false);
  const rootRef = useRef(null);
  const size = useSize(rootRef);
  const isSmallView = useCreation(() => {
    if (size) {
      return size.width < DID_CONNECT_MEDIUM_WIDTH - 50;
    }
    return true;
  }, [size, size?.width]);

  const [isInitSmallView, setIsInitSmallView] = useState(false);

  useMount(() => {
    setIsInitSmallView(size?.width < DID_CONNECT_MEDIUM_WIDTH - 50);
  });

  const { oauthState, setBaseUrl } = useOAuth();
  const { passkeyState, setTargetAppPid } = usePasskey();

  useMount(() => {
    setBaseUrl(baseUrl);
    setTargetAppPid(blocklet?.appPid);
    // 每次打开 did-connect，都重置状态
    state.reset();
    oauthState.reset();
    passkeyState.reset();
  });

  useEffect(() => {
    setColor(currentAppColor);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [currentAppColor]);

  const statusMessages = useCreation(() => {
    return {
      confirm: messages.confirm,
      success: messages.success,
      error: selectedPlugin?.state?.error || state.error || passkeyState.error || oauthState.error || '',
    };
  }, [
    messages.confirm,
    messages.success,
    state.error,
    oauthState.error,
    passkeyState.error,
    selectedPlugin?.state?.error,
  ]);

  const showStatus = useCreation(() => {
    return (
      BUSY_STATUS.includes(passkeyState.status) ||
      BUSY_STATUS.includes(oauthState.status) ||
      BUSY_STATUS.includes(state.status) ||
      BUSY_STATUS.includes(selectedPlugin?.state?.computedStatus)
    );
  }, [state.status, oauthState.status, passkeyState.status, selectedPlugin?.state?.computedStatus]);

  const handleReset = useMemoizedFn(async () => {
    onRecreateSession();
    oauthState.reset();
    passkeyState.reset();
    await generate(false);
  });
  const handleRetry = useMemoizedFn(() => {
    connectState?.retryConnect();
  });
  const handleCancel = useMemoizedFn(() => {
    onRecreateSession();
    oauthState.reset();
    passkeyState.reset();
    selectedPlugin?.state?.reset();
    staticState.current.cancelCount++;
    cancelWhenScanned();
  });

  const { run: debounceHandleTimeout } = useDebounceFn(
    () => {
      if (refreshingToken.current) return;
      if (state.status === 'timeout') {
        refreshingToken.current = true;
        state.reset();
        handleReset();
        refreshingToken.current = false;
      }
    },
    { leading: true, trailing: false }
  );
  // eslint-disable-next-line react-hooks/exhaustive-deps
  useEffect(debounceHandleTimeout, [state.status]);

  const chooseMethod = useCreation(() => {
    return LOGIN_PROVIDER_NAME[connectState.chooseMethod] || 'DID Wallet';
  }, [connectState.chooseMethod]);

  const {
    showMobileLogin,
    hideChooseList,
    oauthProviderList,
    showOAuthLogin,
    showPasskeyLogin,
    showWebLogin,
    showEmailLogin,
    hideQRCode,
  } = useMethodList({
    action: state.action,
    sourceAppPid: connectState?.sourceAppPid,
    enabledConnectTypes,
    allowWallet,
    passkeyBehavior,
    webWalletUrl,
    mode,
    blocklet: connectState?.sourceAppPid ? masterBlocklet : blocklet,
    isSmallView,
  });

  const prevSourceAppPid = usePrevious(connectState?.sourceAppPid);

  // 切换了当前展示的应用后，需要重新生成二维码
  useUpdateEffect(() => {
    // HACK: connectState.sourceAppPid 是用户在页面中执行操作修改的值，最开始的时候就是 undefined，并在 did-connect 页面加载后，会由 app-info 触发一次更新（无论如何都会触发，这一次更新不应该触发 generate）
    if (isUndefined(prevSourceAppPid)) {
      return;
    }
    generate();
  }, [connectState?.sourceAppPid]);

  const getSpacingNumber = (value) => {
    const spacingValue = theme.spacing(value);
    return parseInt(spacingValue, 10);
  };
  const closeButton = useCreation(() => {
    return hideCloseButton ? null : (
      <CloseButton
        onClose={onClose}
        sx={{
          position: 'absolute',
          right: 14,
          top: 14,
        }}
      />
    );
  }, [hideCloseButton, onClose]);

  let content = null;
  if (showStatus) {
    content = (
      <Box
        sx={{
          flex: 1,
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
        }}>
        <Box>
          <ConnectStatus
            status={selectedPlugin?.state?.computedStatus || oauthState.status || passkeyState.status || state.status}
            nextWorkflow={state.nextWorkflow}
            mfaCode={state.mfaCode}
            onCancel={handleCancel}
            onRetry={handleRetry}
            onClose={onClose}
            messages={statusMessages}
            locale={locale}
            className="did-connect__auth-status auth-status"
            loadingIcon={
              connectState.chooseMethod ? (
                <ProviderIcon provider={connectState.chooseMethod} sx={{ color: 'text.primary' }} />
              ) : null
            }
            chooseMethod={chooseMethod}
            hideRetry={connectState.chooseMethod === 'email'}
            hideBack={!!magicToken}
          />
        </Box>
      </Box>
    );
  }

  const urlWithParams = useAuthUrl({ disableSwitchApp, tokenState: state });

  // 防止二维码抖动，使用 useCreation 进行缓存
  const qrcode = useCreation(() => {
    const backgroundColor = theme.mode === 'dark' ? theme.palette.grey[500] : 'white';
    return (
      <Box
        sx={{
          p: hideChooseList ? 3 : 2,
          width: (hideChooseList ? 240 : 196) + getSpacingNumber(1) * 2,
          height: (hideChooseList ? 240 : 196) + getSpacingNumber(1) * 2,
        }}>
        {state.url ? (
          <QRCode
            data={urlWithParams}
            size={hideChooseList ? 240 - getSpacingNumber(3) * 2 : 196 - getSpacingNumber(2) * 2}
            sx={{
              width:
                (hideChooseList ? 240 - getSpacingNumber(3) * 2 : 196 - getSpacingNumber(2) * 2) +
                getSpacingNumber(1) * 2,
              height:
                (hideChooseList ? 240 - getSpacingNumber(3) * 2 : 196 - getSpacingNumber(2) * 2) +
                getSpacingNumber(1) * 2,
              flex: 1,
              backgroundColor,
              p: 1,
              fontSize: 0,
              textAlign: 'center',
              boxSizing: 'border-box',
              borderRadius: 1,
              border: '1px solid',
              borderColor: 'divider',
            }}
            config={{
              backgroundOptions: {
                color: backgroundColor,
              },
            }}
          />
        ) : (
          <Skeleton
            animation="wave"
            variant="rectangular"
            sx={{
              position: 'absolute',
              left: getSpacingNumber(2) + 1,
              right: getSpacingNumber(2) + 1,
              top: getSpacingNumber(2) + 1,
              bottom: getSpacingNumber(2) + 1,
              borderRadius: 1,
              zIndex: 1,
              width: 'auto',
              height: 'auto',
            }}
          />
        )}
      </Box>
    );
  }, [urlWithParams, hideChooseList]);

  const didConnectFlexDirection = useCreation(() => {
    if (hideChooseList) {
      return 'column-reverse';
    }
    if (!hideQRCode && isInitSmallView) {
      return 'column';
    }
    return 'row';
  }, [hideChooseList, isInitSmallView, hideQRCode]);

  const fallbackContent = (
    <Box
      className="did-connect__body"
      sx={{
        display: 'flex',
        flexDirection: didConnectFlexDirection,
        justifyContent: 'center',
        alignItems: 'stretch',
        flex: 1,
        gap: !hideQRCode && isSmallView ? 0 : 1.5,
        overflow: 'visible',
        px: hideChooseList ? 2 : 0,
      }}>
      {!showStatus && !hideQRCode ? (
        <>
          <Box
            sx={{
              display: 'flex',
              alignItems: 'center',
              overflowX: 'auto',
              overflowY: 'visible',
              maxWidth: '100%',
              margin: 'auto',
            }}>
            <Box
              sx={{
                fontSize: 0,
                position: 'relative',
                mb: hideChooseList ? 4 : 2.5,
              }}>
              {qrcode}
              <Box
                sx={{
                  position: 'absolute',
                  color: 'text.secondary',
                  fontSize: 12,
                  bottom: hideChooseList ? 8 : 4,
                  transform: 'translateY(100%)',
                  left: 0,
                  right: 0,
                  textAlign: 'center',
                }}>
                {t('scanWithWallet1')} <DownloadTips /> {t('scanWithWallet2')}
              </Box>
            </Box>
          </Box>
          {hideChooseList ? null : (
            <Box>
              <Divider
                orientation="vertical"
                sx={{
                  fontSize: 12,
                  color: 'text.hint',
                  '&::before, &::after': {
                    borderColor: 'divider',
                  },
                }}>
                or
              </Divider>
            </Box>
          )}
        </>
      ) : null}
      <Box
        sx={{
          display: 'flex',
          flex: 1,
        }}>
        {/* eslint-disable-next-line no-nested-ternary */}
        {content}
        <ConnectChooseList
          slotProps={{
            root: {
              sx: [showStatus ? { display: 'none' } : {}],
            },
          }}
          allowWallet={allowWallet}
          size={hideQRCode && mode !== 'dialog' ? 'normal' : 'small'}
          tokenState={state}
          messages={messages}
          tokenKey={tokenKey}
          onSuccess={onSuccess}
          passkeyBehavior={passkeyBehavior}
          webWalletUrl={webWalletUrl}
          extraContent={extraContent}
          enabledConnectTypes={enabledConnectTypes}
          onRest={handleReset}
          showMobileLogin={showMobileLogin}
          showOAuthLogin={showOAuthLogin}
          showPasskeyLogin={showPasskeyLogin}
          showWebLogin={showWebLogin}
          showEmailLogin={showEmailLogin}
          oauthProviderList={oauthProviderList}
          disableSwitchApp={disableSwitchApp}
          forceUpdate={forceUpdate}
          magicToken={magicToken}
          baseUrl={baseUrl}
          customItems={customItems}
        />
      </Box>
    </Box>
  );
  let contentResult = fallbackContent;
  if (selectedPlugin) {
    contentResult = selectedPlugin.renderPlaceholder({
      fallback: fallbackContent,
      forceUpdate,
      onSuccess,
      onError,
    });
  } else {
    contentResult = fallbackContent;
  }

  return (
    <Box
      ref={rootRef}
      className="did-connect__root"
      sx={{
        backgroundColor: 'background.default',
        display: 'flex',
        flexDirection: 'column',
        height: '100%',
        position: 'relative',
        maxWidth: '100%',
        width:
          // eslint-disable-next-line no-nested-ternary
          mode === 'drawer'
            ? '100%'
            : hideQRCode || showStatus || hideChooseList
              ? DID_CONNECT_SMALL_WIDTH
              : DID_CONNECT_MEDIUM_WIDTH,
        transition: 'width 0.2s ease-in-out',
        margin: 'auto',
        p: isSmallView ? 2 : 3,
        pb: 0,
        gap: 2,
      }}>
      <Box data-did-auth-url={state.url} sx={{ display: 'none' }} />
      <DIDConnectTitle
        title={messages.title}
        description={messages.scan}
        extraContent={extraContent}
        disableSwitchApp={disableSwitchApp}
      />
      <AutoHeight initHeight={24 + 16 + 32}>{contentResult}</AutoHeight>
      <DIDConnectFooter currentAppInfo={currentAppInfo} currentAppColor={currentAppColor} />
      {closeButton}
    </Box>
  );
}

Connect.propTypes = {
  mode: PropTypes.oneOf(['dialog', 'drawer', 'page']),

  action: PropTypes.string.isRequired,
  baseUrl: PropTypes.string,
  checkFn: PropTypes.func.isRequired,
  checkInterval: PropTypes.number,
  checkTimeout: PropTypes.number,
  // extraParams: PropTypes.object, // 需要使用 useStateContext 中导出的
  prefix: PropTypes.string,
  messages: PropTypes.object,
  tokenKey: PropTypes.string,
  locale: PropTypes.oneOf(['en', 'zh', '']),
  encKey: PropTypes.string,
  autoConnect: PropTypes.bool,
  forceConnected: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
  saveConnect: PropTypes.bool,
  useSocket: PropTypes.bool,
  extraContent: PropTypes.any,
  passkeyBehavior: PropTypes.oneOf(['none', 'both', 'only-existing', 'only-new']),
  enabledConnectTypes: PropTypes.arrayOf(PropTypes.oneOf(['web', 'mobile', ...Object.keys(OAUTH_PROVIDER)])),
  webWalletUrl: PropTypes.string,

  allowWallet: PropTypes.bool,
  provider: PropTypes.oneOf([LOGIN_PROVIDER.WALLET, ...Object.keys(OAUTH_PROVIDER), '']),
  hideCloseButton: PropTypes.bool,
  disableSwitchApp: PropTypes.bool,
  onClose: PropTypes.func,
  onError: PropTypes.func,
  onSuccess: PropTypes.func,
  onRecreateSession: PropTypes.func,
  setColor: PropTypes.func,
  magicToken: PropTypes.string,
  customItems: PropTypes.arrayOf(PropTypes.node),
};

function WrapConnect({ testOnlyBorderColor = undefined, ...props }) {
  const { checkFn, extraParams = {}, blocklet, masterBlocklet, action, locale = 'en' } = props;
  if (typeof checkFn !== 'function') {
    throw new Error('Cannot initialize did connect component without a fetchFn');
  }

  return (
    <StateProvider
      blocklet={blocklet}
      masterBlocklet={masterBlocklet}
      action={action}
      locale={locale}
      extraParams={extraParams}
      testOnlyBorderColor={testOnlyBorderColor}
      sx={{
        position: 'relative',
        width: '100%',
        height: '100%',
        lineHeight: 1.2,
        color: 'grey.700',
        '&, & *, & *:before, & *:after': {
          fontFamily: 'Lexend', // 保持跟 DID Wallet 一致
          boxSizing: 'border-box',
        },
      }}>
      <Connect {...props} />
    </StateProvider>
  );
}

WrapConnect.propTypes = {
  checkFn: PropTypes.func.isRequired,
  extraParams: PropTypes.object,
  blocklet: PropTypes.object.isRequired,
  masterBlocklet: PropTypes.object,
  action: PropTypes.string.isRequired,
  locale: PropTypes.string,
  testOnlyBorderColor: PropTypes.string,
};

export default withUxTheme(withBridgeCall(withBlocklet(withContainer(WrapConnect))));

export { useSecurity };
