// OAuth 加密工具：PKCE S256 驗證、SHA-256 hash、隨機不可猜 token、常數時間比對。
// 全走 Web Crypto（Workers / Node 18+ 全域 crypto.subtle），無外部依賴。

/** base64url 編碼（無 padding，RFC 7636 §A）。 */
function base64UrlEncode(bytes: Uint8Array): string {
  let bin = "";
  for (const b of bytes) bin += String.fromCharCode(b);
  return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}

/** 產生密碼學等級隨機 token（bytes 位元組 → base64url 字串，預設 32 bytes = 256-bit）。 */
export function randomToken(bytes = 32): string {
  const buf = new Uint8Array(bytes);
  crypto.getRandomValues(buf);
  return base64UrlEncode(buf);
}

/** SHA-256 → base64url（PKCE code_challenge 用；RFC 7636 §4.2 BASE64URL(SHA256(verifier))）。 */
export async function sha256Base64Url(input: string): Promise<string> {
  const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input));
  return base64UrlEncode(new Uint8Array(digest));
}

/** SHA-256 → hex（拿來當 KV key：不把可用的 raw token 直接當 key，KV list 也看不到明碼）。 */
export async function sha256Hex(input: string): Promise<string> {
  const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input));
  return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
}

/**
 * 常數時間字串比對（防 timing attack；比 owner secret / static token 用）。
 * 長度不同直接 false，但仍走完固定迴圈避免長度洩漏。
 */
export function constantTimeEqual(a: string, b: string): boolean {
  const ab = new TextEncoder().encode(a);
  const bb = new TextEncoder().encode(b);
  let diff = ab.length ^ bb.length;
  const len = Math.max(ab.length, bb.length);
  for (let i = 0; i < len; i++) {
    diff |= (ab[i] ?? 0) ^ (bb[i] ?? 0);
  }
  return diff === 0;
}

/**
 * 驗證 PKCE：BASE64URL(SHA256(code_verifier)) === code_challenge（僅支援 S256）。
 * method 非 "S256"（含缺省的 "plain"）一律拒絕——OAuth 2.1 + 本 server 只宣告 S256。
 */
export async function verifyPkceS256(
  codeVerifier: string,
  codeChallenge: string,
  method: string | undefined,
): Promise<boolean> {
  if (method !== "S256") return false;
  if (!codeVerifier || !codeChallenge) return false;
  // RFC 7636 §4.1：verifier 43–128 字元、[A-Za-z0-9-._~]。
  if (codeVerifier.length < 43 || codeVerifier.length > 128) return false;
  if (!/^[A-Za-z0-9\-._~]+$/.test(codeVerifier)) return false;
  const computed = await sha256Base64Url(codeVerifier);
  return constantTimeEqual(computed, codeChallenge);
}
