import { useMemo, useState } from "react";
import { Form, useActionData, useLoaderData } from "react-router";
import {
  AppProvider as PolarisAppProvider,
  Autocomplete,
  Banner,
  BlockStack,
  Box,
  Button,
  Card,
  InlineGrid,
  InlineStack,
  Page,
  Select,
  Tag,
  Text,
  TextField,
} from "@shopify/polaris";
import enTranslations from "@shopify/polaris/locales/en.json";
import { authenticate } from "../shopify.server";
import {
  listFreeGiftRules,
  replaceFreeGiftRules,
} from "../models.free-gift-rules.server";
import {
  buildProductMapping,
  DEFAULT_ERROR_MESSAGE,
  getFreeGiftConfig,
  syncFreeGiftRulesMetafield,
} from "../utils.free-gift-rules.server";

const PRODUCTS_QUERY = `#graphql
  query FreeGiftProducts($first: Int!, $after: String) {
    products(first: $first, after: $after) {
      pageInfo {
        hasNextPage
        endCursor
      }
      edges {
        node {
          id
          title
          variants(first: 100) {
            edges {
              node {
                id
                title
              }
            }
          }
        }
      }
    }
  }
`;

const PRODUCT_VARIANTS_QUERY = `#graphql
  query FreeGiftProductBVariants($first: Int!, $after: String) {
    productVariants(first: $first, after: $after) {
      pageInfo {
        hasNextPage
        endCursor
      }
      edges {
        node {
          id
          title
          product {
            id
            title
          }
        }
      }
    }
  }
`;

function createEmptyRow() {
  return {
    sourceProductId: "",
    sourceVariantId: "",
    sourceSearchValue: "",
    giftSelections: [],
    giftSearchValue: "",
  };
}

async function fetchAllProducts(admin) {
  const products = [];
  let after = null;

  do {
    const response = await admin.graphql(PRODUCTS_QUERY, {
      variables: { first: 250, after },
    });
    const json = await response.json();
    const connection = json?.data?.products;

    products.push(
      ...(connection?.edges?.map(({ node }) => ({
        id: node.id,
        title: node.title,
        variants:
          node.variants?.edges?.map(({ node: variant }) => ({
            id: variant.id,
            title: variant.title,
          })) ?? [],
      })) ?? []),
    );

    after = connection?.pageInfo?.hasNextPage
      ? connection.pageInfo.endCursor
      : null;
  } while (after);

  return products;
}

async function fetchAllProductBVariantOptions(admin) {
  const variantOptions = [];
  let after = null;

  do {
    const response = await admin.graphql(PRODUCT_VARIANTS_QUERY, {
      variables: { first: 250, after },
    });
    const json = await response.json();
    const connection = json?.data?.productVariants;

    variantOptions.push(
      ...(connection?.edges?.map(({ node }) => ({
        value: node.id,
        label:
          node.title === "Default Title"
            ? node.product?.title ?? node.id
            : `${node.product?.title ?? "Untitled product"} / ${node.title}`,
      })) ?? []),
    );

    after = connection?.pageInfo?.hasNextPage
      ? connection.pageInfo.endCursor
      : null;
  } while (after);

  return variantOptions;
}

function groupRulesBySource(rules, products) {
  const productByVariantId = new Map();
  const productTitleById = new Map();

  for (const product of products) {
    productTitleById.set(product.id, product.title);
    for (const variant of product.variants) {
      productByVariantId.set(variant.id, product.id);
    }
  }

  const grouped = new Map();

  for (const rule of rules) {
    const sourceVariantId = rule.sourceVariantId;
    const giftVariantId = rule.giftVariantId;
    const quantityMultiplier = Number.isFinite(rule.quantityMultiplier)
      ? Math.max(1, Math.trunc(rule.quantityMultiplier))
      : 1;

    if (!sourceVariantId || !giftVariantId) continue;

    if (!grouped.has(sourceVariantId)) {
      const sourceProductId = productByVariantId.get(sourceVariantId) ?? "";
      grouped.set(sourceVariantId, {
        sourceProductId,
        sourceVariantId,
        sourceSearchValue: productTitleById.get(sourceProductId) ?? "",
        giftSelections: [],
        giftSearchValue: "",
      });
    }

    const row = grouped.get(sourceVariantId);
    if (!row.giftSelections.some((selection) => selection.variantId === giftVariantId)) {
      row.giftSelections.push({
        variantId: giftVariantId,
        quantityMultiplier,
      });
    }
  }

  return grouped.size > 0 ? Array.from(grouped.values()) : [createEmptyRow()];
}

export const loader = async ({ request }) => {
  const { session, admin } = await authenticate.admin(request);
  const [rules, config, products, giftVariantOptions] = await Promise.all([
    listFreeGiftRules(session.shop),
    getFreeGiftConfig(admin),
    fetchAllProducts(admin),
    fetchAllProductBVariantOptions(admin),
  ]);

  return {
    rules: groupRulesBySource(rules, products),
    productMapping: Object.keys(config.productMapping).length
      ? config.productMapping
      : buildProductMapping(rules),
    errorMessage: config.errorMessage,
    products,
    giftVariantOptions,
  };
};

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

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

  let payload;
  try {
    payload = JSON.parse(rulesJson.toString());
  } catch {
    return { status: "error", message: "Rules payload is not valid JSON." };
  }

  if (!Array.isArray(payload)) {
    return { status: "error", message: "Rules payload must be an array." };
  }

  const errorMessage =
    typeof errorMessageValue === "string" && errorMessageValue.trim()
      ? errorMessageValue.trim()
      : DEFAULT_ERROR_MESSAGE;

  try {
    const rules = payload.flatMap((rule, index) => {
      const source =
        typeof rule?.sourceVariantId === "string"
          ? rule.sourceVariantId.trim()
          : "";
      const giftSelections = Array.isArray(rule?.giftSelections)
        ? rule.giftSelections.flatMap((value) => {
            if (!value || typeof value !== "object" || Array.isArray(value)) {
              return [];
            }

            const variantId =
              typeof value.variantId === "string" ? value.variantId.trim() : "";
            const quantityMultiplier = Number.isFinite(value.quantityMultiplier)
              ? Math.max(1, Math.trunc(value.quantityMultiplier))
              : 1;

            return variantId ? [{ variantId, quantityMultiplier }] : [];
          })
        : [];

      if (!source && giftSelections.length === 0) return [];

      if (!source || giftSelections.length === 0) {
        throw new Error(
          `Rule ${index + 1} requires Product A and at least one Product B variant.`,
        );
      }

      return giftSelections.map((selection) => ({
        source,
        gift: selection.variantId,
        quantityMultiplier: selection.quantityMultiplier,
      }));
    });

    await replaceFreeGiftRules(session.shop, rules);
    const config = await syncFreeGiftRulesMetafield(admin, rules, errorMessage);

    return {
      status: "success",
      message: "Free gift rules saved successfully.",
      productMapping: config.productMapping,
      errorMessage: config.errorMessage,
    };
  } catch (error) {
    return {
      status: "error",
      message:
        error instanceof Error
          ? error.message
          : "Unable to save free gift rules.",
    };
  }
};

export default function FreeGiftRulesPage() {
  const { rules, productMapping, errorMessage, products, giftVariantOptions } =
    useLoaderData();
  const actionData = useActionData();
  const [rows, setRows] = useState(rules);
  const [message, setMessage] = useState(errorMessage);

  const productOptions = useMemo(
    () =>
      products.map((product) => ({
        value: product.id,
        label: product.title,
      })),
    [products],
  );

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

  const effectiveMapping =
    actionData?.status === "success"
      ? actionData.productMapping
      : productMapping;
  const effectiveErrorMessage =
    actionData?.status === "success" && actionData.errorMessage
      ? actionData.errorMessage
      : message;
  const findProduct = (productId) =>
    products.find((product) => product.id === productId) ?? null;

  const getVariantLabel = (variantId) =>
    productBVariantOptions.find((option) => option.value === variantId)?.label ??
    productAVariantOptions.find((option) => option.value === variantId)?.label ??
    variantId;

  const getFilteredProductOptions = (searchValue) => {
    const normalizedSearch = searchValue.trim().toLowerCase();
    if (!normalizedSearch) return productOptions;

    return productOptions.filter((option) =>
      option.label.toLowerCase().includes(normalizedSearch),
    );
  };

  const getFilteredVariantOptions = (searchValue) => {
    const normalizedSearch = searchValue.trim().toLowerCase();
    if (!normalizedSearch) return productBVariantOptions;

    return productBVariantOptions.filter((option) =>
      option.label.toLowerCase().includes(normalizedSearch),
    );
  };

  const updateRow = (index, updates) => {
    setRows((currentRows) =>
      currentRows.map((row, rowIndex) =>
        rowIndex === index ? { ...row, ...updates } : row,
      ),
    );
  };

  const addRow = () => {
    setRows((currentRows) => [...currentRows, createEmptyRow()]);
  };

  const removeRow = (index) => {
    setRows((currentRows) =>
      currentRows.length === 1
        ? [createEmptyRow()]
        : currentRows.filter((_, rowIndex) => rowIndex !== index),
    );
  };

  const updateSourceProductSelection = (index, selectedProductId) => {
    const selectedProduct = findProduct(selectedProductId);
    updateRow(index, {
      sourceProductId: selectedProductId,
      sourceVariantId: "",
      sourceSearchValue: selectedProduct?.title ?? "",
    });
  };

  const removeGiftVariant = (index, variantId) => {
    const row = rows[index];
    updateRow(index, {
      giftSelections: row.giftSelections.filter(
        (selection) => selection.variantId !== variantId,
      ),
    });
  };

  const updateGiftSelections = (index, selectedVariantIds) => {
    const row = rows[index];
    updateRow(index, {
      giftSelections: selectedVariantIds.map((variantId) => {
        const existing = row.giftSelections.find(
          (selection) => selection.variantId === variantId,
        );

        return existing ?? { variantId, quantityMultiplier: 1 };
      }),
      giftSearchValue: "",
    });
  };

  const updateGiftQuantityMultiplier = (index, variantId, value) => {
    const parsedValue = Number.parseInt(value, 10);
    const quantityMultiplier = Number.isFinite(parsedValue) && parsedValue > 0 ? parsedValue : 1;

    updateRow(index, {
      giftSelections: rows[index].giftSelections.map((selection) =>
        selection.variantId === variantId
          ? { ...selection, quantityMultiplier }
          : selection,
      ),
    });
  };

  return (
    <PolarisAppProvider i18n={enTranslations}>
      <Page
        title="Free Gift Rules"
        subtitle="Choose one Product A variant and multiple Product B variants in each rule."
        primaryAction={{ content: "Add mapping", onAction: addRow }}
      >
        <BlockStack gap="500">
          {actionData?.message ? (
            <Banner
              tone={actionData.status === "success" ? "success" : "critical"}
            >
              <p>{actionData.message}</p>
            </Banner>
          ) : null}

          <Card>
            <BlockStack gap="400">
              <Text as="h2" variant="headingMd">
                Mapping Rules
              </Text>
              <Text as="p" tone="subdued">
                Select one qualifying Product A variant, then choose one or more
                free-gift Product B variants from the multi-select list.
              </Text>

              <Form method="post">
                <input
                  type="hidden"
                  name="rulesJson"
                  value={JSON.stringify(rows)}
                />
                <input type="hidden" name="errorMessage" value={message} />
                <BlockStack gap="300">
                  <TextField
                    label="Checkout error message"
                    autoComplete="off"
                    value={message}
                    onChange={setMessage}
                    helpText="This message is shown when a free gift is added without its qualifying product."
                  />

                  {rows.map((row, index) => {
                    const sourceProduct = findProduct(row.sourceProductId);
                    const sourceVariantOptions = [
                      { label: "Select variant", value: "" },
                      ...(sourceProduct?.variants ?? []).map((variant) => ({
                        label: variant.title,
                        value: variant.id,
                      })),
                    ];

                    return (
                      <Card
                        key={`free-gift-rule-${index}`}
                        background="bg-surface-secondary"
                      >
                        <BlockStack gap="300">
                          <InlineGrid
                            columns={{ xs: 1, md: "1fr 1fr auto" }}
                            gap="300"
                          >
                            <Autocomplete
                              options={getFilteredProductOptions(
                                row.sourceSearchValue,
                              )}
                              selected={
                                row.sourceProductId ? [row.sourceProductId] : []
                              }
                              onSelect={(selected) =>
                                updateSourceProductSelection(
                                  index,
                                  selected[0] ?? "",
                                )
                              }
                              textField={
                                <Autocomplete.TextField
                                  label="Product A"
                                  value={row.sourceSearchValue}
                                  autoComplete="off"
                                  placeholder="Search product"
                                  onChange={(value) =>
                                    updateRow(index, {
                                      sourceSearchValue: value,
                                    })
                                  }
                                />
                              }
                            />

                            <Select
                              label="Product A variant"
                              options={sourceVariantOptions}
                              value={row.sourceVariantId}
                              disabled={!row.sourceProductId}
                              onChange={(value) =>
                                updateRow(index, { sourceVariantId: value })
                              }
                            />

                            <Box paddingBlockStart={{ xs: "400", md: "600" }}>
                              <Button
                                destructive
                                onClick={() => removeRow(index)}
                              >
                                Remove
                              </Button>
                            </Box>
                          </InlineGrid>

                          <Autocomplete
                            allowMultiple
                            options={getFilteredVariantOptions(
                              row.giftSearchValue,
                            )}
                            selected={row.giftSelections.map((selection) => selection.variantId)}
                            onSelect={(selected) => updateGiftSelections(index, selected)}
                            textField={
                              <Autocomplete.TextField
                                label="Product B variants"
                                value={row.giftSearchValue}
                                autoComplete="off"
                                placeholder="Search Product B variants"
                                onChange={(value) =>
                                  updateRow(index, { giftSearchValue: value })
                                }
                              />
                            }
                          />

                          <InlineStack gap="200">
                            {/* {row.sourceVariantId ? (
                              <Tag onRemove={() => updateRow(index, { sourceVariantId: "" })}>
                                {`${row.sourceSearchValue || "Product A"}: ${
                                  sourceProduct?.variants.find(
                                    (variant) => variant.id === row.sourceVariantId,
                                  )?.title || "Variant selected"
                                }`}
                              </Tag>
                            ) : null} */}
                            {row.giftSelections.map((selection) => (
                              <Tag
                                key={`${index}-${selection.variantId}`}
                                onRemove={() =>
                                  removeGiftVariant(index, selection.variantId)
                                }
                              >
                                {`${getVariantLabel(selection.variantId)} x${selection.quantityMultiplier}`}
                              </Tag>
                            ))}
                          </InlineStack>

                          {row.giftSelections.length ? (
                            <BlockStack gap="200">
                              {row.giftSelections.map((selection) => (
                                <InlineGrid
                                  key={`gift-qty-${index}-${selection.variantId}`}
                                  columns={{ xs: 1, md: "1fr 140px" }}
                                  gap="300"
                                >
                                  <Text as="p">{getVariantLabel(selection.variantId)}</Text>
                                  <TextField
                                    label="Qty multiplier"
                                    type="number"
                                    autoComplete="off"
                                    min={1}
                                    value={String(selection.quantityMultiplier)}
                                    onChange={(value) =>
                                      updateGiftQuantityMultiplier(
                                        index,
                                        selection.variantId,
                                        value,
                                      )
                                    }
                                  />
                                </InlineGrid>
                              ))}
                            </BlockStack>
                          ) : null}
                        </BlockStack>
                      </Card>
                    );
                  })}

                  <InlineStack gap="300">
                    <Button submit variant="primary">
                      Save rules
                    </Button>
                    <Button onClick={addRow}>Add another mapping</Button>
                  </InlineStack>
                </BlockStack>
              </Form>
            </BlockStack>
          </Card>

          <Card>
            <BlockStack gap="300">
              <Text as="h2" variant="headingMd">
                Current Product Mapping
              </Text>
              <Box
                as="pre"
                padding="400"
                background="bg-surface-secondary"
                borderRadius="200"
                overflowX="auto"
              >
                {JSON.stringify(effectiveMapping, null, 2)}
              </Box>
              <Text as="p" tone="subdued">
                Current error message: {effectiveErrorMessage}
              </Text>
            </BlockStack>
          </Card>
        </BlockStack>
      </Page>
    </PolarisAppProvider>
  );
}
