import TencentCloudSDKHttpException from "./exception/tencent_cloud_sdk_exception"
import * as crypto from "crypto"
import { URL } from "url"
import * as JSONBigInt from "json-bigint"

const JSONbigNative = JSONBigInt({ useNativeBigInt: true })

/**
 * @inner
 */
export default class Sign {
  static sign(secretKey: string, signStr: string, signMethod: string): string {
    const signMethodMap: {
      [key: string]: string
    } = {
      HmacSHA1: "sha1",
      HmacSHA256: "sha256",
    }

    if (!signMethodMap.hasOwnProperty(signMethod)) {
      throw new TencentCloudSDKHttpException(
        "signMethod invalid, signMethod only support (HmacSHA1, HmacSHA256)"
      )
    }
    const hmac = crypto.createHmac(signMethodMap[signMethod], secretKey || "")
    return hmac.update(Buffer.from(signStr, "utf8")).digest("base64")
  }

  static sign3({
    method = "POST",
    url = "",
    payload,
    timestamp,
    service,
    secretId,
    secretKey,
    multipart,
    boundary,
    headers: configHeaders = {},
  }: {
    method?: string
    url?: string
    payload: any
    timestamp: number
    service: string
    secretId: string
    secretKey: string
    multipart: boolean
    boundary: string
    headers: Record<string, string>
  }): string {
    const urlObj = new URL(url)
    const contentType = configHeaders["Content-Type"]

    // 通用头部
    let headers = ""
    let signedHeaders = ""
    if (method === "GET") {
      signedHeaders = "content-type"
      headers = `content-type:${contentType}\n`
    } else if (method === "POST") {
      signedHeaders = "content-type"
      if (multipart) {
        headers = `content-type:multipart/form-data; boundary=${boundary}\n`
      } else {
        headers = `content-type:${contentType}\n`
      }
    }
    headers += `host:${urlObj.hostname}\n`
    signedHeaders += ";host"

    const path = urlObj.pathname
    const querystring = urlObj.search.slice(1)

    let payload_hash = ""
    if (multipart) {
      const hash = crypto.createHash("sha256")
      hash.update(`--${boundary}`)
      for (const key in payload) {
        const content = payload[key]
        if (Buffer.isBuffer(content)) {
          hash.update(
            `\r\nContent-Disposition: form-data; name="${key}"\r\nContent-Type: application/octet-stream\r\n\r\n`
          )
          hash.update(content)
          hash.update("\r\n")
        } else if (typeof content === "string") {
          hash.update(`\r\nContent-Disposition: form-data; name="${key}"\r\n\r\n`)
          hash.update(`${content}\r\n`)
        }
        hash.update(`--${boundary}`)
      }
      hash.update(`--\r\n`)
      payload_hash = hash.digest("hex")
    } else {
      const hashMessage = Buffer.isBuffer(payload) ? payload : JSONbigNative.stringify(payload)
      payload_hash = payload ? getHash(hashMessage) : getHash("")
    }

    const canonicalRequest =
      method +
      "\n" +
      path +
      "\n" +
      querystring +
      "\n" +
      headers +
      "\n" +
      signedHeaders +
      "\n" +
      payload_hash
    const date = getDate(timestamp)

    const StringToSign =
      "TC3-HMAC-SHA256" +
      "\n" +
      timestamp +
      "\n" +
      `${date}/${service}/tc3_request` +
      "\n" +
      getHash(canonicalRequest)

    const kDate = sha256(date, "TC3" + secretKey)
    const kService = sha256(service, kDate)
    const kSigning = sha256("tc3_request", kService)
    const signature = sha256(StringToSign, kSigning, "hex")

    return `TC3-HMAC-SHA256 Credential=${secretId}/${date}/${service}/tc3_request, SignedHeaders=${signedHeaders}, Signature=${signature}`
  }
}

function sha256(message: string, secret = "", encoding?: string): string {
  const hmac = crypto.createHmac("sha256", secret)
  return hmac.update(message).digest(encoding as any)
}

function getHash(message: crypto.BinaryLike, encoding = "hex"): string {
  const hash = crypto.createHash("sha256")
  return hash.update(message).digest(encoding as any)
}

function getDate(timestamp: number): string {
  const date = new Date(timestamp * 1000)
  const year = date.getUTCFullYear()
  const month = ("0" + (date.getUTCMonth() + 1)).slice(-2)
  const day = ("0" + date.getUTCDate()).slice(-2)
  return `${year}-${month}-${day}`
}
