import GetPermissions from "../../permissions.js";

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, "");
}

function response(body: string, status: number) {
  return new Response(body, {
    headers: {
      "content-type": "application/json",
    },
    status,
  });
}

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

  if (!cookies) return response('{"error":"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: {
        "set-cookie": "_s=; Max-Age=0",
      },
      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 response('{"error":"Missing code"}', 400);
  if (!state) return response('{"error":"Missing state"}', 400);

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

  if (!stateRedirect) return response('{"error":"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 response('{"error":"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 response('{"error":"Do not touch the scopes!"}', 400);

  let userData: { [k: string]: any } = {
    ...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 response('{"error":"Failed to retrieve user"}', 500);
  }

  const apiUser: { [k: string]: any } = await userReq.json();
  userData = {
    ...userData,
    ...apiUser,
  };

  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 = GetPermissions(userData.id, memberData.roles);
    userData.roles = memberData.roles;
  } else {
    userData.permissions = GetPermissions(userData.id);
  }

  const tokenPrefixes = [
    "ABOVE-THE-SKY",
    "BANDITO",
    "BE-CONCERNED",
    "CAR-RADIO",
    "CHEESE",
    "CHLORINE",
    "CRAZY-EQUALS-GENIUS",
    "CUBICLES",
    "DEAD",
    "DEMOLITION-LOVERS",
    "DEVIL-DOGS",
    "DOUBT",
    "DREADNOUGHT",
    "DYING-IN-LA",
    "FAIRLY-LOCAL",
    "FORMIDABLE",
    "GATES-OF-GLORY",
    "GIRLS-GIRLS-BOYS",
    "GONER",
    "HEATHENS",
    "HEAVYDIRTYSOUL",
    "HELENA",
    "HYDRA",
    "I-WRITE-SINS-NOT-TRAGEDIES",
    "KITCHEN-SINK",
    "LEVITATE",
    "LOCAL-GOD",
    "MAGGIE",
    "MAMA",
    "MONTANA",
    "NERO-FORTE",
    "NOOB",
    "NOT-TODAY",
    "NO-CHANCES",
    "POLARIZE",
    "PSYCHO",
    "ROMANCE",
    "SAD-CLOWN",
    "SATURDAY",
    "SAY-IT-LOUDER",
    "SEMI-AUTOMATIC",
    "TEENAGERS",
    "THUNDERSWORD",
    "TOKYO-DRIFTING",
    "TRAPDOOR",
    "TREES",
    "UMA-THURMAN",
    "UNSAINTED",
    "VERMILION",
    "VERSAILLES",
    "VICTORIOUS",
    "VIVA-LAS-VENGEANCE",
    "XIX",
  ];

  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: tokenData.expires_in,
  });

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