import { Hono } from "hono";
import * as swa from "@simplewebauthn/server";
import { randomUUID } from "node:crypto";
import { eq } from "drizzle-orm";
import { RP_ID, ORIGIN, db } from "./index.js";
import { sessionTable, userTable, webauthnChallenges } from "./db/schema.js";
import { stringify, parse } from "superjson";
import { setCookie } from "hono/cookie";

let app = new Hono();

export const LoginForm = () => <section class="register-form">
  <button hx-post="/auth/register-begin" hx-target="closest .register-form" hx-swap="outerHTML">register</button>
  <button hx-post="/auth/login-begin" hx-target="closest .register-form" hx-swap="outerHTML">login</button>
</section>;

app.post("/register-begin", async c => {
  const username = randomUUID();
  let options = await swa.generateRegistrationOptions({
    rpName: "uneven",
    rpID: RP_ID,
    userName: username,
    authenticatorSelection: {
      residentKey: "required",
      userVerification: "preferred",
    },
  });
  await db.insert(webauthnChallenges).values({ key: "register:" + username, challenge: options.challenge });
  return c.html(
    <section
      class="register-form"
      _={`
        on load
          call SimpleWebAuthnBrowser.startRegistration(${JSON.stringify(options)})
          fetch /auth/register-finish with method:"POST", body:JSON.stringify({ resp: it, username: ${JSON.stringify(username)} })
          put the result into my.outerHTML
      `}
    />
  );
});

app.post("/register-finish", async c => {
  let { resp, username } = await c.req.json();
  let [{ chall }] = await db.delete(webauthnChallenges).where(eq(webauthnChallenges.key, "register:" + username)).returning({ chall: webauthnChallenges.challenge });
  let r = await swa.verifyRegistrationResponse({ response: resp, expectedChallenge: chall, expectedOrigin: ORIGIN });
  if (!r.verified || !("registrationInfo" in r)) return c.html(<p style="color: red">Could not verify registration response!</p>);
  await db.insert(userTable).values({ name: username, passkey: stringify(r.registrationInfo), passkeyId: r.registrationInfo!.credential.id });
  return c.html(
    <p>You now have an account!</p>
  );
});

app.post("/login-begin", async c => {
  let options = await swa.generateAuthenticationOptions({
    rpID: RP_ID,
    userVerification: "preferred",
  });
  let key = randomUUID();
  await db.insert(webauthnChallenges).values({ challenge: options.challenge, key: "login:" + key });
  return c.html(
    <section
      class="register-form"
      _={`
        on load
          call SimpleWebAuthnBrowser.startAuthentication(${JSON.stringify(options)})
          fetch /auth/login-finish with method:"POST", body:JSON.stringify({ resp: it, key: ${JSON.stringify(key)} })
          put the result into my.outerHTML
      `}
    />
  );
});

app.post("/login-finish", async c => {
  let { resp, key } = await c.req.json();
  let [{ chall }] = await db.delete(webauthnChallenges).where(eq(webauthnChallenges.key, "login:" + key)).returning({ chall: webauthnChallenges.challenge });
  let [{ id: userId, passkey }] = await db.select().from(userTable).where(user => eq(user.passkeyId, resp.id));
  if (!passkey) return c.html(<p style="color: red">Who are you?</p>);
  let r = await swa.verifyAuthenticationResponse({
    response: resp,
    expectedChallenge: chall,
    expectedOrigin: ORIGIN,
    expectedRPID: RP_ID,
    requireUserVerification: false,
    credential: parse<swa.VerifiedRegistrationResponse["registrationInfo"]>(passkey)!.credential,
  });
  if (!r.verified) return c.html(<p style="color: red">Could not verify authentication response!</p>);
  let uuid = randomUUID();
  await db.insert(sessionTable).values({ userId, uuid });
  setCookie(c, "session", uuid);
  return c.html(<p>Logged in!</p>);
});

export default app;
