async function constructHTML(context: RequestContext) {
  const { pathname } = new URL(context.request.url);

  if (pathname.startsWith("/api/")) return await context.next();

  if (
    pathname.startsWith("/assets/") ||
    ["/app.webmanifest", "/favicon.ico", "/robots.txt"].includes(pathname) ||
    pathname.startsWith("/files/")
  )
    return await context.env.ASSETS.fetch(context.request);

  return await context.next();
}

async function generateTokenHash(token: string) {
  const hash = await crypto.subtle.digest(
    "SHA-512",
    new TextEncoder().encode(token),
  );
  return btoa(String.fromCharCode(...new Uint8Array(hash)))
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=/g, "");
}

async function setAuth(context: RequestContext) {
  const cookies = context.request.headers.get("cookie");

  if (!cookies) return await context.next();

  const cookieList = cookies.split(/; /);

  for (const c of cookieList) {
    const [name, value] = c.split("=");

    if (name !== "_s") continue;

    const userData = await context.env.DATA.get(
      `auth_${await generateTokenHash(value)}`,
    );

    if (userData) context.data.current_user = JSON.parse(userData);
    else
      context.request.headers.append(
        "set-cookie",
        "_s=; HttpOnly; Max-Age=0; Path=/; Secure;",
      );

    break;
  }

  return await context.next();
}

async function setBody(context: RequestContext) {
  if (
    context.request.method === "POST" &&
    !context.request.url.endsWith("/api/infractions/new")
  ) {
    if (context.request.headers.get("content-type") !== "application/json")
      return new Response('{"error":"Invalid content-type"}', {
        headers: {
          "content-type": "application/json",
        },
        status: 400,
      });

    let body: { [k: string]: any };

    try {
      body = await context.request.json();
    } catch {
      return new Response('{"error":"Invalid JSON"}', {
        headers: {
          "content-type": "application/json",
        },
        status: 400,
      });
    }

    context.data.body = body;
  }

  return await context.next();
}

async function setHeaders(context: RequestContext) {
  const response = await context.next();
  const rtvValues = [
    "Aldaria",
    "Altadena",
    "DEMA",
    "Dragonborn",
    "Heaven, Iowa",
    "Hollywood",
    "Parkway East",
    "Parkway North",
    "Parkway West",
    "Tokyo",
    "Wintervale",
  ];

  response.headers.set("Permissions-Policy", "clipboard-write=(self)");
  response.headers.set("Referrer-Policy", "same-origin");
  response.headers.set(
    "RTV",
    rtvValues[Math.round(Math.random() * (rtvValues.length - 1))],
  );
  response.headers.set("X-Frame-Options", "SAMEORIGIN");

  return response;
}

async function setTheme(context: RequestContext) {
  const cookies = context.request.headers.get("cookie");

  if (!cookies) {
    context.data.theme = "dark";
    return await context.next();
  }

  const cookieList = cookies.split("; ");

  const themeCookie = cookieList.find((c) =>
    c.startsWith("chakra-ui-color-mode"),
  );
  const theme = themeCookie?.split("=").at(1);

  if (!theme || !["dark", "light"].includes(theme)) context.data.theme = "dark";
  else context.data.theme = theme;

  return await context.next();
}

export const onRequest = [
  setAuth,
  setTheme,
  constructHTML,
  setBody,
  setHeaders,
];