import { getFormProps, getInputProps, useForm } from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import {
  EnvelopeIcon,
  GlobeAltIcon,
  HashtagIcon,
  LockClosedIcon,
  XMarkIcon,
} from "@heroicons/react/20/solid";
import { BellAlertIcon } from "@heroicons/react/24/solid";
import { useFetcher, useNavigate } from "@remix-run/react";
import { SlackIcon } from "@trigger.dev/companyicons";
import { Fragment, useEffect, useRef, useState } from "react";
import { z } from "zod";
import { ExitIcon } from "~/assets/icons/ExitIcon";
import { InlineCode } from "~/components/code/InlineCode";
import { Button, LinkButton } from "~/components/primitives/Buttons";
import { Callout, variantClasses } from "~/components/primitives/Callout";
import { Fieldset } from "~/components/primitives/Fieldset";
import { FormError } from "~/components/primitives/FormError";
import { Header2, Header3 } from "~/components/primitives/Headers";
import { Hint } from "~/components/primitives/Hint";
import { Input } from "~/components/primitives/Input";
import { InputGroup } from "~/components/primitives/InputGroup";
import { Select, SelectItem } from "~/components/primitives/Select";
import { TextLink } from "~/components/primitives/TextLink";
import { useToast } from "~/components/primitives/Toast";
import { UnorderedList } from "~/components/primitives/UnorderedList";
import { useOptimisticLocation } from "~/hooks/useOptimisticLocation";
import { useOrganization } from "~/hooks/useOrganizations";
import type { ErrorAlertChannelData } from "~/presenters/v3/ErrorAlertChannelPresenter.server";
import { cn } from "~/utils/cn";
import { organizationSlackIntegrationPath } from "~/utils/pathBuilder";

export const ErrorAlertsFormSchema = z.object({
  emails: z.preprocess((i) => {
    if (typeof i === "string") return i === "" ? [] : [i];
    if (Array.isArray(i)) return i.filter((v) => typeof v === "string" && v !== "");
    return [];
  }, z.string().email().array()),
  slackChannel: z.string().optional(),
  slackIntegrationId: z.string().optional(),
  webhooks: z.preprocess((i) => {
    if (typeof i === "string") return i === "" ? [] : [i];
    if (Array.isArray(i)) return i.filter((v) => typeof v === "string" && v !== "");
    return [];
  }, z.string().url().array()),
});

type ConfigureErrorAlertsProps = ErrorAlertChannelData & {
  connectToSlackHref?: string;
  formAction: string;
};

export function ConfigureErrorAlerts({
  emails: existingEmails,
  webhooks: existingWebhooks,
  slackChannel: existingSlackChannel,
  slack,
  emailAlertsEnabled,
  connectToSlackHref,
  formAction,
}: ConfigureErrorAlertsProps) {
  const organization = useOrganization();
  const fetcher = useFetcher<{ ok?: boolean }>();
  const navigate = useNavigate();
  const toast = useToast();
  const location = useOptimisticLocation();
  const isSubmitting = fetcher.state !== "idle";

  const [selectedSlackChannelValue, setSelectedSlackChannelValue] = useState<string | undefined>(
    existingSlackChannel
      ? `${existingSlackChannel.channelId}/${existingSlackChannel.channelName}`
      : undefined
  );

  const selectedSlackChannel =
    slack.status === "READY"
      ? slack.channels?.find((s) => selectedSlackChannelValue === `${s.id}/${s.name}`)
      : undefined;

  const closeHref = (() => {
    const params = new URLSearchParams(location.search);
    params.delete("alerts");
    const qs = params.toString();
    return qs ? `?${qs}` : location.pathname;
  })();

  const hasHandledSuccess = useRef(false);
  useEffect(() => {
    if (fetcher.state === "idle" && fetcher.data?.ok && !hasHandledSuccess.current) {
      hasHandledSuccess.current = true;
      toast.success("Alert settings saved");
      navigate(closeHref, { replace: true });
    }
  }, [fetcher.state, fetcher.data, closeHref, navigate, toast]);

  const emailFieldValues = useRef<string[]>(
    existingEmails.length > 0 ? [...existingEmails.map((e) => e.email), ""] : [""]
  );

  const webhookFieldValues = useRef<string[]>(
    existingWebhooks.length > 0 ? [...existingWebhooks.map((w) => w.url), ""] : [""]
  );

  const [form, fields] = useForm<z.infer<typeof ErrorAlertsFormSchema>>({
    id: "configure-error-alerts",
    onValidate({ formData }) {
      return parseWithZod(formData, { schema: ErrorAlertsFormSchema });
    },
    shouldRevalidate: "onSubmit",
    defaultValue: {
      emails: emailFieldValues.current,
      webhooks: webhookFieldValues.current,
    },
  });
  const { emails, webhooks, slackChannel, slackIntegrationId } = fields;

  const emailFields = emails.getFieldList();
  const webhookFields = webhooks.getFieldList();

  return (
    <div className="grid h-full grid-rows-[auto_1fr_auto] overflow-hidden">
      <div className="flex items-center justify-between border-b border-grid-bright px-3 py-2">
        <Header2 className="flex items-center gap-2">
          <BellAlertIcon className="size-5 text-alerts" /> Configure alerts
        </Header2>
        <LinkButton
          to={closeHref}
          variant="minimal/small"
          TrailingIcon={ExitIcon}
          shortcut={{ key: "esc" }}
          shortcutPosition="before-trailing-icon"
          className="pl-1"
        />
      </div>

      <fetcher.Form method="post" action={formAction} {...getFormProps(form)} className="contents">
        <div className="flex-1 overflow-y-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-surface-control">
          <Fieldset className="flex flex-col gap-4 p-4">
            <div className="flex flex-col">
              <Header3>Receive alerts when</Header3>
              <UnorderedList variant="small/dimmed" className="mt-1">
                <li>An error is seen for the first time</li>
                <li>A resolved error re-occurs</li>
                <li>An ignored error re-occurs based on settings you configured</li>
              </UnorderedList>
            </div>

            {/* Email section */}
            <div>
              <Header3 className="mb-1">Email</Header3>
              {emailAlertsEnabled ? (
                <InputGroup>
                  {emailFields.map((emailField, index) => (
                    <Fragment key={emailField.key}>
                      <Input
                        {...getInputProps(emailField, { type: "email" })}
                        placeholder={index === 0 ? "Enter an email address" : "Add another email"}
                        icon={EnvelopeIcon}
                        onChange={(e) => {
                          emailFieldValues.current[index] = e.target.value;
                          if (
                            emailFields.length === emailFieldValues.current.length &&
                            emailFieldValues.current.every((v) => v !== "")
                          ) {
                            form.insert({ name: emails.name });
                          }
                        }}
                      />
                      <FormError id={emailField.errorId}>{emailField.errors}</FormError>
                    </Fragment>
                  ))}
                </InputGroup>
              ) : (
                <Callout variant="warning">
                  Email integration is not available. Please contact your organization
                  administrator.
                </Callout>
              )}
            </div>

            {/* Slack section */}
            <div>
              <Header3 className="mb-1">Slack</Header3>

              <InputGroup fullWidth>
                {slack.status === "READY" ? (
                  <>
                    <Select
                      name={slackChannel.name}
                      placeholder={<span className="text-text-dimmed">Select a Slack channel</span>}
                      heading="Filter channels…"
                      value={selectedSlackChannelValue ?? ""}
                      dropdownIcon
                      variant="tertiary/medium"
                      items={slack.channels}
                      setValue={(value) => {
                        typeof value === "string" && setSelectedSlackChannelValue(value);
                      }}
                      filter={(channel, search) =>
                        channel.name?.toLowerCase().includes(search.toLowerCase()) ?? false
                      }
                      text={(value) => {
                        const channel = slack.channels.find((s) => value === `${s.id}/${s.name}`);
                        if (!channel) return;
                        return (
                          <span className="text-text-bright">
                            <SlackChannelTitle {...channel} />
                          </span>
                        );
                      }}
                    >
                      {(matches) => (
                        <>
                          <SelectItem
                            value=""
                            className="border-b border-grid-bright text-text-dimmed"
                          >
                            <div className="flex items-center gap-1.5">
                              <XMarkIcon className="size-4" />
                              <span>No channel</span>
                            </div>
                          </SelectItem>
                          {matches?.map((channel) => (
                            <SelectItem
                              key={channel.id}
                              value={`${channel.id}/${channel.name}`}
                              className="text-text-bright"
                            >
                              <SlackChannelTitle {...channel} />
                            </SelectItem>
                          ))}
                        </>
                      )}
                    </Select>
                    {selectedSlackChannel && selectedSlackChannel.is_private && (
                      <Callout
                        variant="warning"
                        className={cn("text-sm", variantClasses.warning.textColor)}
                      >
                        To receive alerts in the{" "}
                        <InlineCode variant="extra-small">{selectedSlackChannel.name}</InlineCode>{" "}
                        channel, you need to invite the @Trigger.dev Slack Bot. Go to the channel in
                        Slack and type:{" "}
                        <InlineCode variant="extra-small">/invite @Trigger.dev</InlineCode>.
                      </Callout>
                    )}
                    <Hint>
                      <TextLink to={organizationSlackIntegrationPath(organization)}>
                        Manage Slack connection
                      </TextLink>
                    </Hint>
                    <input
                      type="hidden"
                      name={slackIntegrationId.name}
                      value={slack.integrationId}
                    />
                  </>
                ) : slack.status === "NOT_CONFIGURED" ? (
                  connectToSlackHref ? (
                    <LinkButton variant="tertiary/medium" to={connectToSlackHref} fullWidth>
                      <span className="flex items-center gap-2 text-text-bright">
                        <SlackIcon className="size-5" /> Connect to Slack
                      </span>
                    </LinkButton>
                  ) : (
                    <Callout variant="info">
                      Slack is not connected. Connect Slack from the{" "}
                      <span className="font-medium text-text-bright">Alerts</span> page to enable
                      Slack notifications.
                    </Callout>
                  )
                ) : slack.status === "TOKEN_REVOKED" || slack.status === "TOKEN_EXPIRED" ? (
                  connectToSlackHref ? (
                    <div className="flex flex-col gap-4">
                      <Callout variant="info">
                        The Slack integration in your workspace has been revoked or has expired.
                        Please re-connect your Slack workspace.
                      </Callout>
                      <LinkButton
                        variant="tertiary/large"
                        to={`${connectToSlackHref}?reinstall=true`}
                        fullWidth
                      >
                        <span className="flex items-center gap-2 text-text-bright">
                          <SlackIcon className="size-5" /> Connect to Slack
                        </span>
                      </LinkButton>
                    </div>
                  ) : (
                    <Callout variant="info">
                      The Slack integration in your workspace has been revoked or expired. Please
                      re-connect from the{" "}
                      <span className="font-medium text-text-bright">Alerts</span> page.
                    </Callout>
                  )
                ) : slack.status === "FAILED_FETCHING_CHANNELS" ? (
                  <Callout variant="warning">
                    Failed loading channels from Slack. Please try again later.
                  </Callout>
                ) : (
                  <Callout variant="warning">
                    Slack integration is not available. Please contact your organization
                    administrator.
                  </Callout>
                )}
              </InputGroup>
            </div>

            {/* Webhook section */}
            <div>
              <Header3 className="mb-1">Webhook</Header3>
              <InputGroup>
                {webhookFields.map((webhookField, index) => (
                  <Fragment key={webhookField.key}>
                    <Input
                      {...getInputProps(webhookField, { type: "url" })}
                      placeholder={
                        index === 0 ? "https://example.com/webhook" : "Add another webhook URL"
                      }
                      icon={GlobeAltIcon}
                      onChange={(e) => {
                        webhookFieldValues.current[index] = e.target.value;
                        if (
                          webhookFields.length === webhookFieldValues.current.length &&
                          webhookFieldValues.current.every((v) => v !== "")
                        ) {
                          form.insert({ name: webhooks.name });
                        }
                      }}
                    />
                    <FormError id={webhookField.errorId}>{webhookField.errors}</FormError>
                  </Fragment>
                ))}
                <Hint>We'll issue POST requests to these URLs with a JSON payload.</Hint>
              </InputGroup>
            </div>

            <FormError>{form.errors}</FormError>
          </Fieldset>
        </div>

        <div className="flex items-center justify-between border-t border-grid-bright px-3 py-3">
          <LinkButton variant="secondary/medium" to={closeHref}>
            Cancel
          </LinkButton>
          <Button
            variant="primary/medium"
            type="submit"
            disabled={isSubmitting}
            isLoading={isSubmitting}
          >
            {isSubmitting ? "Saving…" : "Save"}
          </Button>
        </div>
      </fetcher.Form>
    </div>
  );
}

function SlackChannelTitle({ name, is_private }: { name?: string; is_private?: boolean }) {
  return (
    <div className="flex items-center gap-1.5">
      {is_private ? <LockClosedIcon className="size-4" /> : <HashtagIcon className="size-4" />}
      <span>{name}</span>
    </div>
  );
}
