/**
 * Drives the real `attemptSignIn` Server Action on the running dev server, so
 * the limiter is measured where people actually meet it and not only in the
 * unit tests. Throwaway. `bun run tmp/ratelimit/e2e.ts`.
 *
 * The action id is minted by Turbopack and changes whenever `app/login/actions.ts`
 * is recompiled, so it is read out of the client chunk rather than pasted in.
 */

const BASE = "http://127.0.0.1:4214"

async function actionId(): Promise<string> {
  const html = await fetch(`${BASE}/login`).then((r) => r.text())
  const chunks = [
    ...new Set([...html.matchAll(/\/_next\/static\/[^"'\\ ]+?\.js/g)].map((m) => m[0])),
  ]
  for (const chunk of chunks) {
    const source = await fetch(`${BASE}${chunk}`).then((r) => r.text())
    const found = source.match(
      /__next_internal_action_entry_do_not_use__ \[\{"([0-9a-f]+)":\{"name":"attemptSignIn"\}\}/,
    )
    if (found) return found[1]
  }
  throw new Error("no action id for attemptSignIn")
}

const ID = await actionId()
console.log("action id:", ID, "\n")

type Answer = { ok?: boolean; tier?: string; reason?: string; retryAfter?: number }

async function attempt(username: string, password: string): Promise<Answer> {
  const response = await fetch(`${BASE}/login`, {
    method: "POST",
    headers: { "Next-Action": ID, "Content-Type": "text/plain;charset=UTF-8" },
    body: JSON.stringify([username, password]),
  })
  const body = await response.text()
  // The flight stream carries the returned object verbatim; this pulls it out.
  const line = body.match(/\{"ok":(?:true|false)[^}]*\}/)
  if (!line) throw new Error(`no answer in ${response.status}: ${body.slice(0, 400)}`)
  return JSON.parse(line[0]) as Answer
}

function show(label: string, answer: Answer) {
  console.log(label.padEnd(34), JSON.stringify(answer))
}

const step = process.argv[2] ?? "all"

if (step === "all" || step === "lock") {
  console.log("--- A. eleven wrong passwords for nattikan (staff) ---")
  for (let n = 1; n <= 11; n++) {
    show(`  attempt ${n}`, await attempt("nattikan", "definitely-not-it"))
  }

  console.log("\n--- B. another username while nattikan is blocked ---")
  show("  narin / admin1", await attempt("narin", "admin1"))
  show("  nattikan still", await attempt("nattikan", "staff1"))

  console.log("\n--- C. a success clears the count (narin, admin) ---")
  for (let n = 1; n <= 8; n++) await attempt("narin", "definitely-not-it")
  show("  the 9th failure", await attempt("narin", "definitely-not-it"))
  show("  correct on the 10th try", await attempt("narin", "admin1"))
  for (let n = 1; n <= 8; n++) await attempt("narin", "definitely-not-it")
  show("  9 fresh failures later", await attempt("narin", "definitely-not-it"))
  show("  and still gets in", await attempt("narin", "admin1"))

  console.log("\n--- D. an invented username is refused the same way ---")
  for (let n = 1; n <= 10; n++) await attempt("ghost.tester", "definitely-not-it")
  show("  11th for ghost.tester", await attempt("ghost.tester", "definitely-not-it"))
}

if (step === "all" || step === "unlock") {
  console.log("\n--- E. nattikan when the window has rolled ---")
  show("  nattikan / staff1", await attempt("nattikan", "staff1"))
}

export {}
