import { URLSearchParams } from "url";
import {
  discordRedirectUri,
  DiscordAccessTokenResponse,
  getDiscordGuilds,
  getDiscordUser,
  getDiscordAvatar
} from "../oauth";
import { cookies } from "next/dist/client/components/headers";
import prisma from "@/prisma";
import { v4 } from "uuid";
import * as ldap from "@/ldap";
import { getLogger } from "@/logger";
import { AuthState, getAuthState, getUser } from "@/auth";

export async function GET(request: Request) {
  const logger = getLogger("/oauth/discord/redirect");

  let url = new URL(request.url);
  let code = url.searchParams.get("code");
  let state = url.searchParams.get("state");

  if (code === null || state === null) {
    logger.info("request made with missing code/state");
    return new Response("missing code/state", { status: 400 });
  }

  const cookieStore = cookies();
  let cookieState = cookieStore.get("state");
  // prevent forgery
  if (cookieState?.value !== state) {
    logger.info(
      "request made with invalid state - someone attempting forgery?"
    );
    return new Response("state is invalid", { status: 400 });
  }

  let form = new URLSearchParams();
  form.append("client_id", process.env.DISCORD_CLIENT_ID);
  form.append("client_secret", process.env.DISCORD_CLIENT_SECRET);
  form.append("grant_type", "authorization_code");
  form.append("code", code);
  form.append("redirect_uri", discordRedirectUri());

  let tokenResponse = await fetch("https://discord.com/api/oauth2/token", {
    method: "POST",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded"
    },
    body: form.toString()
  });
  if (!tokenResponse.ok) {
    logger.error("baby");
    throw "baby";
  }

  let tokenBody: DiscordAccessTokenResponse = await tokenResponse.json();

  const discordUser = await getDiscordUser(tokenBody.access_token);
  const guilds = await getDiscordGuilds(tokenBody.access_token);
  const allowedGuilds = process.env.DISCORD_ALLOWED_GUILDS?.split(",") ?? [];

  let allowed = false;
  for (const guild of allowedGuilds) if (guilds.includes(guild)) allowed = true;
  if (!allowed) {
    logger.info({ id: discordUser.id }, "user tried to sign up");
    return new Response("not permitted to register account", { status: 403 });
  }

  let userId = null;
  const authState = await getAuthState();
  if (authState === AuthState.LoggedIn) {
    const currentUser = await getUser();
    userId = currentUser?.id;
  }

  // - create the discord auth data in prisma, which will make the user if it doesn't exist
  // - get the user from the discord auth data
  // - either create a new auth ticket or invalidate the old one
  // - update the user to point to the new auth ticket

  const discordAuth = await prisma.discordAuth.upsert({
    where: {
      id: discordUser.id
    },
    create: {
      id: discordUser.id,
      accessToken: tokenBody.access_token,
      refreshToken: tokenBody.refresh_token,
      expiresAt: new Date(Date.now() + tokenBody.expires_in * 1000),
      user:
        userId != null
          ? { connect: { id: userId } }
          : { create: { username: null } }
    },
    update: {
      accessToken: tokenBody.access_token,
      refreshToken: tokenBody.refresh_token,
      expiresAt: new Date(Date.now() + tokenBody.expires_in * 1000)
    }
  });

  const user = await prisma.user.findFirst({
    where: {
      id: discordAuth.userId
    }
  });

  // check if user got deleted from ldap, same as /api/register
  if (
    user !== null &&
    user.username !== null &&
    !(await ldap.checkUserExists(user.username))
  ) {
    logger.warn(
      { username: user.username },
      "user doesn't exist in ldap anymore"
    );
    user.username = null;
    await prisma.user.update({
      where: {
        id: user.id
      },
      data: {
        username: null
      }
    });
  }

  const authTicket = await prisma.authTicket.upsert({
    where: {
      userId: user!.id
    },
    create: {
      userId: user!.id,
      ticket: v4(),
      expiresAt: new Date(Date.now() + 86400000)
    },
    update: {
      ticket: v4(),
      expiresAt: new Date(Date.now() + 86400000)
    }
  });

  await prisma.user.update({
    where: {
      id: user!.id
    },
    data: {
      authTicket: {
        connect: {
          id: authTicket.id
        }
      }
    }
  });

  if (user?.username !== null) {
    return new Response(null, {
      status: 302,
      headers: {
        "Set-Cookie": `ticket=${authTicket.ticket}; Path=/;`,
        Location: "/me"
      }
    });
  }

  const avatarUrl = await getDiscordAvatar(tokenBody.access_token);

  const query = new URLSearchParams();
  query.append("username", discordUser.username);
  query.append("email", discordUser.email ?? "");
  query.append("avatar", avatarUrl ?? "");

  return new Response(null, {
    status: 302,
    headers: {
      "Set-Cookie": `ticket=${authTicket.ticket}; Path=/;`,
      Location: "/register?" + query.toString()
    }
  });
}
