import { MembershipRole } from "@prisma/client";
import { randomBytes } from "crypto";
import type { NextApiRequest, NextApiResponse } from "next";

import { getSession } from "@lib/auth";
import { BASE_URL } from "@lib/config/constants";
import { sendTeamInviteEmail } from "@lib/emails/email-manager";
import { TeamInvite } from "@lib/emails/templates/team-invite-email";
import prisma from "@lib/prisma";
import slugify from "@lib/slugify";

import { getTranslation } from "@server/lib/i18n";

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const t = await getTranslation(req.body.language ?? "en", "common");

  if (req.method !== "POST") {
    return res.status(400).json({ message: "Bad request" });
  }

  const session = await getSession({ req });
  if (!session) {
    return res.status(401).json({ message: "Not authenticated" });
  }

  const team = await prisma.team.findFirst({
    where: {
      id: parseInt(req.query.team as string),
    },
  });

  if (!team) {
    return res.status(404).json({ message: "Invalid team" });
  }

  const reqBody = req.body as {
    usernameOrEmail: string;
    role: MembershipRole;
    sendEmailInvitation: boolean;
  };
  const { role, sendEmailInvitation } = reqBody;
  // liberal email match
  const isEmail = (str: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str);
  const usernameOrEmail = isEmail(reqBody.usernameOrEmail)
    ? reqBody.usernameOrEmail.toLowerCase()
    : slugify(reqBody.usernameOrEmail);

  const invitee = await prisma.user.findFirst({
    where: {
      OR: [{ username: usernameOrEmail }, { email: usernameOrEmail }],
    },
  });

  if (!invitee) {
    const email = isEmail(usernameOrEmail) ? usernameOrEmail : undefined;
    if (!email) {
      return res.status(400).json({
        message: `Invite failed because there is no corresponding user for ${usernameOrEmail}`,
      });
    }
    await prisma.user.create({
      data: {
        email,
        teams: {
          create: {
            team: {
              connect: {
                id: parseInt(req.query.team as string),
              },
            },
            role,
          },
        },
      },
    });

    const token: string = randomBytes(32).toString("hex");

    await prisma.verificationRequest.create({
      data: {
        identifier: usernameOrEmail,
        token,
        expires: new Date(new Date().setHours(168)), // +1 week
      },
    });

    if (session?.user?.name && team?.name) {
      const teamInviteEvent: TeamInvite = {
        language: t,
        from: session.user.name,
        to: usernameOrEmail,
        teamName: team.name,
        joinLink: `${BASE_URL}/auth/signup?token=${token}&callbackUrl=${BASE_URL + "/settings/teams"}`,
      };

      await sendTeamInviteEmail(teamInviteEvent);
    }

    return res.status(201).json({});
  }

  // create provisional membership
  try {
    await prisma.membership.create({
      data: {
        teamId: parseInt(req.query.team as string),
        userId: invitee.id,
        role,
      },
    });
  } catch (err: any) {
    if (err.code === "P2002") {
      // unique constraint violation
      return res.status(409).json({
        message: "This user is a member of this team / has a pending invitation.",
      });
    } else {
      throw err; // rethrow
    }
  }

  // inform user of membership by email
  if (sendEmailInvitation && session?.user?.name && team?.name) {
    const teamInviteEvent: TeamInvite = {
      language: t,
      from: session.user.name,
      to: usernameOrEmail,
      teamName: team.name,
      joinLink: BASE_URL + "/settings/teams",
    };

    await sendTeamInviteEmail(teamInviteEvent);
  }

  res.status(201).json({});
}