import {
  Button,
  CircularProgress,
  CircularProgressLabel,
  Container,
  FormControl,
  FormLabel,
  Heading,
  HStack,
  Input,
  Link,
  Text,
  Textarea,
  useToast,
} from "@chakra-ui/react";
import { useEffect, useState } from "react";
import { useLoaderData } from "@remix-run/react";
import Success from "../../components/Success.js";

export async function loader({
  context,
}: {
  context: RequestContext;
}): Promise<{ logged_in: boolean; site_key: string }> {
  return {
    logged_in: Boolean(context.data.current_user),
    site_key: context.env.TURNSTILE_SITEKEY,
  };
}

export function meta() {
  return [
    {
      title: "Report an Exploiter - Car Crushers",
    },
  ];
}

export default function () {
  const [fileProgress, setFileProgress] = useState(0);
  const [showSuccess, setShowSuccess] = useState(false);
  const [supportsRequestStreams, setSupportsRequestStreams] = useState(false);
  const toast = useToast();
  const [uploading, setUploading] = useState(false);
  const [loading, setLoading] = useState(false);
  const fileTypes: { [k: string]: string } = {
    gif: "image/gif",
    m4v: "video/x-m4v",
    mkv: "video/x-matroska",
    mov: "video/mp4",
    mp4: "video/mp4",
    webm: "video/webm",
    wmv: "video/x-ms-wmv",
  };

  useEffect(() => {
    setSupportsRequestStreams(
      (() => {
        let duplexAccessed = false;

        const hasContentType = new Request("", {
          body: new ReadableStream(),
          method: "POST",
          // @ts-ignore
          get duplex() {
            duplexAccessed = true;
            return "half";
          },
        }).headers.has("Content-Type");

        return duplexAccessed && !hasContentType;
      })(),
    );
  }, []);

  const { logged_in, site_key } = useLoaderData<typeof loader>();

  async function submit() {
    setLoading(true);
    const usernames = (
      document.getElementById("usernames") as HTMLInputElement
    ).value
      .replaceAll(" ", "")
      .split(",");
    const files = (document.getElementById("evidence") as HTMLInputElement)
      .files;

    if (!usernames.length) {
      setLoading(false);
      return toast({
        description: "Must provide at least one username",
        isClosable: true,
        status: "error",
        title: "Error",
      });
    }

    if (!files?.length) {
      setLoading(false);
      return toast({
        description: "Must attach at least one file",
        isClosable: true,
        status: "error",
        title: "Error",
      });
    }

    if (usernames.length > 20) {
      setLoading(false);
      return toast({
        description: "Only up to twenty users can be reported at a time",
        isClosable: true,
        status: "error",
        title: "Too Many Usernames",
      });
    }

    let turnstileToken = "";

    if (!logged_in) {
      const tokenElem = document
        .getElementsByName("cf-turnstile-response")
        .item(0) as HTMLInputElement;

      if (!tokenElem.value) {
        setLoading(false);
        return toast({
          description: "Please complete the captcha and try again",
          isClosable: true,
          status: "error",
          title: "Captcha not completed",
        });
      }

      turnstileToken = tokenElem.value;
    }

    const description = (
      document.getElementById("description") as HTMLTextAreaElement
    ).value;

    const filelist = [];

    for (const file of files) {
      filelist.push({ name: file.name, size: file.size });
    }

    const submitReq = await fetch("/api/reports/submit", {
      body: JSON.stringify({
        bypass: false,
        description: description || undefined,
        files: filelist,
        turnstileResponse: logged_in ? undefined : turnstileToken,
        usernames,
      }),
      headers: {
        "content-type": "application/json",
      },
      method: "POST",
    });

    if (!submitReq.ok) {
      setLoading(false);
      if (!logged_in) {
        try {
          // @ts-expect-error
          turnstile.reset();
        } catch {}
      }

      return toast({
        description: ((await submitReq.json()) as { error: string }).error,
        isClosable: true,
        status: "error",
        title: "Error",
      });
    }

    const { id, upload_urls }: { id: string; upload_urls: string[] } =
      await submitReq.json();

    const totalSize = filelist.reduce((a, b) => a + b.size, 0);
    let bytesRead = 0;
    let shouldRecall = false;

    setUploading(true);

    for (let i = 0; i < upload_urls.length; i++) {
      const reader = files[i].stream().getReader();

      try {
        const uploadReq = await fetch(upload_urls[i], {
          body: supportsRequestStreams
            ? new ReadableStream({
                async pull(controller) {
                  const chunk = await reader.read();

                  if (chunk.done) {
                    controller.close();

                    if (i === upload_urls.length - 1) setUploading(false);

                    return;
                  }

                  controller.enqueue(chunk.value);
                  bytesRead += chunk.value.length;
                  setFileProgress(Math.floor((bytesRead / totalSize) * 100));
                },
              })
            : files[i],
          // @ts-expect-error
          duplex: supportsRequestStreams ? "half" : undefined,
          headers: {
            "content-type":
              (files[i].name.split(".").at(-1) as string).toLowerCase() ===
              "mov"
                ? "video/mp4"
                : files[i].type ||
                  fileTypes[files[i].name.split(".").at(-1) as string],
          },
          method: "PUT",
        });

        if (!uploadReq.ok) {
          shouldRecall = true;
          break;
        }
      } catch (e) {
        console.error(e);

        shouldRecall = true;
        break;
      }
    }

    if (shouldRecall) {
      setLoading(false);
      await fetch("/api/reports/recall", {
        body: JSON.stringify({ id }),
        headers: {
          "content-type": "application/json",
        },
        method: "POST",
      });

      // @ts-expect-error
      turnstile.reset();

      return toast({
        description: "Failed to upload file",
        isClosable: true,
        status: "error",
        title: "Error",
      });
    }

    await fetch("/api/reports/complete", {
      body: JSON.stringify({ id }),
      headers: {
        "content-type": "application/json",
      },
      method: "POST",
    });

    setShowSuccess(true);
    setLoading(false);
  }

  useEffect(() => {
    if (logged_in) return;

    const script = document.createElement("script");
    script.async = true;
    script.defer = true;
    script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js";

    document.body.appendChild(script);
  }, [logged_in]);

  return showSuccess ? (
    <Success
      heading="Report Submitted"
      message="We will review it as soon as possible."
    />
  ) : (
    <>
      <Container maxW="container.md" pt="4vh" textAlign="start">
        <Heading mb="4vh">Report an Exploiter</Heading>
        {logged_in ? null : (
          <Text>
            Tip: Log in before submitting this report to have it appear on your
            data page.
          </Text>
        )}
        <br />
        <FormControl isRequired>
          <FormLabel htmlFor="usernames">
            Username(s) - To specify more than one, provide a comma-delimited
            list (User1, User2, User3...)
          </FormLabel>
          <Input id="usernames" placeholder="builderman" />
        </FormControl>
        <br />
        <FormControl isRequired>
          <FormLabel>Your Evidence (Max size per file: 512MB)</FormLabel>
          <Button
            colorScheme="blue"
            mr="8px"
            onClick={() => document.getElementById("evidence")?.click()}
          >
            Select File
          </Button>
          <input id="evidence" multiple type="file" />
        </FormControl>
        <br />
        <FormControl>
          <FormLabel>Optional description</FormLabel>
          <Textarea id="description" maxLength={512} />
        </FormControl>
        <br />
        <div
          className="cf-turnstile"
          data-error-callback="onTurnstileError"
          data-sitekey={site_key}
        ></div>
        <br />
        <Text>
          By submitting this form, you agree to the{" "}
          <Link color="#646cff" href="/terms">
            Terms of Service
          </Link>{" "}
          and{" "}
          <Link color="#646cff" href="/privacy">
            Privacy Policy
          </Link>
          .
        </Text>
        <br />
        <HStack>
          <Button
            colorScheme="blue"
            disabled={uploading}
            isLoading={loading}
            loadingText="Submitting"
            mr="8px"
            onClick={async () => await submit()}
          >
            Submit
          </Button>
          <CircularProgress
            display={uploading ? "" : "none"}
            isIndeterminate={!supportsRequestStreams}
            value={supportsRequestStreams ? fileProgress : undefined}
          >
            {supportsRequestStreams ? (
              <CircularProgressLabel>{fileProgress}%</CircularProgressLabel>
            ) : null}
          </CircularProgress>
        </HStack>
      </Container>
      <script>
        {`
          function onTurnstileError(code) {
            const messages = {
              110500: "Your browser is too old to complete the captcha, please update it.",
              110510: "Something unexpected happened, please try disabling all extensions and refresh the page. If this does not solve the problem, use a different browser.",
              110600: "Failed to solve the captcha, please refresh the page to try again.",
              200010: "Invalid cache, please clear your cache and site data in your browser's settings.",
              200100: "Your device's clock is wrong, please fix it.",
            };

            const message = messages[code];

            alert(message ?? \`Unknown error when solving captcha. Error \${code}\`);

            return true;
          }
        `}
      </script>
    </>
  );
}