import { ensureSchema, getRawDb } from "./mvp-db";

export type McpExportKind = "recovery" | "resources";

type ExportTokenRow = {
  kind: McpExportKind;
  payload: string;
  expires_at: string;
};

function sqliteTimestamp(date: Date) {
  return date.toISOString().replace("T", " ").slice(0, 19);
}

async function digest(value: string) {
  const bytes = await crypto.subtle.digest(
    "SHA-256",
    new TextEncoder().encode(value),
  );
  return [...new Uint8Array(bytes)]
    .map((byte) => byte.toString(16).padStart(2, "0"))
    .join("");
}

export async function issueMcpExportToken(
  kind: McpExportKind,
  payload: Record<string, unknown>,
  ttlMinutes = 15,
) {
  await ensureSchema();
  const db = getRawDb();
  const token = `${crypto.randomUUID().replaceAll("-", "")}${crypto
    .randomUUID()
    .replaceAll("-", "")}`;
  const expiresAt = new Date(
    Date.now() + Math.max(1, Math.min(60, ttlMinutes)) * 60_000,
  );
  await db.prepare("DELETE FROM mcp_export_tokens WHERE expires_at <= CURRENT_TIMESTAMP").run();
  await db
    .prepare(
      `INSERT INTO mcp_export_tokens (token_hash, kind, payload, expires_at)
       VALUES (?, ?, ?, ?)`,
    )
    .bind(await digest(token), kind, JSON.stringify(payload), sqliteTimestamp(expiresAt))
    .run();
  return { token, expiresAt: expiresAt.toISOString() };
}

export async function consumeMcpExportToken(
  token: string,
  expectedKind: McpExportKind,
) {
  if (!token || token.length < 32) return null;
  await ensureSchema();
  const db = getRawDb();
  const tokenHash = await digest(token);
  const row = await db
    .prepare(
      `SELECT kind, payload, expires_at
       FROM mcp_export_tokens
       WHERE token_hash = ? AND expires_at > CURRENT_TIMESTAMP`,
    )
    .bind(tokenHash)
    .first<ExportTokenRow>();
  if (!row || row.kind !== expectedKind) return null;
  await db
    .prepare("DELETE FROM mcp_export_tokens WHERE token_hash = ?")
    .bind(tokenHash)
    .run();
  try {
    return JSON.parse(row.payload) as Record<string, unknown>;
  } catch {
    return null;
  }
}
