import { utils } from '@cloudbase/utilities'
import { ReadableStream } from 'web-streams-polyfill'
import { ERROR, checkCustomUserIdRegex } from './constants'
import { lookupAppId } from './metadata'

/** 从 @cloudbase/utilities 中提取的表单和查询字符串工具 */
export const { isFormData, toQueryString } = utils as {
  isFormData: (obj: unknown) => boolean
  toQueryString: (data: Record<string, any>) => string
}

/**
 * 跨端安全获取环境变量
 * Web 端、小游戏端等没有 process 全局对象，直接访问 process.env 会报错
 */
export function getEnv(): Record<string, string | undefined>
export function getEnv(key: string): string | undefined
export function getEnv(key?: string): string | undefined | Record<string, string | undefined> {
  if (typeof process === 'undefined' || !process.env) {
    return key !== undefined ? undefined : {}
  }
  return key !== undefined ? process.env[key] : process.env
}

/** 判断当前是否在 SCF（云函数）运行环境中 */
export const checkIsInScf = () => getEnv('TENCENTCLOUD_RUNENV') === 'SCF'

export function checkIsInCBR() {
  // CBR = CLOUDBASE_RUN
  return !!getEnv('CBR_ENV_ID')
}

const kSumeruEnvSet = new Set(['formal', 'pre', 'test'])
export function checkIsInSumeru() {
  // SUMERU_ENV=formal | test | pre
  return kSumeruEnvSet.has(getEnv('SUMERU_ENV') || '')
}

export function isNonEmptyString(str: string) {
  return typeof str === 'string' && str !== ''
}

export async function checkIsInTencentCloud(): Promise<boolean> {
  if (getEnv('TENCENTCLOUD') === 'true') {
    return true
  }
  return isNonEmptyString(await lookupAppId())
}

export const getCurrRunEnvTag = async (): Promise<string> => {
  if (checkIsInScf()) {
    return 'scf'
  } if (checkIsInCBR()) {
    return 'cbr'
  } if (checkIsInSumeru()) {
    return 'sumeru'
  } if (await checkIsInTencentCloud()) {
    return 'tencentcloud'
  }
  return 'unknown'
}

/**
 * 校验自定义用户 ID 格式
 * @throws 格式不合法时抛出 INVALID_PARAM 错误
 */
export const validateUid = (uid: string): void => {
  if (typeof uid !== 'string') {
    throw { ...ERROR.INVALID_PARAM, message: 'uid must be a string' }
  }
  if (!checkCustomUserIdRegex.test(uid)) {
    throw { ...ERROR.INVALID_PARAM, message: `Invalid uid: "${uid}"` }
  }
}

/**
 * 安全的 JSON 解析，解析失败时返回原始文本而非抛异常
 * @param text - 待解析的字符串
 * @returns 解析后的对象，空字符串返回 null，解析失败返回原始文本
 */
export function safeParseJson(text: string): any {
  if (!text || !text.trim()) {
    return null
  }
  try {
    return JSON.parse(text)
  } catch (e) {
    console.warn('catch an error', { e, text })
    return text
  }
}

/**
 * 将对象的所有值转为字符串类型
 * 用于将 headers 等对象统一为 Record<string, string>
 */
export function obj2StrRecord(obj: object): Record<string, string> {
  return Object.entries(obj).reduce<Record<string, string>>((acc, cur: [string, unknown]) => {
    const [key, value] = cur
    acc[key] = String(value)
    return acc
  }, {})
}

/**
 * 将 HeadersInit 转为可索引的普通对象
 * 兼容 Headers 实例和普通对象两种形式
 */
export function headersInit2Indexable(h: HeadersInit) {
  if (isHeaders(h)) {
    const ret: Record<string, string> = {}
    h.forEach((val, key) => {
      ret[key] = val
    })
    return ret
  }
  return h

  function isHeaders(h: HeadersInit): h is Headers {
    try {
      // Node 低版本可能没有全局 Headers
      return h instanceof Headers
    } catch (_) {
      return false
    }
  }
}

/**
 * 手动解析查询字符串，避免 URLSearchParams 的兼容性问题
 * 支持重复 key 自动转为数组
 * @param search - 查询字符串，可带前导 ? 或 #
 */
export function parseQueryString(search: string): Record<string, string | string[]> {
  const params: any = {}
  const cleanSearch = search.replace(/^[?#]/, '')

  if (!cleanSearch) {
    return params
  }

  const pairs = cleanSearch.split('&')

  pairs.forEach((item) => {
    let [key, value] = item.split('=')
    key = decodeURIComponent(key)
    value = decodeURIComponent(value || '')
    if (key) {
      if (params[key]) {
        // 已存在同名 key，转为数组
        if (Array.isArray(params[key])) {
          params[key].push(value)
        } else {
          params[key] = [params[key], value]
        }
      } else {
        params[key] = value
      }
    }
  })
  return params
}

/**
 * 将 Node ReadableStream 转换为 Web ReadableStream (polyfill)
 * 用于 fetch 流式响应在 Node 环境下的适配
 */
export function createWebStreamFromNodeReadableStream(stream: NodeJS.ReadableStream): ReadableStream {
  const asyncIterator = stream[Symbol.asyncIterator]()

  return new ReadableStream({
    async pull(controller) {
      const next = await asyncIterator.next()

      if (next.done) {
        controller.close()
      } else {
        controller.enqueue(next.value)
      }
    },
  })
}
