import { createId } from "@paralleldrive/cuid2";
import { APIEvent } from "@solidjs/start/server/types";
import { OAuth2RequestError } from "arctic";
import { eq } from "drizzle-orm";
import httpStatus from "http-status";
import createClient from "openapi-fetch";
import { getCookie, setCookie } from "vinxi/http";
import db from "~/drizzle";
import { discordTokens, users } from "~/drizzle/schema";
import { discord, lucia } from "~/lib/auth";
import { paths } from "~/types/discord";

export async function GET(event: APIEvent): Promise<Response> {
  const url = new URL(event.request.url);
  const code = url.searchParams.get("code");
  const state = url.searchParams.get("state");
  const error = url.searchParams.get("error");
  const error_description = url.searchParams.get("error_description");
  if (error)
    switch (error) {
      case "access_denied":
        return new Response(null, {
          status: httpStatus.FOUND,
          headers: { Location: "/" },
        });
      default:
        console.log("Discord oauth error:", error_description);
        return new Response(decodeURI(error_description ?? ""), {
          status: httpStatus.BAD_REQUEST,
        });
    }

  const storedState = getCookie("discord_oauth_state") ?? null;
  if (!code || !state || !storedState || state !== storedState) {
    return new Response(null, {
      status: httpStatus.BAD_REQUEST,
    });
  }

  try {
    const tokens = await discord.validateAuthorizationCode(code);
    const { GET } = createClient<paths>({
      baseUrl: "https://discord.com/api/v10",
    });
    const discordUserResponse = await GET("/users/@me", {
      headers: { Authorization: `Bearer ${tokens.accessToken}` },
    });
    if (discordUserResponse.error) throw discordUserResponse.error;
    const discordUser = discordUserResponse.data;
    const existingUser = await db.query.users
      .findFirst({
        where: eq(users.discord_id, discordUser.id),
      })
      .execute();

    if (existingUser) {
      const session = await lucia.createSession(
        existingUser.id,
        {},
        { sessionId: createId() },
      );
      const sessionCookie = lucia.createSessionCookie(session.id);
      setCookie(
        sessionCookie.name,
        sessionCookie.value,
        sessionCookie.attributes,
      );
      await db
        .update(users)
        .set({
          name: discordUser.global_name,
          image: discordUser.avatar,
        })
        .where(eq(users.discord_id, discordUser.id))
        .returning()
        .execute();

      return new Response(null, {
        status: httpStatus.FOUND,
        headers: { Location: "/config" },
      });
    }

    const userId = createId();
    await db.insert(users).values({
      id: userId,
      discord_id: discordUser.id,
      name: discordUser.global_name,
      image: discordUser.avatar,
    });
    await db
      .insert(discordTokens)
      .values({
        userId,
        accessToken: tokens.accessToken,
        expiresAt: tokens.accessTokenExpiresAt,
        refreshToken: tokens.refreshToken,
      })
      .returning()
      .execute();
    const session = await lucia.createSession(
      userId,
      {},
      { sessionId: createId() },
    );
    const sessionCookie = lucia.createSessionCookie(session.id);
    setCookie(
      sessionCookie.name,
      sessionCookie.value,
      sessionCookie.attributes,
    );
    return new Response(null, {
      status: httpStatus.FOUND,
      headers: { Location: "/config" },
    });
  } catch (e) {
    // the specific error message depends on the provider
    if (e instanceof OAuth2RequestError) {
      // invalid code
      return new Response(null, {
        status: httpStatus.BAD_REQUEST,
      });
    }
    console.error("Unknown error on callback.");
    console.error(e);
    return new Response(null, {
      status: httpStatus.INTERNAL_SERVER_ERROR,
    });
  }
}
