// OAuth 短效認證儲存（KV）。儲存鐵律（leo + wiki）：
//   只有「取得的暫時性認證」可進 KV，且帶 TTL——authorization code、access token 是也。
//   長效機密（owner secret）走 CF Secrets，不進此處。DCR client 不落地（見 OAUTH.md 相容決策）。
// KV key 一律用 SHA-256 hex（不把 raw code/token 當 key）→ 就算 KV list 也拿不到可用憑證。
import { sha256Hex } from "./crypto.js";

/** authorization code 綁定的資料（一次性；/token 驗證後即刪）。 */
export interface AuthCodeData {
  client_id: string;
  redirect_uri: string;
  code_challenge: string;
  code_challenge_method: string;
  scope: string;
  /** RFC 8707 resource：綁定 token 的目標 MCP server（audience）。 */
  resource: string;
  /** 換發後 token 綁定的資料分區（owner namespace）。 */
  namespace: string;
}

/** access token 綁定的資料。 */
export interface AccessTokenData {
  namespace: string;
  client_id: string;
  scope: string;
  /** token 的目標受眾（= 本 MCP server 的 canonical URI），驗證時比對。 */
  aud: string;
  /** 過期時間（epoch 秒），與 KV TTL 雙保險。 */
  exp: number;
}

const CODE_PREFIX = "oauth:code:";
const TOKEN_PREFIX = "oauth:tok:";
/** authorization code 存活秒數（一次性、極短效）。 */
export const AUTH_CODE_TTL_SECONDS = 600;

/** 存 authorization code（TTL 極短）。回傳 raw code 給 client。 */
export async function putAuthCode(kv: KVNamespace, code: string, data: AuthCodeData): Promise<void> {
  const key = CODE_PREFIX + (await sha256Hex(code));
  await kv.put(key, JSON.stringify(data), { expirationTtl: AUTH_CODE_TTL_SECONDS });
}

/** 取出並「消費」authorization code（一次性：讀到即刪，防重放）。找不到回 null。 */
export async function consumeAuthCode(kv: KVNamespace, code: string): Promise<AuthCodeData | null> {
  const key = CODE_PREFIX + (await sha256Hex(code));
  const raw = await kv.get(key);
  if (!raw) return null;
  await kv.delete(key); // 一次性使用（OAuth 2.1：code 用過必失效）
  try {
    return JSON.parse(raw) as AuthCodeData;
  } catch {
    return null;
  }
}

/** 存 access token（TTL = ttlSeconds）。回傳 raw token 給 client。 */
export async function putAccessToken(
  kv: KVNamespace,
  token: string,
  data: AccessTokenData,
  ttlSeconds: number,
): Promise<void> {
  const key = TOKEN_PREFIX + (await sha256Hex(token));
  await kv.put(key, JSON.stringify(data), { expirationTtl: ttlSeconds });
}

/** 查 access token → 綁定資料。找不到 / 過期回 null（KV TTL 到期會自動消失，另做 exp 雙檢）。 */
export async function getAccessToken(kv: KVNamespace, token: string): Promise<AccessTokenData | null> {
  const key = TOKEN_PREFIX + (await sha256Hex(token));
  const raw = await kv.get(key);
  if (!raw) return null;
  try {
    const data = JSON.parse(raw) as AccessTokenData;
    if (typeof data.exp === "number" && data.exp * 1000 < Date.now()) return null;
    return data;
  } catch {
    return null;
  }
}
