import { useCallback, useState } from "react";
import { Form, useActionData, useLoaderData } from "react-router";
import {
  AppProvider as PolarisAppProvider,
  Autocomplete,
  Badge,
  Banner,
  Box,
  Button,
  Card,
  EmptyState,
  FormLayout,
  InlineStack,
  Layout,
  Page,
  ResourceList,
  Select,
  Tag,
  Text,
  TextField,
} from "@shopify/polaris";
import enTranslations from "@shopify/polaris/locales/en.json";
import { authenticate } from "../shopify.server";
import {
  listUpsellRules,
  replaceUpsellRules,
} from "../models.upsell-rules.server";

const PRODUCTS_QUERY = `#graphql
  query getProducts($first: Int!) {
    products(first: $first, query: "status:active") {
      edges {
        node {
          id
          title
          variants(first: 50) {
            edges {
              node {
                id
                title
              }
            }
          }
        }
      }
    }
  }
`;

const VARIANT_PRODUCTS_QUERY = `#graphql
  query getVariantProducts($ids: [ID!]!) {
    nodes(ids: $ids) {
      ... on ProductVariant {
        id
        product {
          id
        }
      }
    }
  }
`;

function createEmptyRule(index) {
  return {
    name: `Rule ${index}`,
    status: "ACTIVE",
    matchType: "ANY",
    priority: "0",
    exitIfMatched: false,
    triggerVariantIds: [],
    upsellVariantIds: [],
    triggerSearchValue: "",
    upsellSearchValue: "",
    expanded: true,
  };
}

export const loader = async ({ request }) => {
  const { session, admin } = await authenticate.admin(request);

  const rules = await listUpsellRules(session.shop);

  const response = await admin.graphql(PRODUCTS_QUERY, {
    variables: { first: 100 },
  });
  const json = await response.json();
  const products =
    json?.data?.products?.edges?.map(({ node }) => ({
      id: node.id,
      title: node.title,
      variants:
        node.variants?.edges?.map(({ node: variant }) => ({
          id: variant.id,
          title: variant.title,
        })) ?? [],
    })) ?? [];

  return {
    rules,
    products,
  };
};

export const action = async ({ request }) => {
  const { session, admin } = await authenticate.admin(request);
  const formData = await request.formData();
  const rulesJson = formData.get("rulesJson");

  if (!rulesJson) {
    return { error: "No rules submitted." };
  }

  let rulesPayload;
  try {
    rulesPayload = JSON.parse(rulesJson.toString());
  } catch {
    return { error: "Invalid rules data." };
  }

  if (!Array.isArray(rulesPayload) || rulesPayload.length === 0) {
    return { error: "Please add at least one rule." };
  }

  for (const [index, rule] of rulesPayload.entries()) {
    if (!rule.name || !rule.name.toString().trim()) {
      return { error: `Rule ${index + 1} is missing a name.` };
    }
  }

  const allVariantIds = Array.from(
    new Set(
      rulesPayload.flatMap((rule) => [
        ...(Array.isArray(rule.triggerVariantIds) ? rule.triggerVariantIds : []),
        ...(Array.isArray(rule.upsellVariantIds) ? rule.upsellVariantIds : []),
      ]),
    ),
  ).filter((id) => typeof id === "string" && id.trim().length > 0);

  const variantToProductId = new Map();

  if (allVariantIds.length) {
    const variantResponse = await admin.graphql(VARIANT_PRODUCTS_QUERY, {
      variables: { ids: allVariantIds },
    });
    const variantJson = await variantResponse.json();
    const nodes = Array.isArray(variantJson?.data?.nodes)
      ? variantJson.data.nodes
      : [];

    for (const node of nodes) {
      const variantId = typeof node?.id === "string" ? node.id : "";
      const productId =
        typeof node?.product?.id === "string" ? node.product.id : "";

      if (variantId && productId) {
        variantToProductId.set(variantId, productId);
      }
    }
  }

  const normalizedRules = rulesPayload.map((rawRule) => {
    const name = rawRule.name.toString().trim();
    const status = rawRule.status === "DISABLED" ? "DISABLED" : "ACTIVE";
    const matchType = rawRule.matchType === "ALL" ? "ALL" : "ANY";
    const priority = Number(rawRule.priority ?? 0) || 0;
    const exitIfMatched = Boolean(rawRule.exitIfMatched);

    const triggerVariantIds = Array.isArray(rawRule.triggerVariantIds)
      ? rawRule.triggerVariantIds
      : [];
    const upsellVariantIds = Array.isArray(rawRule.upsellVariantIds)
      ? rawRule.upsellVariantIds
      : [];

    const triggerProducts = triggerVariantIds
      .map((variantId) => ({
        productId: variantToProductId.get(variantId) ?? "",
        variantId,
      }))
      .filter((entry) => entry.productId && entry.variantId);

    const upsellProducts = upsellVariantIds
      .map((variantId) => ({
        productId: variantToProductId.get(variantId) ?? "",
        variantId,
        discountType: null,
        discountValue: null,
        customMessage: null,
      }))
      .filter((entry) => entry.productId && entry.variantId);

    return {
      shop: session.shop,
      name,
      status,
      matchType,
      priority,
      exitIfMatched,
      triggerProducts,
      upsellProducts,
    };
  });

  await replaceUpsellRules(session.shop, normalizedRules);

  return {
    success: true,
    message: "Upsell rules saved successfully.",
  };
};

export default function UpsellRulesPage() {
  const { rules, products } = useLoaderData();
  const actionData = useActionData();

  const [rulesState, setRulesState] = useState(() => {
    if (Array.isArray(rules) && rules.length > 0) {
      return rules.map((rule, index) => ({
        name: rule.name ?? `Rule ${index + 1}`,
        status: rule.status ?? "ACTIVE",
        matchType: rule.match_type ?? "ANY",
        priority: String(rule.priority ?? 0),
        exitIfMatched: Boolean(rule.exit_if_matched),
        triggerVariantIds: (rule.triggerProducts ?? [])
          .map((p) => p.variant_id)
          .filter(Boolean),
        upsellVariantIds: (rule.upsellProducts ?? [])
          .map((p) => p.variant_id)
          .filter(Boolean),
        triggerSearchValue: "",
        upsellSearchValue: "",
        expanded: true,
      }));
    }

    return [createEmptyRule(1)];
  });

  const [dragIndex, setDragIndex] = useState(null);
  const [lastMovedIndex, setLastMovedIndex] = useState(null);

  const variantOptions = (products || []).flatMap((product) =>
    (product.variants || []).map((variant) => ({
      value: variant.id,
      label:
        variant.title === "Default Title"
          ? product.title
          : `${product.title} / ${variant.title}`,
    })),
  );

  const getFilteredOptions = useCallback(
    (searchValue) =>
      searchValue.trim()
        ? variantOptions.filter((option) =>
            option.label.toLowerCase().includes(searchValue.toLowerCase()),
          )
        : variantOptions,
    [variantOptions],
  );

  const getVariantLabel = useCallback(
    (id) => variantOptions.find((option) => option.value === id)?.label ?? id,
    [variantOptions],
  );

  const updateRuleField = useCallback((index, field, value) => {
    setRulesState((prev) =>
      prev.map((rule, ruleIndex) =>
        ruleIndex === index ? { ...rule, [field]: value } : rule,
      ),
    );
  }, []);

  const updateRuleVariants = useCallback((index, field, selectedIds) => {
    setRulesState((prev) =>
      prev.map((rule, ruleIndex) =>
        ruleIndex === index ? { ...rule, [field]: selectedIds } : rule,
      ),
    );
  }, []);

  const addRule = useCallback(() => {
    setRulesState((prev) => [...prev, createEmptyRule(prev.length + 1)]);
  }, []);

  const removeRule = useCallback((index) => {
    setRulesState((prev) =>
      prev.length <= 1 ? prev : prev.filter((_, ruleIndex) => ruleIndex !== index),
    );
  }, []);

  const removeTriggerTag = useCallback((ruleIndex, id) => {
    setRulesState((prev) =>
      prev.map((rule, index) =>
        index === ruleIndex
          ? {
              ...rule,
              triggerVariantIds: rule.triggerVariantIds.filter(
                (value) => value !== id,
              ),
            }
          : rule,
      ),
    );
  }, []);

  const removeUpsellTag = useCallback((ruleIndex, id) => {
    setRulesState((prev) =>
      prev.map((rule, index) =>
        index === ruleIndex
          ? {
              ...rule,
              upsellVariantIds: rule.upsellVariantIds.filter(
                (value) => value !== id,
              ),
            }
          : rule,
      ),
    );
  }, []);

  const moveRule = useCallback((fromIndex, toIndex) => {
    setRulesState((prev) => {
      if (toIndex < 0 || toIndex >= prev.length) return prev;
      const next = [...prev];
      const [moved] = next.splice(fromIndex, 1);
      next.splice(toIndex, 0, moved);
      return next;
    });
    setLastMovedIndex(toIndex);
    setTimeout(() => {
      setLastMovedIndex((current) => (current === toIndex ? null : current));
    }, 400);
  }, []);

  const rulesPayload = rulesState.map((rule, index) => ({
    name: rule.name,
    status: rule.status,
    matchType: rule.matchType,
    priority: index,
    exitIfMatched: rule.exitIfMatched,
    triggerVariantIds: rule.triggerVariantIds,
    upsellVariantIds: rule.upsellVariantIds,
  }));

  return (
    <PolarisAppProvider i18n={enTranslations}>
      <Page title="Upsell rules">
        <Layout>
          <Layout.Section>
            <Card sectioned>
              <Form method="post">
                <FormLayout>
                  <input
                    type="hidden"
                    name="rulesJson"
                    value={JSON.stringify(rulesPayload)}
                  />

                  {actionData?.message ? (
                    <Banner tone={actionData.success ? "success" : "critical"}>
                      <p>{actionData.message}</p>
                    </Banner>
                  ) : null}

                  {rulesState.map((rule, index) => {
                    const triggerOptions = getFilteredOptions(
                      rule.triggerSearchValue,
                    );
                    const upsellOptions = getFilteredOptions(
                      rule.upsellSearchValue,
                    );

                    const handleDragStart = (event) => {
                      event.dataTransfer.effectAllowed = "move";
                      setDragIndex(index);
                    };

                    const handleDragOver = (event) => {
                      event.preventDefault();
                      event.dataTransfer.dropEffect = "move";
                    };

                    const handleDrop = (event) => {
                      event.preventDefault();
                      if (dragIndex == null || dragIndex === index) return;
                      moveRule(dragIndex, index);
                      setDragIndex(null);
                    };

                    const handleDragEnd = () => {
                      setDragIndex(null);
                    };

                    return (
                      <Box
                        key={index}
                        draggable
                        onDragStart={handleDragStart}
                        onDragOver={handleDragOver}
                        onDrop={handleDrop}
                        onDragEnd={handleDragEnd}
                        style={{
                          cursor: "grab",
                          opacity: dragIndex === index ? 0.6 : 1,
                          marginBottom: "12px",
                          transition:
                            "background-color 150ms ease, box-shadow 150ms ease, transform 150ms ease",
                          backgroundColor:
                            lastMovedIndex === index
                              ? "rgba(0, 128, 255, 0.06)"
                              : "transparent",
                          boxShadow:
                            lastMovedIndex === index
                              ? "0 0 0 1px rgba(0, 128, 255, 0.35)"
                              : "none",
                          transform:
                            lastMovedIndex === index
                              ? "translateY(-2px) scale(1.01)"
                              : "none",
                        }}
                      >
                        <Card sectioned>
                          <InlineStack
                            align="space-between"
                            blockAlign="center"
                            paddingBlockEnd="200"
                          >
                            <InlineStack gap="200" blockAlign="center">
                              <Text as="h2" variant="headingSm">
                                Rule {index + 1}
                              </Text>
                              <Text as="span" tone="subdued">
                                (Priority: {index})
                              </Text>
                            </InlineStack>
                            <InlineStack gap="200">
                              <Button
                                size="slim"
                                variant="tertiary"
                                onClick={() =>
                                  updateRuleField(index, "expanded", !rule.expanded)
                                }
                              >
                                {rule.expanded ? "Collapse" : "Expand"}
                              </Button>
                              {rulesState.length > 1 && (
                                <>
                                  <Button
                                    size="slim"
                                    variant="tertiary"
                                    onClick={() => moveRule(index, index - 1)}
                                    disabled={index === 0}
                                  >
                                    Move up
                                  </Button>
                                  <Button
                                    size="slim"
                                    variant="tertiary"
                                    onClick={() => moveRule(index, index + 1)}
                                    disabled={index === rulesState.length - 1}
                                  >
                                    Move down
                                  </Button>
                                  <Button
                                    variant="tertiary"
                                    tone="critical"
                                    onClick={() => removeRule(index)}
                                  >
                                    Remove
                                  </Button>
                                </>
                              )}
                            </InlineStack>
                          </InlineStack>

                          {rule.expanded && (
                            <FormLayout>
                              <TextField
                                label="Rule name"
                                value={rule.name}
                                onChange={(value) =>
                                  updateRuleField(index, "name", value)
                                }
                                autoComplete="off"
                                requiredIndicator
                              />

                              <Select
                                label="Status"
                                options={[
                                  { label: "Active", value: "ACTIVE" },
                                  { label: "Disabled", value: "DISABLED" },
                                ]}
                                value={rule.status}
                                onChange={(value) =>
                                  updateRuleField(index, "status", value)
                                }
                              />

                              <Select
                                label="Match type"
                                options={[
                                  { label: "Any", value: "ANY" },
                                  { label: "All", value: "ALL" },
                                ]}
                                value={rule.matchType}
                                onChange={(value) =>
                                  updateRuleField(index, "matchType", value)
                                }
                              />

                              <Text as="span" tone="subdued">
                                Priority is set automatically by the rule order
                                above.
                              </Text>

                              <Box paddingBlockEnd="400">
                                {rule.triggerVariantIds.length > 0 && (
                                  <Box paddingBlockEnd="200">
                                    <InlineStack gap="200" wrap>
                                      {rule.triggerVariantIds.map((id) => (
                                        <Tag
                                          key={`trigger-${index}-${id}`}
                                          onRemove={() =>
                                            removeTriggerTag(index, id)
                                          }
                                        >
                                          {getVariantLabel(id)}
                                        </Tag>
                                      ))}
                                    </InlineStack>
                                  </Box>
                                )}
                                <Autocomplete
                                  options={triggerOptions}
                                  selected={rule.triggerVariantIds}
                                  onSelect={(selected) =>
                                    updateRuleVariants(
                                      index,
                                      "triggerVariantIds",
                                      selected,
                                    )
                                  }
                                  allowMultiple
                                  textField={
                                    <Autocomplete.TextField
                                      label="Trigger variants"
                                      value={rule.triggerSearchValue}
                                      onChange={(value) =>
                                        updateRuleField(
                                          index,
                                          "triggerSearchValue",
                                          value,
                                        )
                                      }
                                      placeholder="Search and select variants"
                                      helpText="When these variants are in the cart, the upsell can show."
                                    />
                                  }
                                  emptyState="No variants found"
                                />
                                <InlineStack gap="200" paddingBlockStart="200">
                                  <Button
                                    size="slim"
                                    onClick={() =>
                                      updateRuleVariants(
                                        index,
                                        "triggerVariantIds",
                                        variantOptions.map((option) => option.value),
                                      )
                                    }
                                  >
                                    Select all variants
                                  </Button>
                                  <Button
                                    size="slim"
                                    tone="critical"
                                    onClick={() =>
                                      updateRuleVariants(
                                        index,
                                        "triggerVariantIds",
                                        [],
                                      )
                                    }
                                  >
                                    Clear
                                  </Button>
                                </InlineStack>
                              </Box>

                              <Box paddingBlockEnd="400">
                                {rule.upsellVariantIds.length > 0 && (
                                  <Box paddingBlockEnd="200">
                                    <InlineStack gap="200" wrap>
                                      {rule.upsellVariantIds.map((id) => (
                                        <Tag
                                          key={`upsell-${index}-${id}`}
                                          onRemove={() =>
                                            removeUpsellTag(index, id)
                                          }
                                        >
                                          {getVariantLabel(id)}
                                        </Tag>
                                      ))}
                                    </InlineStack>
                                  </Box>
                                )}
                                <Autocomplete
                                  options={upsellOptions}
                                  selected={rule.upsellVariantIds}
                                  onSelect={(selected) =>
                                    updateRuleVariants(
                                      index,
                                      "upsellVariantIds",
                                      selected,
                                    )
                                  }
                                  allowMultiple
                                  textField={
                                    <Autocomplete.TextField
                                      label="Upsell variants"
                                      value={rule.upsellSearchValue}
                                      onChange={(value) =>
                                        updateRuleField(
                                          index,
                                          "upsellSearchValue",
                                          value,
                                        )
                                      }
                                      placeholder="Search and select variants"
                                      helpText="These variants will be suggested when the rule triggers."
                                    />
                                  }
                                  emptyState="No variants found"
                                />
                                <InlineStack gap="200" paddingBlockStart="200">
                                  <Button
                                    size="slim"
                                    onClick={() =>
                                      updateRuleVariants(
                                        index,
                                        "upsellVariantIds",
                                        variantOptions.map((option) => option.value),
                                      )
                                    }
                                  >
                                    Select all variants
                                  </Button>
                                  <Button
                                    size="slim"
                                    tone="critical"
                                    onClick={() =>
                                      updateRuleVariants(
                                        index,
                                        "upsellVariantIds",
                                        [],
                                      )
                                    }
                                  >
                                    Clear
                                  </Button>
                                </InlineStack>
                              </Box>

                              <InlineStack blockAlign="center" gap="200">
                                <Text as="span" tone="subdued">
                                  Exit if matched
                                </Text>
                                <Button
                                  size="slim"
                                  variant={
                                    rule.exitIfMatched ? "primary" : "secondary"
                                  }
                                  onClick={() =>
                                    updateRuleField(
                                      index,
                                      "exitIfMatched",
                                      !rule.exitIfMatched,
                                    )
                                  }
                                >
                                  {rule.exitIfMatched ? "Yes" : "No"}
                                </Button>
                              </InlineStack>
                            </FormLayout>
                          )}
                        </Card>
                      </Box>
                    );
                  })}

                  <InlineStack gap="200" align="space-between">
                    <Button variant="tertiary" onClick={addRule}>
                      Add another rule
                    </Button>
                    <Button submit primary>
                      Save all rules
                    </Button>
                  </InlineStack>
                </FormLayout>
              </Form>
            </Card>
          </Layout.Section>

          <Layout.Section>
            <Card>
              {rules.length === 0 ? (
                <EmptyState
                  heading="No upsell rules yet"
                  action={{ content: "Create your first rule" }}
                  secondaryAction={{
                    content: "Learn more",
                    url: "https://shopify.dev",
                  }}
                >
                  <p>
                    Create rules that show upsell variants when specific variants
                    are in the cart.
                  </p>
                </EmptyState>
              ) : (
                <ResourceList
                  resourceName={{ singular: "rule", plural: "rules" }}
                  items={rules}
                  renderItem={(rule) => {
                    const { id, name, status, match_type, priority } = rule;
                    return (
                      <ResourceList.Item id={id}>
                        <Text as="span" fontWeight="bold">
                          {name}
                        </Text>
                        <div style={{ marginTop: 4 }}>
                          <Badge status={status === "ACTIVE" ? "success" : "info"}>
                            {status}
                          </Badge>{" "}
                          <Badge>
                            {match_type === "ANY"
                              ? "Any trigger variant"
                              : "All trigger variants"}
                          </Badge>{" "}
                          <Text as="span" tone="subdued">
                            Priority: {priority}
                          </Text>
                        </div>
                      </ResourceList.Item>
                    );
                  }}
                />
              )}
            </Card>
          </Layout.Section>
        </Layout>
      </Page>
    </PolarisAppProvider>
  );
}
