import crypto from 'crypto'

export interface ByteplusCredentialConfig {
  accessKeyId: string
  secretAccessKey: string
  region: string // e.g. ap-singapore-1
  service: string // e.g. rtc
}

export interface ByteplusSignParams {
  method: string
  url: string // full url like https://open.byteplusapi.com?Action=StartVoiceChat&Version=2025-03-01
  headers?: Record<string, string>
  body?: string
  datetimeISO8601Z?: string // optional override, e.g. 20201230T081805Z
}

function sha256Hex(content: string) {
  return crypto
    .createHash('sha256')
    .update(content || '', 'utf8')
    .digest('hex')
}

function hmac(key: Buffer | string, data: string) {
  return crypto.createHmac('sha256', key).update(data, 'utf8').digest()
}

function hmacHex(key: Buffer | string, data: string) {
  return crypto.createHmac('sha256', key).update(data, 'utf8').digest('hex')
}

function toISO8601Z(date: Date) {
  const iso = date.toISOString()
  // 2020-12-30T08:18:05.000Z -> 20201230T081805Z
  return (
    iso.slice(0, 4) +
    iso.slice(5, 7) +
    iso.slice(8, 10) +
    'T' +
    iso.slice(11, 13) +
    iso.slice(14, 16) +
    iso.slice(17, 19) +
    'Z'
  )
}

function getUrlParts(url: string) {
  const u = new URL(url)
  return {
    protocol: u.protocol,
    host: u.host,
    pathname: u.pathname || '/',
    // Build canonical query string: sort by key, then by value
    canonicalQuery: Array.from(u.searchParams.entries())
      .sort(([aKey, aVal], [bKey, bVal]) =>
        aKey === bKey ? aVal.localeCompare(bVal) : aKey.localeCompare(bKey)
      )
      .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
      .join('&'),
  }
}

export function signByteplusRequest(creds: ByteplusCredentialConfig, params: ByteplusSignParams) {
  const method = params.method.toUpperCase()
  const { protocol, host, pathname, canonicalQuery } = getUrlParts(params.url)
  const now = params.datetimeISO8601Z || toISO8601Z(new Date())
  const date = now.slice(0, 8) // YYYYMMDD

  // Prepare headers
  const headers: Record<string, string> = {
    host,
    'x-content-sha256': sha256Hex(params.body || ''),
    'x-date': now,
    ...(params.headers || {}),
  }

  // Lowercase header names and trim values
  const normalizedHeaders = Object.fromEntries(
    Object.entries(headers).map(([k, v]) => [k.toLowerCase().trim(), (v ?? '').toString().trim()])
  )

  // Canonical headers: sort by header name
  const sortedHeaderKeys = Object.keys(normalizedHeaders).sort()
  const canonicalHeaders =
    sortedHeaderKeys.map((k) => `${k}:${normalizedHeaders[k]}`).join('\n') + '\n'
  const signedHeaders = sortedHeaderKeys.join(';')

  const canonicalRequest = [
    method,
    pathname || '/',
    canonicalQuery,
    canonicalHeaders,
    signedHeaders,
    normalizedHeaders['x-content-sha256'],
  ].join('\n')

  const algorithm = 'HMAC-SHA256'
  const credentialScope = `${date}/${creds.region}/${creds.service}/request`
  const stringToSign = [algorithm, now, credentialScope, sha256Hex(canonicalRequest)].join('\n')

  // Derive signing key (per official example)
  const kDate = hmac(creds.secretAccessKey, date)
  const kRegion = hmac(kDate, creds.region)
  const kService = hmac(kRegion, creds.service)
  const kSigning = hmac(kService, 'request')
  const signature = hmacHex(kSigning, stringToSign)

  const authorization = `${algorithm} Credential=${creds.accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`

  return {
    headers: {
      Authorization: authorization,
      Host: host,
      'X-Content-Sha256': normalizedHeaders['x-content-sha256'],
      'X-Date': now,
      ...(params.headers || {}),
    },
    canonicalRequest,
    stringToSign,
    signature,
    url: `${protocol}//${host}${pathname}${canonicalQuery ? `?${canonicalQuery}` : ''}`,
  }
}
