import {
  hasOwn
} from 'uni-shared'

import {
  publish,
  requireNativePlugin,
  base64ToArrayBuffer,
  arrayBufferToBase64
} from '../../bridge'

import {
  invoke
} from 'uni-core/service/bridge'

let requestTaskId = 0
const requestTasks = {}

const publishStateChange = res => {
  publish('onRequestTaskStateChange', res)
  delete requestTasks[requestTaskId]
}

const cookiesParse = header => {
  let cookiesStr = header['Set-Cookie'] || header['set-cookie']
  let cookiesArr = []
  if (!cookiesStr) {
    return []
  }
  if (cookiesStr[0] === '[' && cookiesStr[cookiesStr.length - 1] === ']') {
    cookiesStr = cookiesStr.slice(1, -1)
  }
  const handleCookiesArr = cookiesStr.split(';')
  for (let i = 0; i < handleCookiesArr.length; i++) {
    if (handleCookiesArr[i].indexOf('Expires=') !== -1 || handleCookiesArr[i].indexOf('expires=') !== -1) {
      cookiesArr.push(handleCookiesArr[i].replace(',', ''))
    } else {
      cookiesArr.push(handleCookiesArr[i])
    }
  }
  cookiesArr = cookiesArr.join(';').split(',')

  return cookiesArr
}

export function createRequestTaskById (requestTaskId, {
  url,
  data,
  header,
  method = 'GET',
  responseType,
  sslVerify = true,
  firstIpv4 = false,
  tls,
  timeout = (__uniConfig.networkTimeout && __uniConfig.networkTimeout.request) || 60 * 1000
} = {}) {
  const stream = requireNativePlugin('stream')
  const headers = {}

  let abortTimeout
  let aborted
  let hasContentType = false
  for (const name in header) {
    if (!hasContentType && name.toLowerCase() === 'content-type') {
      hasContentType = true
      headers['Content-Type'] = header[name]
      // TODO 需要重构
      if (method !== 'GET' && header[name].indexOf('application/x-www-form-urlencoded') === 0 && typeof data !==
        'string' && !(data instanceof ArrayBuffer)) {
        const bodyArray = []
        for (const key in data) {
          if (hasOwn(data, key)) {
            bodyArray.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
          }
        }
        data = bodyArray.join('&')
      }
    } else {
      headers[name] = header[name]
    }
  }

  if (!hasContentType && method === 'POST') {
    headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'
  }

  if (timeout) {
    abortTimeout = setTimeout(() => {
      aborted = true
      publishStateChange({
        requestTaskId,
        state: 'fail',
        statusCode: 0,
        errMsg: 'timeout'
      })
    }, (timeout + 200)) // TODO +200 发消息到原生层有时间开销，以后考虑由原生层回调超时
  }
  const options = {
    method,
    url: url.trim(),
    // weex 官方文档有误，headers 类型实际 object，用 string 类型会无响应
    headers,
    type: responseType === 'arraybuffer' ? 'base64' : 'text',
    // weex 官方文档未说明实际支持 timeout，单位：ms
    timeout: timeout || 6e5,
    // 配置和weex模块内相反
    sslVerify: !sslVerify,
    firstIpv4: firstIpv4,
    tls
  }
  let withArrayBuffer
  if (method !== 'GET') {
    if (toString.call(data) === '[object ArrayBuffer]') {
      withArrayBuffer = true
    } else {
      options.body = typeof data === 'string' ? data : JSON.stringify(data)
    }
  }
  const callback = ({
    ok,
    status,
    data,
    headers,
    errorMsg
  }) => {
    if (aborted) {
      return
    }
    if (abortTimeout) {
      clearTimeout(abortTimeout)
    }
    const statusCode = status
    if (statusCode > 0) {
      publishStateChange({
        requestTaskId,
        state: 'success',
        data: ok && responseType === 'arraybuffer' ? base64ToArrayBuffer(data) : data,
        statusCode,
        header: headers,
        cookies: cookiesParse(headers)
      })
    } else {
      let errMsg = 'abort statusCode:' + statusCode
      if (errorMsg) {
        errMsg = errMsg + ' ' + errorMsg
      }
      publishStateChange({
        requestTaskId,
        state: 'fail',
        statusCode,
        errMsg
      })
    }
  }
  try {
    if (withArrayBuffer) {
      stream.fetchWithArrayBuffer({
        '@type': 'binary',
        base64: arrayBufferToBase64(data)
      }, options, callback)
    } else {
      stream.fetch(options, callback)
    }
    requestTasks[requestTaskId] = {
      abort () {
        aborted = true
        if (abortTimeout) {
          clearTimeout(abortTimeout)
        }
        publishStateChange({
          requestTaskId,
          state: 'fail',
          statusCode: 0,
          errMsg: 'abort'
        })
      }
    }
  } catch (e) {
    return {
      requestTaskId,
      errMsg: 'createRequestTask:fail'
    }
  }
  return {
    requestTaskId,
    errMsg: 'createRequestTask:ok'
  }
}

export function createRequestTask (args) {
  return createRequestTaskById(++requestTaskId, args)
}

export function operateRequestTask ({
  requestTaskId,
  operationType
} = {}) {
  const requestTask = requestTasks[requestTaskId]
  if (requestTask && operationType === 'abort') {
    requestTask.abort()
    return {
      errMsg: 'operateRequestTask:ok'
    }
  }
  return {
    errMsg: 'operateRequestTask:fail'
  }
}

export function configMTLS ({ certificates }, callbackId) {
  const stream = requireNativePlugin('stream')
  stream.configMTLS(certificates, ({ type, code, message }) => {
    switch (type) {
      case 'success':
        invoke(callbackId, {
          errMsg: 'configMTLS:ok',
          code
        })
        break
      case 'fail':
        invoke(callbackId, {
          errMsg: 'configMTLS:fail ' + message,
          code
        })
        break
    }
  })
}
