import GetPermissions from "../../permissions.js";
import { jsonError } from "../../common.js";
import tokenPrefixes from "../../../data/token_prefixes.json";

async function generateTokenHash(token: string): Promise<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, "");
}

export async function onRequestDelete(context: RequestContext) {
  const cookies = context.request.headers.get("cookie")?.split("; ");

  if (!cookies) return jsonError("Not logged in", 401);

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

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

    await context.env.DATA.delete(`auth_${await generateTokenHash(value)}`);

    return new Response(null, {
      headers: {
        "clear-site-data": '"cookies"',
      },
      status: 204,
    });
  }
}

export async function onRequestGet(context: RequestContext) {
  const { host, protocol, searchParams } = new URL(context.request.url);
  const code = searchParams.get("code");
  const state = searchParams.get("state");

  if (!code) return jsonError("Missing code", 400);
  if (!state) return jsonError("Missing state", 400);

  const stateRedirect = await context.env.DATA.get(`state_${state}`);

  if (!stateRedirect) return jsonError("Invalid state", 400);

  const tokenReq = await fetch("https://discord.com/api/oauth2/token", {
    body: new URLSearchParams({
      code,
      grant_type: "authorization_code",
      redirect_uri: `${protocol}//${host}/api/auth/session`,
    }).toString(),
    headers: {
      authorization: `Basic ${btoa(
        context.env.DISCORD_ID + ":" + context.env.DISCORD_SECRET,
      )}`,
      "content-type": "application/x-www-form-urlencoded",
    },
    method: "POST",
  });

  if (!tokenReq.ok) {
    console.log(await tokenReq.text());

    return jsonError("Failed to redeem code", 500);
  }

  const tokenData: {
    access_token: string;
    expires_in: number;
    refresh_token: string;
    scope: string;
    token_type: string;
  } = await tokenReq.json();

  if (tokenData.scope.search("guilds.members.read") === -1)
    return jsonError("Do not touch the scopes!", 400);

  const oauthData = {
    ...tokenData,
    refresh_at: Date.now() + tokenData.expires_in * 1000 - 86400000,
  };

  const userReq = await fetch("https://discord.com/api/v10/users/@me", {
    headers: {
      authorization: `Bearer ${tokenData.access_token}`,
    },
  });

  if (!userReq.ok) {
    console.log(await userReq.text());
    return jsonError("Failed to retrieve user", 500);
  }

  const userData: { [k: string]: any } = await userReq.json();
  const serverMemberReq = await fetch(
    "https://discord.com/api/v10/users/@me/guilds/242263977986359297/member",
    {
      headers: {
        authorization: `Bearer ${tokenData.access_token}`,
      },
    },
  );

  const memberData: { [k: string]: any } = await serverMemberReq.json();

  if (serverMemberReq.ok) {
    userData.permissions = await GetPermissions(
      userData.id,
      context,
      memberData.roles,
    );
    userData.roles = memberData.roles;
  } else {
    userData.permissions = await GetPermissions(userData.id, context);
  }

  const selectedTokenStart =
    tokenPrefixes[Math.round(Math.random() * (tokenPrefixes.length - 1))] + "_";

  const authToken =
    selectedTokenStart +
    `${crypto.randomUUID()}${crypto.randomUUID()}${crypto.randomUUID()}${crypto.randomUUID()}`.replaceAll(
      "-",
      "",
    );

  const tokenHash = await generateTokenHash(authToken);

  await context.env.DATA.put(`auth_${tokenHash}`, JSON.stringify(userData), {
    expirationTtl: 2419200,
  });

  await context.env.DATA.put(
    `oauthcredentials_${userData.id}`,
    JSON.stringify(oauthData),
    {
      expirationTtl: 1209600,
    },
  );

  return new Response(null, {
    headers: {
      location: stateRedirect,
      "set-cookie": `_s=${authToken}; HttpOnly; Max-Age=${tokenData.expires_in}; Path=/; SameSite=Lax; Secure`,
    },
    status: 302,
  });
}
