/* eslint-disable-next-line */
import { parse } from 'url'

import { sign } from '@cloudbase/signature-nodejs'

import { ICloudBaseConfig, ICustomReqOpts } from '../../types'
import { IRequestInfo, IReqOpts } from '../../types/internal'

import { SYMBOL_CURRENT_ENV } from '../const/symbol'
import { CloudBase } from '../cloudbase'

import { generateTracingInfo, ITracingInfo } from './tracing'
import { checkIsInScf, checkIsInternalAsync, getCurrRunEnvTag } from './cloudplatform'
import { buildUrl } from './tcbopenapiendpoint'
import { prepareCredentials } from './tcbapirequester'

import { extraRequest } from './request'

import { version } from './version'

import * as utils from './utils'

const { second } = utils

export function getEnvIdFromContext(): string {
  const { TCB_ENV, SCF_NAMESPACE } = CloudBase.getCloudbaseContext()
  return TCB_ENV || SCF_NAMESPACE || ''
}

type ICallContainerRequestInfo = Omit<IRequestInfo, 'params'> & {
  path: string
  data: any
  token?: string
  cloudrun: {
    name: string
    // version: string
  }
}

export class TcbOpenApiHttpRequester {
  private readonly args: ICallContainerRequestInfo
  private readonly config: ICloudBaseConfig
  private readonly opts: ICustomReqOpts
  private readonly defaultTimeout = 15000
  private readonly timestamp: number = new Date().valueOf()
  private readonly tracingInfo: ITracingInfo

  /* eslint-disable no-undef */
  private readonly slowWarnTimer: NodeJS.Timer = null
  /* eslint-enable no-undef */

  public constructor(args: ICallContainerRequestInfo) {
    this.args = args
    this.config = args.config
    this.opts = args.opts || {}
    this.tracingInfo = generateTracingInfo(args.config?.context?.eventID)
  }

  public async request(): Promise<any> {
    if (!this.config.accessKey) {
      await this.prepareCredentials()
    }

    const opts = this.makeReqOpts()
    // console.log('opts', opts)

    const argopts: any = this.opts
    const config = this.config

    // 注意：必须初始化为 null
    let retryOptions: any = null
    if (argopts.retryOptions) {
      retryOptions = argopts.retryOptions
    } else if (config.retries && typeof config.retries === 'number') {
      retryOptions = { retries: config.retries }
    }

    return await extraRequest(opts, {
      debug: config.debug,
      op: `${opts.method}:${opts.url}`,
      seqId: this.tracingInfo.seqId,
      retryOptions,
      timingsMeasurerOptions: config.timingsMeasurerOptions || {}
    }).then((response: any) => {
      this.slowWarnTimer && clearTimeout(this.slowWarnTimer)
      return response
    })
  }

  private makeReqOpts(): IReqOpts {
    const config = this.config
    const args = this.args

    const envId = args.config.envName === SYMBOL_CURRENT_ENV
      ? getEnvIdFromContext()
      : (args.config.envName as string)

    const url = buildUrl({
      envId,
      region: this.config.region,
      // protocol: this.config.protocol || 'https',
      // serviceUrl: this.config.serviceUrl,
      // seqId: this.tracingInfo.seqId,
      // isInternal: this.args.isInternal,
      name: args.cloudrun.name,
      path: args.path
    })

    const timeout = this.args.opts?.timeout || this.config.timeout || this.defaultTimeout

    const opts: IReqOpts = {
      url,
      method: args.method,
      timeout,
      headers: this.buildHeaders(args.method, url),
      proxy: config.proxy
    }

    if (typeof config.keepalive === 'undefined' && !checkIsInScf()) {
      // 非云函数环境下，默认开启 keepalive
      opts.keepalive = true
    } else {
      /** eslint-disable-next-line */
      opts.keepalive = typeof config.keepalive === 'boolean' && config.keepalive
    }

    if (args.data) {
      if (args.method.toLowerCase() === 'post') {
        if (args.isFormData) {
          opts.formData = args.data
          opts.encoding = null
        } else {
          opts.body = args.data
          opts.json = true
        }
      } else {
        /* istanbul ignore next */
        // 这里 qs 参数暂时并没使用
        opts.qs = args.data
      }
    } else {
      opts.noBody = true
    }

    return opts
  }

  private async prepareCredentials(): Promise<void> {
    prepareCredentials.bind(this)()
  }

  private buildHeaders(method: string, url: string): any {
    const config = this.config
    const { context, secretId, secretKey, sessionToken, accessKey } = config
    const args = this.args

    const { TCB_SOURCE } = CloudBase.getCloudbaseContext()
    // Note: 云函数被调用时可能调用端未传递 SOURCE，TCB_SOURCE 可能为空
    const SOURCE = `${context?.extendedContext?.source || TCB_SOURCE || ''},${args.opts.runEnvTag}`

    // 注意：因为 url.parse 和 url.URL 存在差异，因 url.parse 已被废弃，这里可能会需要改动。
    // 因 @cloudbase/signature-nodejs sign 方法目前内部使用 url.parse 解析 url，
    // 如果这里需要改动，需要注意与 @cloudbase/signature-nodejs 的兼容性
    // 否则将导致签名存在问题
    const parsedUrl = parse(url)
    // const parsedUrl = new URL(url)

    let requiredHeaders = {
      'User-Agent': `tcb-node-sdk/${version}`,
      'X-TCB-Source': SOURCE,
      'X-Client-Timestamp': this.timestamp,
      'X-SDK-Version': `tcb-node-sdk/${version}`,
      Host: parsedUrl.host
    }

    if (config.version) {
      requiredHeaders['X-SDK-Version'] = config.version
    }

    if (this.tracingInfo.trace) {
      requiredHeaders['X-TCB-Tracelog'] = this.tracingInfo.trace
    }

    const region = this.config.region || process.env.TENCENTCLOUD_REGION || ''

    if (region) {
      requiredHeaders['X-TCB-Region'] = region
    }

    requiredHeaders = { ...config.headers, ...args.headers, ...requiredHeaders }

    let params = args.data || ''

    // GET 方法不支持传 BODY
    if (method.toLowerCase() === 'get') {
      params = ''
    }

    // TODO: 升级SDK版本，否则没传 args.data 时会签名失败
    const { authorization, timestamp } = sign({
      secretId,
      secretKey,
      method,
      url,
      params,
      headers: requiredHeaders,
      timestamp: second() - 1,
      withSignedParams: false,
      isCloudApi: true
    })
    let token = ''
    // 如果请求参数里面传了 token，优先使用 token
    if (args.token) {
      token = makeBearerToken(args.token)
    } else if (accessKey) {
      // 如果配置了 API_KEY，优先使用 API_KEY
      token = makeBearerToken(accessKey)
    } else if (typeof sessionToken === 'string' && sessionToken !== '') {
      // 如果配置了 sessionToken，使用 sessionToken
      token = `${authorization}, Timestamp=${timestamp}, Token=${sessionToken}`
    } else {
      token = `${authorization}, Timestamp=${timestamp}`
    }
    /* eslint-disable @typescript-eslint/dot-notation */
    requiredHeaders['Authorization'] = token

    return { ...requiredHeaders }
  }
}

export async function request(args: ICallContainerRequestInfo & { path: string }): Promise<any> {
  if (typeof args.isInternal === 'undefined') {
    args.isInternal = await checkIsInternalAsync()
  }
  args.opts = args.opts || {}
  args.opts.runEnvTag = await getCurrRunEnvTag()

  const requester = new TcbOpenApiHttpRequester(args)

  return await requester.request()
}
function makeBearerToken(token: string): string {
  const trimmed = token.trim()
  return trimmed.startsWith('Bearer ') ? trimmed : `Bearer ${trimmed}`
}
