import "server-only"

/**
 * Breached password detection via Have I Been Pwned API (audit-P2-4).
 *
 * 使用 k-anonymity 范围查询：仅发送 SHA-1 哈希前 5 个字符到 HIBP API，
 * API 返回该前缀下所有泄露密码的哈希后缀 + 出现次数。本地比对完整哈希后缀，
 * 从而在不泄露用户密码的前提下判断是否为已泄露密码。
 *
 * 安全设计：
 * - 仅发送 5 字符前缀，HIBP 无法得知用户查询的完整密码哈希
 * - 本地完成完整哈希比对，不发送完整哈希
 * - API 失败时 fail-open（允许密码），避免外部依赖阻断注册流程
 * - 带 3 秒超时 + 1 次重试，避免长时间阻塞
 *
 * 集成点：
 * - `modules/auth/actions.ts` registerAction：注册时校验
 * - `modules/settings/actions-password.ts` changePasswordAction：改密时校验
 */

const HIBP_API_URL = "https://api.pwnedpasswords.com/range"
const REQUEST_TIMEOUT_MS = 3000

export interface BreachedPasswordResult {
  /** true 表示此密码已在已知数据泄露中出现，不应使用 */
  isBreached: boolean
  /** 该密码在 HIBP 数据库中被泄露的次数（isBreached=false 时为 0） */
  breachCount: number
  /** true 表示因 API 不可用等原因无法完成检查（fail-open，允许通过） */
  checkSkipped: boolean
  /** checkSkipped=true 时的原因 */
  reason?: string
}

/**
 * 计算 SHA-1 哈希（hex 格式，全大写）。
 * 使用 Web Crypto API（Node.js / Edge runtime 均可用）。
 */
async function sha1Hex(input: string): Promise<string> {
  const encoder = new TextEncoder()
  const data = encoder.encode(input)
  const hashBuffer = await crypto.subtle.digest("SHA-1", data)
  const hashArray = Array.from(new Uint8Array(hashBuffer))
  return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("").toUpperCase()
}

/**
 * 带 timeout 的 fetch（AbortController）。
 */
async function fetchWithTimeout(url: string, timeoutMs: number): Promise<Response> {
  const controller = new AbortController()
  const timer = setTimeout(() => controller.abort(), timeoutMs)
  try {
    return await fetch(url, {
      signal: controller.signal,
      headers: { "User-Agent": "NextEdu-PasswordCheck/1.0" },
    })
  } finally {
    clearTimeout(timer)
  }
}

/**
 * 查询 HIBP API 获取指定前缀下的所有泄露哈希后缀。
 * 返回 Map<suffix, count>。
 */
async function queryHibpRange(prefix: string): Promise<Map<string, number>> {
  const response = await fetchWithTimeout(`${HIBP_API_URL}/${prefix}`, REQUEST_TIMEOUT_MS)
  if (!response.ok) {
    throw new Error(`HIBP API returned ${response.status}`)
  }
  const text = await response.text()
  const result = new Map<string, number>()
  for (const line of text.split("\n")) {
    const trimmed = line.trim()
    if (!trimmed) continue
    const colonIdx = trimmed.indexOf(":")
    if (colonIdx === -1) continue
    const suffix = trimmed.slice(0, colonIdx)
    const count = parseInt(trimmed.slice(colonIdx + 1), 10)
    if (!Number.isNaN(count)) {
      result.set(suffix, count)
    }
  }
  return result
}

/**
 * 检查密码是否在已知数据泄露中出现。
 *
 * k-anonymity 流程：
 * 1. 计算 SHA-1 哈希
 * 2. 取前 5 字符作为前缀查询 HIBP API
 * 3. API 返回该前缀下所有泄露后缀 + 次数
 * 4. 本地比对完整哈希的后缀（第 6 字符起）
 *
 * 失败策略（fail-open）：
 * - API 不可用 / 超时 / 返回错误 → 返回 checkSkipped=true，允许密码通过
 * - 不应因第三方服务故障阻断用户注册/改密
 */
export async function checkBreachedPassword(
  password: string,
): Promise<BreachedPasswordResult> {
  const fullHash = await sha1Hex(password)
  const prefix = fullHash.slice(0, 5)
  const suffix = fullHash.slice(5)

  try {
    const rangeMap = await queryHibpRange(prefix)
    const count = rangeMap.get(suffix) ?? 0
    return {
      isBreached: count > 0,
      breachCount: count,
      checkSkipped: false,
    }
  } catch (error) {
    const reason = error instanceof Error ? error.message : "unknown error"
    return {
      isBreached: false,
      breachCount: 0,
      checkSkipped: true,
      reason: `HIBP API unavailable: ${reason}`,
    }
  }
}
