import { BREADCRUMBCATEGORYS, BrowserBreadcrumbTypes, ErrorTypes } from '@sdc-monitor/shared'
import { getTimestamp, getUrlWithEnv } from './helpers'
import { Severity } from './Severity'

/**
 * 返回包含id、class、innerTextde字符串的标签
 * @param target html节点
 */
export function htmlElementAsString(target: HTMLElement): string {
  const tagName = target.tagName.toLowerCase()
  if (tagName === 'body') {
    return null
  }
  let classNames = target.classList.value
  classNames = classNames !== '' ? ` class="${classNames}"` : ''
  const id = target.id ? ` id="${target.id}"` : ''
  const innerText = target.innerText
  return `<${tagName}${id}${classNames !== '' ? classNames : ''}>${innerText}</${tagName}>`
}

/**
 * 将地址字符串转换成对象
 * @returns 返回一个对象
 */
export function parseUrlToObj(url: string): {
  host?: string
  path?: string
  protocol?: string
  relative?: string
} {
  if (!url) {
    return {}
  }

  // eslint-disable-next-line no-useless-escape
  const match = url.match(/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/)

  if (!match) {
    return {}
  }

  const query = match[6] || ''
  const fragment = match[8] || ''
  return {
    host: match[4],
    path: match[5],
    protocol: match[2],
    relative: match[5] + query + fragment // everything minus origin
  }
}

/**
 * 根据browser行为类型返回种类
 *
 * @export
 * @param {BrowserBreadcrumbTypes} type
 * @return {*}  {BREADCRUMBCATEGORYS}
 */
export function getBreadcrumbCategoryInBrowser(type: BrowserBreadcrumbTypes): BREADCRUMBCATEGORYS {
  switch (type) {
    case BrowserBreadcrumbTypes.XHR:
    case BrowserBreadcrumbTypes.FETCH:
      return BREADCRUMBCATEGORYS.HTTP
    case BrowserBreadcrumbTypes.CLICK:
    case BrowserBreadcrumbTypes.ROUTE:
      return BREADCRUMBCATEGORYS.USER
    case BrowserBreadcrumbTypes.CUSTOMER:
    case BrowserBreadcrumbTypes.CONSOLE:
      return BREADCRUMBCATEGORYS.DEBUG
    case BrowserBreadcrumbTypes.UNHANDLEDREJECTION:
    case BrowserBreadcrumbTypes.CODE_ERROR:
    case BrowserBreadcrumbTypes.RESOURCE:
    default:
      return BREADCRUMBCATEGORYS.EXCEPTION
  }
}

/**
 * 根据时间戳，返回年月日时分秒
 * @param timestamp
 */
export function timestampToTime(timestamp: number): string {
  // 时间戳为10位需*1000，时间戳为13位不需乘1000
  var date = new Date(timestamp);
  var Y = date.getFullYear() + "-";
  var M =
    (date.getMonth() + 1 < 10 ?
      "0" + (date.getMonth() + 1) :
      date.getMonth() + 1) + "-";
  var D = (date.getDate() < 10 ? "0" + date.getDate() : date.getDate()) + " ";
  var h = (date.getHours() < 10 ? "0" + date.getHours() : date.getHours()) + ":";
  var m = (date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes()) + ":";
  var s = (date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds());
  return Y + M + D + h + m + s;
}

/**
 * 根据当前入参，处理为接口需要的入参，并返回
 * @param data
 */
export function transformParams(data: any): object {
  // 如果不属于这些类型，则阻止上报
  const type = ['HTTP', 'RESOURCE', 'JAVASCRIPT', 'LOG', 'PROMISE', 'VUE'];
  if (!(data.data.type && type.includes(data.data.type)))
    return;
  // 错误等级转换
  const obj = {
    low: 'INFO',
    normal: 'WARN',
    high: 'ERROR',
    critical: 'ERROR'
  };
  const params = {
    "level": obj[data.data.level],
    "authInfo": {
      "sdkVersion": data.authInfo.sdkVersion,
      "apikey": data.authInfo.apikey
    },
    "time": timestampToTime(data.data.time),
    "type": data.data.type,
    "url": data.data.url
  };
  const errData = new Object();
  // 请求的入参
  const request = data.data.request;
  const response = data.data.response;
  if (data.data.type === 'HTTP') {
    // 目前请求只需要这些信息
    errData['http'] = {
      "request": {
        "httpType": request.httpType,
        "data": request.data || null,
        "method": request.method,
        "url": request.url
      },
      "response": {
        "data": response.data,
        "status": response.status
      }
    };
  }
  // 错误栈入参
  const stack = data.data.stack;
  let transformStack = new Array();
  if (data.data.stack) {
    // vue类型的错误目前是string,需要手动提取错误栈
    if (data.data.type === 'VUE' && typeof stack === 'string') {
      transformStack = extractErrorStack({ stack: stack }, data.data.level)['stack'];
    } else {
      transformStack = stack;
    }
    // 错误栈，目前只需要这四个信息
    errData['stack'] = transformStack.map((i) => {
      return {
        "func": i.func,
        "line": i.line,
        "column": i.column,
        "message": data.data.message
      };
    });
  }
  params['data'] = errData;
  return params;
}

/**
 * 解析error的stack，并返回args、column、line、func、url:
 * wx AppOnError use too
 * @param ex
 * @param level
 */
export function extractErrorStack(ex: any, level: Severity) {
  const normal = {
    time: getTimestamp(),
    url: getUrlWithEnv(),
    name: ex.name,
    level,
    message: ex.message
  }
  if (typeof ex.stack === 'undefined' || !ex.stack) {
    return normal
  }

  const chrome =
    /^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|webpack|<anonymous>|[a-z]:|\/).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i,
    gecko =
      /^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|webpack|resource|\[native).*?|[^@]*bundle)(?::(\d+))?(?::(\d+))?\s*$/i,
    winjs = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i,
    // Used to additionally parse URL/line/column from eval frames
    geckoEval = /(\S+) line (\d+)(?: > eval line \d+)* > eval/i,
    chromeEval = /\((\S*)(?::(\d+))(?::(\d+))\)/,
    lines = ex.stack.split('\n'),
    stack = []

  let submatch, parts, element
  // reference = /^(.*) is undefined$/.exec(ex.message)

  for (let i = 0, j = lines.length; i < j; ++i) {
    if ((parts = chrome.exec(lines[i]))) {
      const isNative = parts[2] && parts[2].indexOf('native') === 0 // start of line
      const isEval = parts[2] && parts[2].indexOf('eval') === 0 // start of line
      if (isEval && (submatch = chromeEval.exec(parts[2]))) {
        // throw out eval line/column and use top-most line/column number
        parts[2] = submatch[1] // url
        parts[3] = submatch[2] // line
        parts[4] = submatch[3] // column
      }
      element = {
        url: !isNative ? parts[2] : null,
        func: parts[1] || ErrorTypes.UNKNOWN_FUNCTION,
        args: isNative ? [parts[2]] : [],
        line: parts[3] ? +parts[3] : null,
        column: parts[4] ? +parts[4] : null
      }
    } else if ((parts = winjs.exec(lines[i]))) {
      element = {
        url: parts[2],
        func: parts[1] || ErrorTypes.UNKNOWN_FUNCTION,
        args: [],
        line: +parts[3],
        column: parts[4] ? +parts[4] : null
      }
    } else if ((parts = gecko.exec(lines[i]))) {
      const isEval = parts[3] && parts[3].indexOf(' > eval') > -1
      if (isEval && (submatch = geckoEval.exec(parts[3]))) {
        // throw out eval line/coluqqqqqqqqqqqqqqqqqqqqqqqqqqqqmn and use top-most line number
        parts[3] = submatch[1]
        parts[4] = submatch[2]
        parts[5] = null // no column when eval
      } else if (i === 0 && !parts[5] && typeof ex.columnNumber !== 'undefined') {
        // FireFox uses this awesome columnNumber property for its top frame
        // Also note, Firefox's column number is 0-based and everything else expects 1-based,
        // so adding 1
        // NOTE: this hack doesn't work if top-most frame is eval
        stack[0].column = ex.columnNumber + 1
      }
      element = {
        url: parts[3],
        func: parts[1] || ErrorTypes.UNKNOWN_FUNCTION,
        args: parts[2] ? parts[2].split(',') : [],
        line: parts[4] ? +parts[4] : null,
        column: parts[5] ? +parts[5] : null
      }
    } else {
      continue
    }

    if (!element.func && element.line) {
      element.func = ErrorTypes.UNKNOWN_FUNCTION
    }

    stack.push(element)
  }

  if (!stack.length) {
    return null
  }
  return {
    ...normal,
    stack: stack
  }
}
