import { conform, useForm, type Submission } from "@conform-to/react";

import { parse } from "@conform-to/zod";
import { Form, useActionData } from "@remix-run/react";
import { useEffect, useMemo, useRef, useState } from "react";
import { z } from "zod";
import { AnimatedCallout } from "~/components/primitives/AnimatedCallout";
import { getBillingLimitMode } from "~/components/billing/billingAlertsFormat";
import { formatGracePeriodMs } from "~/components/billing/billingLimitFormat";
import { Button } from "~/components/primitives/Buttons";
import { CheckboxWithLabel } from "~/components/primitives/Checkbox";
import { Fieldset } from "~/components/primitives/Fieldset";
import { FormButtons } from "~/components/primitives/FormButtons";
import { FormError } from "~/components/primitives/FormError";
import { Header2 } from "~/components/primitives/Headers";
import { Input } from "~/components/primitives/Input";
import { InputGroup } from "~/components/primitives/InputGroup";
import { Paragraph } from "~/components/primitives/Paragraph";
import { RadioGroup, RadioGroupItem } from "~/components/primitives/RadioButton";
import type { BillingLimitResult } from "~/services/billingLimit.schemas";
import { cn } from "~/utils/cn";
import { formatCurrency } from "~/utils/numberFormatter";

export const billingLimitFormSchema = z.discriminatedUnion("mode", [
  z.object({
    mode: z.literal("none"),
    cancelInProgressRuns: z
      .preprocess((v) => v === "on" || v === true || v === "true", z.boolean())
      .optional(),
  }),
  z.object({
    mode: z.literal("plan"),
    cancelInProgressRuns: z
      .preprocess((v) => v === "on" || v === true || v === "true", z.boolean())
      .optional(),
  }),
  z.object({
    mode: z.literal("custom"),
    amount: z.coerce
      .number({ invalid_type_error: "Not a valid amount" })
      .positive("Amount must be greater than 0"),
    cancelInProgressRuns: z
      .preprocess((v) => v === "on" || v === true || v === "true", z.boolean())
      .optional(),
  }),
]);

type BillingLimitFormValue = z.infer<typeof billingLimitFormSchema>;

type BillingLimitActionData = {
  formIntent: "billing-limit";
  submission: Submission<BillingLimitFormValue>;
};

export function isBillingLimitFormDirty(input: {
  billingLimit: BillingLimitResult;
  mode: "none" | "plan" | "custom";
  customAmount: string;
  cancelInProgressRuns: boolean;
}): boolean {
  const needsInitialSave = !input.billingLimit.isConfigured;
  const savedMode = getBillingLimitMode(input.billingLimit);
  const savedCustomAmount =
    input.billingLimit.isConfigured && input.billingLimit.mode === "custom"
      ? (input.billingLimit.amountCents / 100).toFixed(2)
      : "";
  const savedCancelInProgressRuns =
    input.billingLimit.isConfigured && input.billingLimit.cancelInProgressRuns;

  const isLimitDirty =
    input.mode !== savedMode ||
    (input.mode === "custom" && input.customAmount !== savedCustomAmount);

  return (
    needsInitialSave || isLimitDirty || input.cancelInProgressRuns !== savedCancelInProgressRuns
  );
}

export function getBillingLimitFormLastSubmission(
  submission: BillingLimitActionData["submission"] | undefined,
  mode: "none" | "plan" | "custom",
  isDirty: boolean
) {
  if (!isDirty || !submission) {
    return undefined;
  }

  if (mode !== "custom" && submission.error?.amount) {
    const { amount: _amount, ...remainingErrors } = submission.error;
    return {
      ...submission,
      error: remainingErrors,
    };
  }

  return submission;
}

type BillingLimitConfigSectionProps = {
  billingLimit: BillingLimitResult;
  planLimitCents: number;
};

export function BillingLimitConfigSection({
  billingLimit,
  planLimitCents,
}: BillingLimitConfigSectionProps) {
  const gracePeriodLabel = formatGracePeriodMs(billingLimit.gracePeriodMs);

  const savedMode = getBillingLimitMode(billingLimit);
  const savedCustomAmount =
    billingLimit.isConfigured && billingLimit.mode === "custom"
      ? (billingLimit.amountCents / 100).toFixed(2)
      : "";
  const savedCancelInProgressRuns = billingLimit.isConfigured && billingLimit.cancelInProgressRuns;

  const [mode, setMode] = useState<"none" | "plan" | "custom">(savedMode);
  const [customAmount, setCustomAmount] = useState(savedCustomAmount);
  const [cancelInProgressRuns, setCancelInProgressRuns] = useState(savedCancelInProgressRuns);
  const customAmountInputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    setMode(savedMode);
    setCustomAmount(savedCustomAmount);
    setCancelInProgressRuns(savedCancelInProgressRuns);
  }, [savedMode, savedCustomAmount, savedCancelInProgressRuns]);

  function handleModeChange(value: string) {
    const nextMode = value as typeof mode;
    if (mode === "custom" && nextMode !== "custom") {
      setCustomAmount(savedCustomAmount);
    }
    setMode(nextMode);
    if (nextMode === "custom") {
      window.setTimeout(() => customAmountInputRef.current?.focus(), 0);
    }
  }

  const actionData = useActionData<BillingLimitActionData>();
  const limitSubmission =
    actionData?.formIntent === "billing-limit" ? actionData.submission : undefined;

  const needsInitialSave = !billingLimit.isConfigured;
  const isLimitDirty =
    mode !== savedMode || (mode === "custom" && customAmount !== savedCustomAmount);
  const isDirty = isBillingLimitFormDirty({
    billingLimit,
    mode,
    customAmount,
    cancelInProgressRuns,
  });
  const lastSubmission = useMemo(
    () => getBillingLimitFormLastSubmission(limitSubmission, mode, isDirty),
    [limitSubmission, mode, isDirty]
  );

  const [form, fields] = useForm({
    id: "billing-limit",
    lastSubmission: lastSubmission as any,
    shouldRevalidate: "onInput",
    onValidate({ formData }) {
      return parse(formData, { schema: billingLimitFormSchema });
    },
    defaultValue: {
      mode: savedMode,
    },
  });

  useEffect(() => {
    form.ref.current?.dispatchEvent(new Event("input", { bubbles: true }));
  }, [customAmount, form.ref, mode]);

  const planLimitLabel = formatCurrency(planLimitCents / 100, false);
  const showPlanInfoCallout = mode === "plan";
  const showCustomInfoCallout = mode === "custom";
  const showNoneWarningCallout = mode === "none";

  return (
    <div>
      <div className="mb-3 border-b border-grid-dimmed pb-3">
        <Header2 spacing>Billing limit</Header2>
        <Paragraph variant="small">
          Set a monthly compute spend limit for your organization. When the limit is reached,
          billable environments enter a grace period before new triggers are rejected.
        </Paragraph>
      </div>

      <Form method="post" {...form.props}>
        <input type="hidden" name="intent" value="billing-limit" />
        <Fieldset>
          <input type="hidden" name="mode" value={mode} />

          <RadioGroup value={mode} onValueChange={handleModeChange} className="flex flex-col gap-2">
            <div className="grid grid-cols-1 gap-2 lg:grid-cols-2 lg:items-start lg:gap-x-4 lg:gap-y-2">
              <RadioGroupItem
                id="limit_mode_plan"
                value="plan"
                variant="description"
                label={
                  <span className="inline-flex items-center">{`My plan limit (${planLimitLabel})`}</span>
                }
                description={`Pause billable environments when monthly spend reaches ${planLimitLabel}.`}
              />
              <div className="relative">
                <AnimatedCallout
                  show={showPlanInfoCallout}
                  variant="info"
                  className="absolute w-full text-sm"
                >
                  <LimitReachedCalloutContent
                    gracePeriodLabel={gracePeriodLabel}
                    cancelInProgressRuns={cancelInProgressRuns}
                  />
                </AnimatedCallout>
              </div>
            </div>

            <div className="grid grid-cols-1 gap-2 lg:grid-cols-2 lg:items-start lg:gap-x-4 lg:gap-y-2">
              <div className="flex flex-col gap-2">
                <RadioGroupItem
                  id="limit_mode_custom"
                  value="custom"
                  variant="description"
                  label="Custom limit"
                  description="Set your own monthly spend threshold."
                />
                {mode === "custom" && (
                  <InputGroup fullWidth className="mb-1 mt-0.5">
                    <Input
                      ref={customAmountInputRef}
                      id="custom-amount"
                      name="amount"
                      type="number"
                      step={0.01}
                      min={0.01}
                      value={customAmount}
                      onChange={(e) => setCustomAmount(e.target.value)}
                      placeholder="Custom limit amount"
                      icon={
                        <span className="-mt-0.5 block pl-1.5 pr-2 text-sm font-semibold text-text-dimmed">
                          $
                        </span>
                      }
                      className="pl-px"
                      fullWidth
                    />
                    {fields.amount && (
                      <FormError id={fields.amount.errorId}>{fields.amount.error}</FormError>
                    )}
                  </InputGroup>
                )}
              </div>
              <div className="relative">
                <AnimatedCallout
                  show={showCustomInfoCallout}
                  variant="info"
                  className="absolute w-full text-sm"
                >
                  <LimitReachedCalloutContent
                    gracePeriodLabel={gracePeriodLabel}
                    cancelInProgressRuns={cancelInProgressRuns}
                  />
                </AnimatedCallout>
              </div>
            </div>

            <div className="grid grid-cols-1 gap-2 lg:grid-cols-2 lg:items-start lg:gap-x-4 lg:gap-y-2">
              <RadioGroupItem
                id="limit_mode_none"
                value="none"
                variant="description"
                label="I don't want a billing limit"
                description="Runs continue without protection against runaway usage."
              />
              <div className="relative">
                <AnimatedCallout
                  show={showNoneWarningCallout}
                  variant="warning"
                  className="absolute w-full"
                >
                  Without a billing limit, runs will continue even if usage spikes unexpectedly. You
                  may have to pay higher fees before you notice.
                </AnimatedCallout>
              </div>
            </div>
          </RadioGroup>

          {mode !== "none" && (
            <CheckboxWithLabel
              className="mt-4"
              name="cancelInProgressRuns"
              id="cancel_in_progress_runs"
              value="on"
              variant="simple/small"
              label="Cancel in-progress runs when this limit is reached"
              defaultChecked={cancelInProgressRuns}
              onChange={setCancelInProgressRuns}
            />
          )}
          <FormButtons
            className={isDirty ? undefined : "invisible"}
            confirmButton={
              <Button type="submit" variant="primary/small" disabled={!isDirty}>
                Save billing limit
              </Button>
            }
          />
        </Fieldset>
      </Form>
    </div>
  );
}

function LimitReachedCalloutContent({
  gracePeriodLabel,
  cancelInProgressRuns,
}: {
  gracePeriodLabel: string;
  cancelInProgressRuns: boolean;
}) {
  return (
    <>
      When this limit is reached, queued runs will be held for {gracePeriodLabel}, then new triggers
      will be rejected until you increase or remove the limit. Limits are enforced with a short
      delay, so spend may briefly exceed the limit before grace begins. See our{" "}
      <a href="https://trigger.dev/terms" className="underline">
        terms
      </a>{" "}
      for refund policy details.
      {cancelInProgressRuns ? (
        <> In-progress runs will be cancelled when the limit is hit.</>
      ) : null}
    </>
  );
}
