import {
  Button,
  CircularProgress,
  CircularProgressLabel,
  Container,
  FormControl,
  FormLabel,
  Heading,
  HStack,
  Input,
  Link,
  Radio,
  RadioGroup,
  Stack,
  Text,
  Textarea,
  useMultiStyleConfig,
  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: "Send a Report - Car Crushers",
    },
    {
      name: "description",
      content: "Use this page to submit a report",
    },
  ];
}

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

  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 | null;

      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({
        description: description || undefined,
        files: filelist,
        submissionType,
        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++) {
      await new Promise((resolve) => {
        const xhr = new XMLHttpRequest();

        xhr.open("PUT", upload_urls[i], true);
        xhr.setRequestHeader(
          "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],
        );

        xhr.upload.onprogress = (e) => {
          if (!e.lengthComputable) return;

          setFileProgress(
            Math.floor(((bytesRead + e.loaded) / totalSize) * 100),
          );
        };
        xhr.upload.onabort = () => {
          shouldRecall = true;
          setUploading(false);
          setFileProgress(0);
        };
        xhr.upload.onerror = () => {
          shouldRecall = true;
          setUploading(false);
          setFileProgress(0);
        };
        xhr.upload.onloadend = (ev) => {
          if (i === upload_urls.length - 1) setUploading(false);

          bytesRead += ev.total;
          setFileProgress(bytesRead);
          resolve(null);
        };

        xhr.send(files[i]);
      });
    }

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

      if (!logged_in) {
        // @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",
    });

    setLoading(false);
    setShowSuccess(true);
  }

  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>
        <FormControl isRequired>
          <FormLabel htmlFor="submissionType">Type of Report</FormLabel>
          <RadioGroup
            id="submissionType"
            onChange={setSubmissionType}
            value={submissionType}
          >
            <Stack spacing={2}>
              <Radio value="exploit">Exploiter</Radio>
              <Radio value="abuse">Server Configurator Abuse</Radio>
            </Stack>
          </RadioGroup>
        </FormControl>
        <br />
        <FormControl isRequired>
          <FormLabel>Your Evidence (Max size per file: 512MB)</FormLabel>
          <Input
            border="none"
            id="evidence"
            sx={{
              "::file-selector-button": {
                border: "none",
                outline: "none",
                ...useMultiStyleConfig("Button", {
                  colorScheme: "blue",
                }),
              },
            }}
            multiple={true}
            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 ? undefined : "none"}
            value={fileProgress}
          >
            <CircularProgressLabel>{fileProgress}%</CircularProgressLabel>
          </CircularProgress>
        </HStack>
      </Container>
      <script
        dangerouslySetInnerHTML={{
          __html: `
        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>
    </>
  );
}
