import { jsonError, jsonResponse } from "../../common.js";
import upload from "../../upload.js";

export async function onRequestPost(context: RequestContext) {
  const {
    description,
    files,
    senderTokenId,
    submissionType,
    turnstileResponse,
    usernames,
  } = context.data.body;

  if (!context.data.current_user) {
    if (typeof turnstileResponse !== "string")
      return jsonError("You must complete the captcha", 401);

    const turnstileAPIResponse = await fetch(
      "https://challenges.cloudflare.com/turnstile/v0/siteverify",
      {
        body: JSON.stringify({
          remoteip: context.request.headers.get("CF-Connecting-IP"),
          response: turnstileResponse,
          secret: context.env.TURNSTILE_SECRETKEY,
        }),
        headers: {
          "content-type": "application/json",
        },
        method: "POST",
      },
    );

    const { success }: { success: boolean } = await turnstileAPIResponse.json();

    if (!success) return jsonError("Captcha test failed", 403);
  }

  if (!Array.isArray(usernames))
    return jsonError("Usernames must be type of array", 400);

  if (!["abuse", "exploit"].includes(submissionType))
    return jsonError("Invalid submission type", 400);

  if (
    !["string", "undefined"].includes(typeof description) ||
    description?.length > 512
  )
    return jsonError("Invalid description", 400);

  if (
    !Array.isArray(files) ||
    files.find((file) => {
      const keys = Object.keys(file);

      return !keys.includes("name") || !keys.includes("size");
    })
  )
    return jsonError("File list missing name(s) and/or size(s)", 400);

  if (
    files.find(
      (file) =>
        typeof file.name !== "string" ||
        typeof file.size !== "number" ||
        file.size < 0 ||
        file.size > 536870912,
    )
  )
    return jsonError("One or more files contain an invalid name or size", 400);

  if (!usernames.length || usernames.length > 20)
    return jsonError(
      "Number of usernames provided must be between 1 and 20",
      400,
    );

  for (const username of usernames) {
    // Roblox thought this name was inappropriate, the standard username rules may not apply.
    const isResetUsername = username.match(/roblox_user_\d+/);

    if (
      !isResetUsername &&
      (username.length < 3 ||
        username.length > 20 ||
        username.match(/_/g)?.length > 1 ||
        username.match(/\W/))
    )
      return jsonError(`Username "${username}" is invalid`, 400);
    else if (isResetUsername && username.length > 30)
      return jsonError(
        `Username "${username}" is not a valid reset username`,
        400,
      );
  }

  const rbxSearchReq = await fetch(
    "https://users.roblox.com/v1/usernames/users",
    {
      body: JSON.stringify({
        usernames,
        excludeBannedUsers: true,
      }),
      headers: {
        "content-type": "application/json",
      },
      method: "POST",
    },
  );

  if (!rbxSearchReq.ok)
    return jsonError(
      "Failed to locate Roblox users due to upstream error",
      500,
    );

  const rbxSearchData: { data: { [k: string]: any }[] } =
    await rbxSearchReq.json();

  if (rbxSearchData.data.length < usernames.length) {
    const missingUsers = [];

    for (const userData of rbxSearchData.data) {
      if (!usernames.includes(userData.requestedUsername))
        missingUsers.push(userData.requestedUsername);
    }

    return jsonError(
      `The following users do not exist or are banned from Roblox: ${missingUsers.toString()}`,
      400,
    );
  }

  const metaIDs = [];
  const metaNames = [];

  for (const data of rbxSearchData.data) {
    metaIDs.push(data.id);
    metaNames.push(data.name);
  }

  const uploadUrlPromises: Promise<string>[] = [];
  const filesToProcess = [];

  for (const file of files) {
    const fileParts = file.name.split(".");
    let fileExten = fileParts.at(-1).toLowerCase();

    if (fileExten === "mov") fileExten = "mp4";

    if (
      fileParts.length < 2 ||
      ![
        "avif",
        "gif",
        "jpeg",
        "jpg",
        "m4v",
        "mkv",
        "mp4",
        "png",
        "webm",
        "webp",
        "wmv",
      ].includes(fileExten)
    )
      return jsonError(
        `File ${file.name} cannot be uploaded as it is unsupported`,
        415,
      );

    const fileUploadKey = `${crypto.randomUUID().replaceAll("-", "")}/${crypto
      .randomUUID()
      .replaceAll("-", "")}${context.request.headers.get(
      "cf-ray",
    )}${Date.now()}`;

    uploadUrlPromises.push(
      upload(
        context.env,
        ["mp4", "m4v", "webm"].includes(fileExten)
          ? fileUploadKey
          : `t/${fileUploadKey}`,
        file.size,
        fileExten,
      ),
    );

    if (!["mp4", "m4v", "webm"].includes(fileExten)) {
      filesToProcess.push(fileUploadKey);
    }
  }

  const uploadUrlResults = await Promise.allSettled(uploadUrlPromises);

  const reportId = `${Date.now()}${context.request.headers
    .get("cf-ray")
    ?.replace("-", "")}${crypto.randomUUID().replaceAll("-", "")}`;

  const { current_user: currentUser } = context.data;
  if (filesToProcess.length)
    await context.env.DATA.put(
      `coconutdata_${reportId}`,
      JSON.stringify({
        attachments: filesToProcess,
      }),
      {
        expirationTtl: 1800,
      },
    );

  await context.env.DATA.put(
    `reportprocessing_${reportId}`,
    currentUser?.id || context.request.headers.get("CF-Connecting-IP"),
    { expirationTtl: 3600 },
  );

  if (uploadUrlResults.find((uploadUrl) => uploadUrl.status === "rejected"))
    return jsonError("Failed to generate upload url", 500);

  const attachments: string[] = [];
  const uploadUrls: string[] = [];

  for (const urlResult of uploadUrlResults as PromiseFulfilledResult<string>[]) {
    uploadUrls.push(urlResult.value);
    attachments.push(new URL(urlResult.value).pathname.replace(/^\/?t?\//, ""));
  }

  await context.data.prisma.report.create({
    data: {
      attachments,
      id: reportId,
      target_ids: metaIDs,
      target_usernames: metaNames,
      type: submissionType,
      user: currentUser
        ? {
            email: currentUser.email,
            id: currentUser.id,
            username: currentUser.username,
          }
        : undefined,
    },
  });

  if (typeof senderTokenId === "string")
    await context.data.prisma.pushNotification.create({
      data: {
        event_id: reportId,
        event_type: reportId,
        token: senderTokenId,
      },
    });

  return jsonResponse(
    JSON.stringify({ id: reportId, upload_urls: uploadUrls }),
  );
}
