import { generateSecret, generateURI, verifySync } from "otplib"
import QRCode from "qrcode"
import { hash, compare } from "bcryptjs"

/**
 * TOTP (Time-based One-Time Password) 服务
 *
 * 基于 otplib v13 函数式 API 实现，遵循 RFC 6238。
 * - 生成密钥 + otpauth URL + QR 码 Data URL
 * - 校验一次性码（允许 ±30s 窗口防止时钟漂移）
 * - 生成 + 哈希 + 校验备份码（10 个，8 位字母数字）
 */

const BACKUP_CODE_COUNT = 10
const BACKUP_CODE_LENGTH = 8
const BACKUP_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
const BCRYPT_ROUNDS = 10
/** TOTP 时间步长（秒） */
const TOTP_PERIOD = 30
/** 时钟漂移容差（秒），±30s 即 ±1 个窗口 */
const TOTP_TOLERANCE = 30

/**
 * 生成 TOTP 密钥（base32）
 */
export function generateTotpSecret(): string {
  return generateSecret()
}

/**
 * 构建 otpauth:// URL，用于二维码扫描
 */
export function buildOtpAuthUrl(params: {
  serviceName: string
  accountName: string
  secret: string
}): string {
  return generateURI({
    issuer: params.serviceName,
    label: params.accountName,
    secret: params.secret,
    period: TOTP_PERIOD,
  })
}

/**
 * 将 otpauth URL 转换为 QR 码 Data URL（base64 PNG）
 */
export async function generateQrCodeDataUrl(otpAuthUrl: string): Promise<string> {
  return QRCode.toDataURL(otpAuthUrl, {
    width: 240,
    margin: 1,
    errorCorrectionLevel: "M",
  })
}

/**
 * 校验 TOTP 一次性码
 *
 * 使用同步 API（NobleCryptoPlugin 支持 sync HMAC）。
 * epochTolerance=30 允许 ±1 个 30s 窗口。
 */
export function verifyTotpCode(token: string, secret: string): boolean {
  try {
    const result = verifySync({
      secret,
      token: token.replace(/\s+/g, ""),
      period: TOTP_PERIOD,
      epochTolerance: TOTP_TOLERANCE,
    })
    return result.valid
  } catch {
    return false
  }
}

/**
 * 生成一批备份码（明文，返回给用户展示一次）
 *
 * 格式：8 位字母数字，去除易混淆字符（0/O/1/I）
 */
export function generateBackupCodes(): string[] {
  const codes: string[] = []
  for (let i = 0; i < BACKUP_CODE_COUNT; i++) {
    let code = ""
    for (let j = 0; j < BACKUP_CODE_LENGTH; j++) {
      const idx = Math.floor(Math.random() * BACKUP_CODE_ALPHABET.length)
      code += BACKUP_CODE_ALPHABET[idx]
    }
    codes.push(code)
  }
  return codes
}

/**
 * 将备份码列表哈希为 JSON 数组字符串（存入 DB）
 */
export async function hashBackupCodes(codes: string[]): Promise<string> {
  const hashed = await Promise.all(codes.map((c) => hash(c, BCRYPT_ROUNDS)))
  return JSON.stringify(hashed)
}

/**
 * 校验用户输入的备份码是否匹配已存储的哈希列表。
 * 返回匹配的索引（>=0），未匹配返回 -1。
 *
 * 注意：bcrypt 比较较慢，10 个码串行约需 300ms，可接受。
 */
export async function verifyBackupCode(
  input: string,
  storedHashedJson: string,
): Promise<number> {
  let hashed: string[] = []
  try {
    hashed = JSON.parse(storedHashedJson) as string[]
    if (!Array.isArray(hashed)) return -1
  } catch {
    return -1
  }

  const normalized = input.trim().toUpperCase()
  for (let i = 0; i < hashed.length; i++) {
    const ok = await compare(normalized, hashed[i])
    if (ok) return i
  }
  return -1
}

/**
 * 从已存储的哈希列表中移除已使用的备份码，返回新的 JSON 字符串。
 */
export async function consumeBackupCode(
  storedHashedJson: string,
  usedIndex: number,
): Promise<string> {
  let hashed: string[] = []
  try {
    hashed = JSON.parse(storedHashedJson) as string[]
  } catch {
    return storedHashedJson
  }
  if (usedIndex < 0 || usedIndex >= hashed.length) return storedHashedJson
  const next = hashed.filter((_, i) => i !== usedIndex)
  return JSON.stringify(next)
}

/**
 * 统计剩余备份码数量
 */
export function countRemainingBackupCodes(storedHashedJson: string): number {
  try {
    const arr = JSON.parse(storedHashedJson) as unknown
    if (!Array.isArray(arr)) return 0
    return arr.length
  } catch {
    return 0
  }
}
