import PropTypes from 'prop-types';
import isUndefined from 'lodash/isUndefined';
import { createContext, use } from 'react';
import Toast from '@arcblock/ux/lib/Toast';
import { useCreation, useMemoizedFn, useReactive } from 'ahooks';
import noop from 'lodash/noop';
import { joinURL } from 'ufo';
import { translate } from '@arcblock/ux/lib/Locale/util';
import { BLOCKLET_SERVICE_PATH_PREFIX } from '@arcblock/ux/lib/Util/constant';
import { getFederatedEnabled, getMaster, getBlockletData } from '@arcblock/ux/lib/Util/federated';
import { startAuthentication, startRegistration } from '@simplewebauthn/browser';

import { getApiErrorMessage, getWebAuthnErrorMessage, createAxios, logger } from '../utils';
import { PassportSwitcher, parseResponse } from '../OAuth/passport-switcher';

const PasskeyContext = createContext({});
const { Provider, Consumer: PasskeyConsumer } = PasskeyContext;

const translations = {
  zh: {
    cancel: '取消',
    usePasskey: '使用 Passkey',
    createPasskey: '新建 Passkey',
    creatingPasskey: '创建 Passkey...',
    connectPasskey: '绑定 Passkey',
    nonePasskey: '未配置 Passkey 方式',
    connectPasskeySucceed: 'Passkey 绑定成功',
    disconnectPasskeySucceed: 'Passkey 解绑成功',
    verifyPasskeyFailed: 'Passkey 验证失败',
    connectPasskeyFailed: 'Passkey 绑定失败',
    disconnectPasskeyFailed: 'Passkey 解绑失败',
    createPasskeyDesc1: 'Passkey 是一种密码替代品，使用指纹、面部识别、设备密码或 PIN 验证您的身份。',
    createPasskeyDesc2: 'Passkey 可以用于登录、验证，作为您账户简单且安全的替代方案。',
    emailPlaceholder: '请输入邮箱以作为备用',
    verifyButton: '验证邮箱',
    sendCodeButton: '获取验证码',
    codeSentMessage: '我们已向 {email} 发送验证码，验证码将在 30 分钟内有效，请检查您的邮箱或垃圾邮件文件夹。',
    codeSentSuccess: '验证码发送成功',
    noPassports: '没有可更换的通行证',
    cancelAuth: '取消授权',
    emailInvalid: '邮箱格式不正确',
    webauthn: {
      error: {
        canceled: '身份验证已被取消',
        security: '身份验证过程中出现安全错误',
        notSupported: '当前浏览器不支持 Passkey 身份验证',
        aborted: '身份验证已被中止',
      },
    },
  },
  en: {
    cancel: 'Cancel',
    usePasskey: 'Use Existing Passkey',
    createPasskey: 'Create New Passkey',
    creatingPasskey: 'Creating Passkey...',
    connectPasskey: 'Connect Passkey',
    nonePasskey: 'Passkey is not configured',
    connectPasskeySucceed: 'Passkey add succeed',
    disconnectPasskeySucceed: 'Passkey remove succeed',
    verifyPasskeyFailed: 'Passkey verify failed',
    connectPasskeyFailed: 'Passkey add failed',
    disconnectPasskeyFailed: 'Passkey remove failed',
    createPasskeyDesc1:
      'Passkeys are a password replacement that validates your identity using touch, facial recognition, a device password, or a PIN.',
    createPasskeyDesc2:
      'Passkeys can be used for sign-in, verification as a simple and secure alternative to your account.',
    emailPlaceholder: 'Enter your email address',
    verifyButton: 'Verify Email',
    sendCodeButton: 'Get Verify Code',
    codeSentMessage:
      'We just sent a verification code to {email}, the code will be valid for 30 minutes, please check your inbox or spam folder.',
    codeSentSuccess: 'Verification code sent successfully',
    noPassports: 'No passports to switch',
    cancelAuth: 'Cancel authentication',
    emailInvalid: 'Invalid email address',
    webauthn: {
      error: {
        canceled: 'Authentication was canceled',
        security: 'A security error occurred during authentication',
        notSupported: 'This browser does not support passkey authentication',
        aborted: 'Authentication was aborted',
      },
    },
  },
};

function PasskeyProvider({
  children,
  locale = 'en',
  onAddPasskey = noop,
  onRemovePasskey = noop,
  onSwitchPassport = noop,
}) {
  const prefix = joinURL(window.env?.apiPrefix || BLOCKLET_SERVICE_PATH_PREFIX, '/api/passkey');

  const state = useReactive({
    baseUrl: '/',
    session: undefined,
    connecting: false,
    disconnecting: false,
    targetAppPid: undefined,
  });

  const switchState = useReactive({
    open: false,
    currentUser: null,
    passports: [],
    selectedPassport: undefined,
    reset() {
      switchState.open = false;
      switchState.currentUser = null;
      switchState.passports = [];
      switchState.selectedPassport = undefined;
    },
  });

  const passkeyState = useReactive({
    user: null,
    loading: false,
    creating: false,
    verifying: false,
    creatingStatus: '',
    verifyingStatus: '',
    error: '',
    email: '',
    code: '',
    verified: !(window?.blocklet?.settings?.kyc?.email || false),
    sent: false,
    openDialog: false,
    get status() {
      // eslint-disable-next-line react/no-this-in-sfc
      if (this.creating || this.verifying) {
        return 'scanned';
      }
      // eslint-disable-next-line react/no-this-in-sfc
      if (this.creatingStatus === 'succeed' || this.verifyingStatus === 'succeed') {
        return 'succeed';
      }
      // eslint-disable-next-line react/no-this-in-sfc
      if (this.creatingStatus === 'error' || this.verifyingStatus === 'error') {
        return 'error';
      }
      return '';
    },
    reset() {
      passkeyState.creating = false;
      passkeyState.verifying = false;
      passkeyState.creatingStatus = '';
      passkeyState.verifyingStatus = '';
      passkeyState.error = '';
    },
  });

  const t = useMemoizedFn((key, data = {}) => {
    return translate(translations, key, locale, 'en', data);
  });

  const api = useCreation(() => {
    return createAxios({ baseURL: joinURL(state.baseUrl, prefix), sessionTokenKey: '__sst', timeout: 60 * 1000 });
  }, [state.baseUrl, prefix]);

  const getBlocklet = useMemoizedFn(async () => {
    if (state.baseUrl === '/') {
      return window.blocklet;
    }
    try {
      const url = new URL(state.baseUrl);
      if (url.host === window.location.host) {
        return window.blocklet;
      }
      // eslint-disable-next-line no-empty
    } catch {}

    const blocklet = await getBlockletData(state.baseUrl);
    return blocklet;
  });

  const componentId = window?.blocklet?.componentId;

  const setBaseUrl = (value) => {
    state.baseUrl = value || '/';
  };
  const setTargetAppPid = (value) => {
    state.targetAppPid = value;
  };

  const createPasskey = useMemoizedFn(async (params) => {
    const { data: options } = await api.get('/register', { params });
    console.warn('passkey.create.options', options);

    try {
      // NOTE: should set useAutoRegister to false to make passkey work in safari
      const response = await startRegistration({ optionsJSON: options, useAutoRegister: false });
      console.warn('passkey.create.response', response);

      const { data: result } = await api.post('/register', response, { params: { challenge: options.challenge } });
      console.warn('passkey.create.result', result);

      if (!result.verified) {
        throw new Error(t('createPasskeyFailed'));
      }

      return result;
    } catch (err) {
      // Check if this is a WebAuthn specific error
      if (err.name) {
        throw new Error(getWebAuthnErrorMessage(err, t('createPasskeyFailed'), t));
      }
      throw err;
    }
  });

  const verifyPasskey = useMemoizedFn(async (params) => {
    const { data: options } = await api.get('/auth', { params });
    console.warn('passkey.auth.options', options);

    try {
      const response = await startAuthentication({ optionsJSON: options });
      console.warn('passkey.auth.response', response);

      // FIXME: @zhanghan 这里只是临时的方案，完整的方案需要优化后端的逻辑后，前端一起改动才行
      const { data: result } = await api.post('/auth', response, {
        params: { challenge: options.challenge, targetAppPid: state.targetAppPid },
      });
      console.warn('passkey.auth.result', result);

      if (!result.verified) {
        throw new Error(t('verifyPasskeyFailed'));
      }

      return result;
    } catch (err) {
      // Check if this is a WebAuthn specific error
      if (err.name) {
        throw new Error(getWebAuthnErrorMessage(err, t('verifyPasskeyFailed'), t));
      }
      throw err;
    }
  });

  // eslint-disable-next-line consistent-return
  const connectPasskey = async (extraParams) => {
    state.connecting = true;

    try {
      const result = await createPasskey({
        ...extraParams,
        locale,
        componentId,
      });
      Toast.success(t('connectPasskeySucceed'));
      onAddPasskey(result);
      return result;
    } catch (err) {
      logger.error('Failed to connect passkey', err);
      Toast.error(getApiErrorMessage(err, t('connectPasskeyFailed')));
    } finally {
      state.connecting = false;
    }
  };

  const disconnectPasskey = async ({ session, connectedAccount }) => {
    state.session = session;
    state.disconnecting = true;

    try {
      const result = await verifyPasskey({
        action: 'disconnect',
        locale,
        componentId,
        sourceAppPid: session?.user?.sourceAppPid,
        credentialId: connectedAccount.id,
      });

      Toast.success(t('disconnectPasskeySucceed'));
      onRemovePasskey(result);
    } catch (err) {
      logger.error('Failed to disconnect passkey', err);
      Toast.error(getApiErrorMessage(err, t('disconnectPasskeyFailed')));
    } finally {
      state.disconnecting = false;
    }
  };

  const switchPassport = async (user = {}) => {
    try {
      const { data: passports } = await api.get('/passports');
      if (passports.length) {
        switchState.open = true;
        switchState.currentUser = user;
        switchState.passports = passports || [];
      } else {
        Toast.error(t('noPassports'));
      }
    } catch (err) {
      Toast.error(err.message || t('getPassportFailed'));
    }
  };

  const handleSwitchPassport = useMemoizedFn((...args) => {
    switchState.reset();
    onSwitchPassport(...args);
  });

  const loginPasskey = useMemoizedFn(async ({ action = 'login', ...extraParams } = {}) => {
    const backupBaseUrl = state.baseUrl;
    if (extraParams?.sourceAppPid === window.blocklet?.appPid) {
      state.baseUrl = window.blocklet?.appUrl || '/';
    } else if (extraParams?.sourceAppPid) {
      const blocklet = await getBlocklet();
      const federatedEnabled = getFederatedEnabled(blocklet);
      const master = getMaster(blocklet);
      if (federatedEnabled && master?.appPid && extraParams?.sourceAppPid === master?.appPid) {
        state.baseUrl = master.appUrl;
      }
    }

    try {
      const params = {
        ...extraParams,
        action,
        locale,
        componentId,
      };
      if (isUndefined(params.inviter) && window.localStorage.getItem('inviter')) {
        params.inviter = window.localStorage.getItem('inviter');
      }

      const result = parseResponse(await verifyPasskey(params));
      result.provider = 'passkey';

      return result;
    } catch (err) {
      // Use the WebAuthn specific error handler if the error has a name property (WebAuthn errors do)
      const errMsg = getWebAuthnErrorMessage(err, t('verifyPasskeyFailed'), t);
      state.baseUrl = backupBaseUrl;
      throw new Error(errMsg);
    }
  });
  const logoutPasskey = useMemoizedFn(() => {});

  return (
    <Provider
      value={{
        api,
        locale,
        connectPasskey,
        disconnectPasskey,
        createPasskey,
        verifyPasskey,
        loginPasskey,
        logoutPasskey,
        switchPassport,
        baseUrl: state.baseUrl,
        setBaseUrl,
        setTargetAppPid,
        passkeyState,
        disconnecting: state.disconnecting,
        connecting: state.connecting,
        t,
      }}>
      {children}
      <PassportSwitcher api={api} locale={locale} switchState={switchState} onSwitchPassport={handleSwitchPassport} />
    </Provider>
  );
}

function usePasskey() {
  const context = use(PasskeyContext);
  return context;
}

PasskeyProvider.propTypes = {
  children: PropTypes.node.isRequired,
  locale: PropTypes.string,
  onAddPasskey: PropTypes.func,
  onRemovePasskey: PropTypes.func,
  onSwitchPassport: PropTypes.func,
};

export { PasskeyContext, PasskeyConsumer, PasskeyProvider, usePasskey };
