import crypto from 'crypto'
import type { AppSecrets } from './app-secrets'
import { getSecretByIss } from './app-secrets'

/**
 * H5 JWT 验签工具（§5.1-5.2）。
 *
 * APP 后端用 Forge 生成的密钥签 JWT（HS256），payload 含 iss(app_id) + sub(uid) + name? + exp。
 * Forge 按 iss 从 app_secrets 取密钥验签。
 *
 * 约定：exp 强制 ≤ 2h（对齐 cookie 有效期），拒绝过期 JWT。
 */

export interface JwtPayload {
  iss: string // app_id
  sub: string // APP 内用户唯一 ID
  name?: string // 可选，用于 H5 页面显示
  iat?: number // 签发时间
  exp: number // 过期时间（必须 ≤ 签发 + 2h）
}

export class JwtVerifyError extends Error {
  constructor(
    message: string,
    public code: 'malformed' | 'bad_signature' | 'expired' | 'unknown_iss' | 'no_exp',
  ) {
    super(message)
    this.name = 'JwtVerifyError'
  }
}

/** JWT 最大有效期（秒），超过拒绝 */
const MAX_JWT_TTL_SECONDS = 2 * 60 * 60 // 2h

/**
 * 验签 JWT。
 *
 * @param token APP 后端签发的 JWT 字符串
 * @param appSecrets 该 H5 页面的 app_secrets JSON 解析结果
 * @throws JwtVerifyError 验签失败时抛出（malformed / bad_signature / expired / unknown_iss / no_exp）
 */
export function verifyJwt(token: string, appSecrets: AppSecrets): JwtPayload {
  const parts = token.split('.')
  if (parts.length !== 3) {
    throw new JwtVerifyError('Malformed JWT: expected 3 parts', 'malformed')
  }

  const [headerB64, payloadB64, signatureB64] = parts

  // 解码 payload 取 iss（先解码 header 确认 alg）
  let header: { alg?: string; typ?: string }
  let payload: JwtPayload
  try {
    header = JSON.parse(base64urlDecode(headerB64))
    payload = JSON.parse(base64urlDecode(payloadB64)) as JwtPayload
  } catch {
    throw new JwtVerifyError('Malformed JWT: failed to decode', 'malformed')
  }

  if (header.alg !== 'HS256') {
    throw new JwtVerifyError(`Unsupported alg: ${header.alg ?? 'none'}, expected HS256`, 'malformed')
  }

  if (!payload.iss || !payload.sub) {
    throw new JwtVerifyError('Missing iss or sub in payload', 'malformed')
  }

  // 按 iss 取密钥
  const secret = getSecretByIss(appSecrets, payload.iss)
  if (!secret) {
    throw new JwtVerifyError(`Unknown iss: ${payload.iss}`, 'unknown_iss')
  }

  // 验签
  const expectedSig = hmacSha256(`${headerB64}.${payloadB64}`, secret)
  const providedSig = base64urlToBase64(signatureB64)
  if (!crypto.timingSafeEqual(Buffer.from(expectedSig), Buffer.from(providedSig))) {
    throw new JwtVerifyError('Bad signature', 'bad_signature')
  }

  // 校验 exp
  if (!payload.exp || typeof payload.exp !== 'number') {
    throw new JwtVerifyError('Missing or invalid exp', 'no_exp')
  }

  const now = Math.floor(Date.now() / 1000)
  if (payload.exp <= now) {
    throw new JwtVerifyError('JWT expired', 'expired')
  }

  // 校验 TTL（签发到过期不能超过 2h）
  const iat = payload.iat ?? (payload.exp - MAX_JWT_TTL_SECONDS)
  if (payload.exp - iat > MAX_JWT_TTL_SECONDS) {
    throw new JwtVerifyError(
      `JWT TTL exceeds max (${MAX_JWT_TTL_SECONDS}s)`,
      'expired',
    )
  }

  return payload
}

/** HMAC-SHA256 签名，返回 base64 编码 */
function hmacSha256(data: string, secret: string): string {
  return crypto.createHmac('sha256', secret).update(data).digest('base64')
}

/** base64url 解码为 UTF-8 字符串 */
function base64urlDecode(b64url: string): string {
  const b64 = b64url.replace(/-/g, '+').replace(/_/g, '/')
  const padded = b64 + '='.repeat((4 - (b64.length % 4)) % 4)
  return Buffer.from(padded, 'base64').toString('utf-8')
}

/** base64url → base64（用于 timingSafeEqual 对比） */
function base64urlToBase64(b64url: string): string {
  const b64 = b64url.replace(/-/g, '+').replace(/_/g, '/')
  return b64 + '='.repeat((4 - (b64.length % 4)) % 4)
}
