/**
 * 阿里云人机验证服务官方文档:
 *  https://help.aliyun.com/document_detail/122071.html?spm=a2c4g.11186623.6.570.7b827622W9cfGl
 *
 * 认证流程分为两步:
 *    1、初始化SDK: initSdk
 *    2、发送业务请求 - 请求内置校验: safeRequest
 * nvcRequest = initSdk + safeRequest
 */

import requestAjax from './request'
import { heartCheck, addClass, removeClass } from './index'

const sdkInitingKey = 'weiyi_alinvc_unique_initing'
const sdkInitedKey = 'weiyi_alinvc_unique_inited'
// 标识滑动验证是否成功
const sdkSlidingVerificationKey = 'weiyi_alinvc_sliding_verification'
// 默认滑动验证组件会放到 该 id 的 div 中
const renderToElementId = 'weiyi_alinvc_render_to_element_id'
if (window[sdkInitingKey] === undefined) window[sdkInitingKey] = false
if (window[sdkInitingKey] === undefined) window[sdkInitingKey] = false
if (window[sdkSlidingVerificationKey] === undefined)
  window[sdkSlidingVerificationKey] = false

function startObserverDom(buttonEle, trackEle) {
  const observer = new MutationObserver(eles => {
    if (eles[0].attributeName === 'style') {
      const left = window.parseFloat(buttonEle.style.left)
      if (left <= 3) {
        trackEle.style.width = '0px' // eslint-disable-line no-param-reassign
        buttonEle.style.left = `3px` // eslint-disable-line no-param-reassign
        addClass(trackEle, 'force-width-zero')
      } else {
        trackEle.style.width = `${left + 46}px` // eslint-disable-line no-param-reassign
        removeClass(trackEle, 'force-width-zero')
      }
    }
  })
  observer.observe(buttonEle, {
    attributes: true,
    subtree: false,
    characterData: true,
    attributeFilter: ['style'],
  })
}

function getHourString() {
  const d = new Date()
  return `${d.getFullYear()}${d.getMonth() + 1}${d.getDate()}${d.getHours()}`
}

// 用于检测 SDK 是否已加载, 且初始化完成
function heartCheckSureReady() {
  return (
    typeof window.getNVCVal === 'function' &&
    // eslint-disable-next-line no-underscore-dangle
    window.__nvc__umid.indexOf('defaultToken') < 0
  )
}

// 加载 SDK 并确保 SDK 相关初始化已完成
function loadJSScriptAndCheck(jsUrl) {
  return new Promise((resolve, reject) => {
    const existEl = document.querySelector('script#bxs-ali-sdk-nvc')
    if (existEl) {
      // 包含 3 种情况: 文件已经加载完成、文件正在加载、文件加载完成但sdk未初始化完成
      heartCheck(heartCheckSureReady)
        .then(() => resolve())
        .catch(err => reject(err))
    } else {
      const el = document.createElement('script')
      el.setAttribute('id', 'bxs-ali-sdk-nvc')
      el.onload = () => {
        heartCheck(heartCheckSureReady)
          .then(() => resolve())
          .catch(err => reject(err))
      }
      el.onerror = err =>
        typeof el.remove === 'function' && el.remove() && reject(err)
      el.src = jsUrl
      document.head.appendChild(el)
    }
  })
}

const cacheData = {}

export function resetNvc(requestId) {
  window.nvcReset && window.nvcReset() // eslint-disable-line
  window._nvc_nc && window._nvc_nc.reset() // eslint-disable-line
  window._nvc_nc && window._nvc_nc.hide() // eslint-disable-line
  if (requestId && cacheData[requestId].renderToWrapperId) {
    const wrapperEle = document.querySelector(
      cacheData[requestId].renderToWrapperId,
    )
    if (wrapperEle) {
      const wrapperBox = wrapperEle.parentElement
      wrapperBox.setAttribute('style', 'display: none')
    }
  }
}

export function safeRequest(
  requestId,
  url,
  renderToWrapperId,
  nvcVal,
  sliderStartCb,
) {
  return new Promise((resolve, reject) => {
    if (!requestId) reject(new Error('requestId 参数缺失'))
    if (!cacheData[requestId])
      reject(new Error('请确保 requestId 初始化方法 initSdk 中的一致'))
    if (!window[sdkInitedKey])
      reject(new Error('请先初始化人机验证 SDK: initSdk'))

    cacheData[requestId].url = url
    cacheData[requestId].renderToWrapperId = renderToWrapperId
    cacheData[requestId].nvcVal = nvcVal

    const urlObj = new URL(
      url.indexOf('http') !== 0 ? `${window.location.protocol}${url}` : url,
    )
    urlObj.searchParams.append('nvcValue', nvcVal || window.getNVCVal())
    requestAjax(urlObj.toString())
      .then(res => {
        const { nvcCode } = res
        const code = +nvcCode
        delete res.nvcCode
        const resolveWrapper = resResolve => {
          resolve(resResolve)
        }
        if (code === 200) {
          // 无痕校验 - 成功通过
          resetNvc()
          resolveWrapper(res)
        } else if (code === 100) {
          // 二次验证 - 成功通过
          resetNvc()
          window[sdkSlidingVerificationKey] = true
          resolveWrapper(res)
          const wrapperEle = document.querySelector(renderToWrapperId)
          const wrapperBox = wrapperEle.parentElement
          wrapperBox.setAttribute('style', 'display: none')
        } else if (code === 400 || code === 600 || code === 700) {
          // 唤醒二次验证
          if (typeof sliderStartCb === 'function') sliderStartCb()
          // 将真实的 renderTo DIV 元素移至 renderToWrapperId 内
          if (renderToWrapperId) {
            const wrapperEle = document.querySelector(renderToWrapperId)
            if (wrapperEle) {
              const wrapperBox = wrapperEle.parentElement
              if (!wrapperEle.querySelector(`#${renderToElementId}`)) {
                const ele = document.querySelector(`#${renderToElementId}`)
                wrapperEle.appendChild(ele)
                wrapperBox.setAttribute('style', 'display: block')
                heartCheck(
                  () => {
                    const tempButtonEle = ele.querySelector('.button')
                    return (
                      tempButtonEle &&
                      tempButtonEle.innerHTML &&
                      tempButtonEle.innerHTML.trim()
                    )
                  },
                  100,
                  1000 * 60 * 5,
                )
                  .then(() => {
                    setTimeout(() => {
                      // TODO: why 为什么还需要延迟 500ms
                      startObserverDom(
                        ele.querySelector('.stage1 .button'),
                        ele.querySelector('.stage1 .track'),
                      )
                    }, 500)
                  })
                  .catch(err => {
                    console.error(err) // eslint-disable-line
                  })
              }
            }
          }
          // TODO: 心跳检测二次验证的结果
          window.getNC().then(() => {
            window.NoCaptcha.setEnabled(true)
            window.NoCaptcha.upLang('cn', {
              LOADING: '加载中...', // 加载状态提示
              SLIDER_LABEL: '请向右滑动验证', // 等待滑动状态提示
              CHECK_Y: '验证通过', // 验证通过状态提示
              ERROR_TITLE: '验证未通过', // 验证失败触发拦截状态提示
            })
            window._nvc_nc.reset() // eslint-disable-line
            setTimeout(() => {
              window._nvc_nc.reset() // eslint-disable-line
            }, 100) // TODO: why 需要延迟 100ms
          })
          // 5分钟内如果未通过, 则被认为是机器人
          heartCheck(
            () => window[sdkSlidingVerificationKey],
            1000,
            1000 * 60 * 5,
          )
            .then(() => resolveWrapper(res))
            .catch(err => reject(err))
        } else if (code === 800 || code === 900) {
          reject(new Error('系统检测为机器人操作'))
        }
      })
      .catch(err => {
        reject(err)
      })
  })
}

// 获取人机验证配置对象: 无痕配置 && 滑动验证
function getConfig(requestId, appkey, scene, debugTrans) {
  if (!document.querySelector(`#${renderToElementId}`)) {
    const ele = document.createElement('div')
    ele.setAttribute('id', renderToElementId)
    document.body.appendChild(ele)
  }
  const opts = {
    appkey,
    scene,
    isH5: true,
    popUp: false,
    renderTo: `#${renderToElementId}`,
    nvcCallback: data => {
      safeRequest(
        requestId,
        cacheData[requestId].url,
        cacheData[requestId].renderToWrapperId,
        data,
      )
    },
    language: 'cn',
    retryTimes: 5, // 允许服务器超时重复次数，默认5次。
    customWidth: 200, // 当唤醒滑动验证作为二次验证时，配置滑动验证组件的宽度
    token: new Date().getTime() + String(Math.random()).substring(2),
  }
  if (debugTrans) {
    opts.trans = debugTrans
    opts.appkey = 'CF_APP_1'
    opts.scene = 'nvc_register_h5'
  }
  return opts
}

/**
 * 人机认证 SDK 初始化入口, 步骤如下:
 *  1、请求配置信息: appkey、scene、sdk-url
 *  2、加载 sdk-url (确保 SDK 完全可用后再 resolve)
 * 额外说明: 支持重复调用
 */
export function initSdk(requestId, debugTrans) {
  return new Promise((resolve, reject) => {
    if (!requestId) reject(new Error('requestId 参数缺失'))
    if (cacheData[requestId])
      reject(new Error(`requestId (${requestId}) 已存在`))
    function customResolve() {
      cacheData[requestId] = {
        debugTrans,
      }
      resolve()
    }
    if (window[sdkInitedKey]) {
      // SDK 已初始化完成
      customResolve()
      return
    }
    if (window[sdkInitingKey]) {
      // SDK 正在初始化的
      heartCheck(heartCheckSureReady)
        .then(() => customResolve())
        .catch(err => reject(err))
      return
    }
    window[sdkInitingKey] = true
    // 开始初始化 SDK
    const url = '//third-api.winbaoxian.cn/afs/getAfsConfig'
    requestAjax(url)
      .then(({ data: cfg }) => {
        window.NVC_Opt = getConfig(requestId, cfg.appkey, cfg.scene, debugTrans)
        return loadJSScriptAndCheck(`${cfg.sdk}?t=${getHourString()}`)
      })
      .then(() => {
        window[sdkInitingKey] = false
        window[sdkInitedKey] = true
        customResolve()
      })
      .catch(err => {
        window[sdkInitingKey] = false
        window[sdkInitedKey] = false
        reject(err)
      })
  })
}

export default function nvcRequest(
  requestId,
  url,
  renderId,
  debugTrans,
  sliderStartCb,
) {
  return initSdk(requestId, debugTrans).then(() =>
    safeRequest(requestId, url, renderId, null, sliderStartCb),
  )
}
