import './env.mjs'

let cachedAccessToken = null
let cachedAccessTokenExpiresAt = 0

function miniappConfig() {
  return {
    appId: process.env.MINIAPP_WECHAT_APP_ID || process.env.WECHAT_MINIAPP_APP_ID || process.env.WECHAT_APP_ID || '',
    appSecret: process.env.MINIAPP_WECHAT_APP_SECRET || process.env.WECHAT_MINIAPP_APP_SECRET || process.env.WECHAT_APP_SECRET || '',
    timeoutMs: Math.max(1000, Number(process.env.MINIAPP_WECHAT_TIMEOUT_MS || 8000)),
    qrPage: process.env.MINIAPP_PC_LOGIN_PAGE || 'pages/auth/login/index',
    envVersion: process.env.MINIAPP_ENV_VERSION || 'trial',
  }
}

function providerError(message, code = 'miniapp_wechat_failed', statusCode = 502, details = {}) {
  const error = new Error(message)
  error.statusCode = statusCode
  error.code = code
  error.details = details
  return error
}

function assertConfigured(config) {
  if (!config.appId || !config.appSecret) {
    throw providerError('小程序微信配置不完整，请配置 MINIAPP_WECHAT_APP_ID 和 MINIAPP_WECHAT_APP_SECRET', 'miniapp_wechat_not_configured', 503)
  }
}

async function fetchJson(url, options = {}, timeoutMs = 8000) {
  const controller = new AbortController()
  const timer = setTimeout(() => controller.abort(), timeoutMs)
  let response
  try {
    response = await fetch(url, { ...options, signal: controller.signal })
  } catch (error) {
    const timedOut = error.name === 'AbortError'
    throw providerError(timedOut ? '微信小程序网关请求超时' : '微信小程序网关连接失败', timedOut ? 'miniapp_wechat_timeout' : 'miniapp_wechat_unreachable', 502, {
      cause: error.message,
    })
  } finally {
    clearTimeout(timer)
  }

  const text = await response.text()
  let payload
  try {
    payload = text ? JSON.parse(text) : {}
  } catch {
    throw providerError('微信小程序网关返回异常', 'miniapp_wechat_bad_response', 502, { status: response.status })
  }

  if (!response.ok || payload.errcode) {
    throw providerError(payload.errmsg || '微信小程序接口请求失败', 'miniapp_wechat_provider_error', 502, {
      providerCode: payload.errcode || '',
    })
  }

  return payload
}

export function miniappAppId() {
  return miniappConfig().appId
}

export async function code2Session(jsCode) {
  const config = miniappConfig()
  assertConfigured(config)
  const code = String(jsCode || '').trim()
  if (!code) {
    const error = new Error('缺少小程序登录 code')
    error.statusCode = 400
    error.code = 'missing_miniapp_code'
    throw error
  }

  const url = new URL('https://api.weixin.qq.com/sns/jscode2session')
  url.searchParams.set('appid', config.appId)
  url.searchParams.set('secret', config.appSecret)
  url.searchParams.set('js_code', code)
  url.searchParams.set('grant_type', 'authorization_code')
  const payload = await fetchJson(url, {}, config.timeoutMs)
  if (!payload.openid) {
    throw providerError('微信小程序登录失败，未返回 openid', 'miniapp_openid_missing', 502)
  }
  return {
    appId: config.appId,
    openid: payload.openid,
    unionid: payload.unionid || '',
    sessionKey: payload.session_key || '',
  }
}

async function getAccessToken() {
  const config = miniappConfig()
  assertConfigured(config)
  if (cachedAccessToken && cachedAccessTokenExpiresAt > Date.now() + 60_000) return cachedAccessToken

  const url = new URL('https://api.weixin.qq.com/cgi-bin/token')
  url.searchParams.set('grant_type', 'client_credential')
  url.searchParams.set('appid', config.appId)
  url.searchParams.set('secret', config.appSecret)
  const payload = await fetchJson(url, {}, config.timeoutMs)
  cachedAccessToken = payload.access_token
  cachedAccessTokenExpiresAt = Date.now() + Math.max(60, Number(payload.expires_in || 7200) - 120) * 1000
  return cachedAccessToken
}

export async function createPcLoginMiniappQr(scene) {
  const config = miniappConfig()
  assertConfigured(config)
  const accessToken = await getAccessToken()
  const url = new URL('https://api.weixin.qq.com/wxa/getwxacodeunlimit')
  url.searchParams.set('access_token', accessToken)

  const controller = new AbortController()
  const timer = setTimeout(() => controller.abort(), config.timeoutMs)
  let response
  try {
    response = await fetch(url, {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({
        scene,
        page: config.qrPage,
        check_path: false,
        env_version: config.envVersion,
      }),
      signal: controller.signal,
    })
  } catch (error) {
    const timedOut = error.name === 'AbortError'
    throw providerError(timedOut ? '小程序码生成超时' : '小程序码生成连接失败', timedOut ? 'miniapp_qr_timeout' : 'miniapp_qr_unreachable', 502, {
      cause: error.message,
    })
  } finally {
    clearTimeout(timer)
  }

  const contentType = response.headers.get('content-type') || ''
  const buffer = Buffer.from(await response.arrayBuffer())
  if (/application\/json/i.test(contentType)) {
    let payload
    try {
      payload = JSON.parse(buffer.toString('utf8'))
    } catch {
      payload = {}
    }
    throw providerError(payload.errmsg || '小程序码生成失败', 'miniapp_qr_failed', 502, {
      providerCode: payload.errcode || '',
    })
  }

  if (!response.ok) {
    throw providerError('小程序码生成失败', 'miniapp_qr_failed', 502, { status: response.status })
  }

  return {
    buffer,
    mimeType: contentType || 'image/png',
  }
}
