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,
  Tag,
  Text,
  TextField,
} from "@shopify/polaris";
import enTranslations from "@shopify/polaris/locales/en.json";
import { authenticate } from "../shopify.server";
import {
  listSampleFreeGiftRules,
  replaceSampleFreeGiftRules,
} from "../models.sample-free-gift-rules.server";

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

async function fetchAllVariantOptions(admin) {
  const options = [];
  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;

    options.push(
      ...(connection?.edges?.map(({ node }) => {
        const productTitle = node.product?.title ?? "Untitled product";
        const status = node.product?.status;
        const name =
          node.title === "Default Title"
            ? productTitle
            : `${productTitle} / ${node.title}`;
        const suffix =
          status && String(status).toUpperCase() !== "ACTIVE"
            ? ` (${String(status).charAt(0)}${String(status).slice(1).toLowerCase()})`
            : "";

        return { value: node.id, label: `${name}${suffix}` };
      }) ?? []),
    );

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

  return options;
}

function createEmptyRow() {
  return {
    productXVariantIds: [],
    sampleVariantIds: [],
    excludeVariantIds: [],
    productYQuantity: "1",
    productXSearch: "",
    sampleSearch: "",
    excludeSearch: "",
  };
}

export const loader = async ({ request }) => {
  const { admin, session } = await authenticate.admin(request);
  const [rules, variantOptions] = await Promise.all([
    listSampleFreeGiftRules(session.shop),
    fetchAllVariantOptions(admin),
  ]);

  return {
    variantOptions,
    rules: rules.length
      ? rules.map((rule) => ({
          productXVariantIds: rule.productXVariantIds,
          sampleVariantIds: rule.sampleVariantIds,
          excludeVariantIds: rule.excludeVariantIds,
          productYQuantity: String(rule.productYQuantity),
          productXSearch: "",
          sampleSearch: "",
          excludeSearch: "",
        }))
      : [createEmptyRow()],
  };
};

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

  if (!rulesJson) {
    return { status: "error", message: "No sample free gift 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." };
  }

  // A completely blank row just means "no rules"; a half-filled one is a mistake.
  const nonEmptyRows = payload.filter(
    (row) =>
      (row?.productXVariantIds?.length ?? 0) > 0 ||
      (row?.sampleVariantIds?.length ?? 0) > 0 ||
      (row?.excludeVariantIds?.length ?? 0) > 0,
  );

  const incomplete = nonEmptyRows.find(
    (row) =>
      (row?.productXVariantIds?.length ?? 0) === 0 ||
      (row?.sampleVariantIds?.length ?? 0) === 0,
  );

  if (incomplete) {
    return {
      status: "error",
      message:
        "Every rule needs at least one Product X variant and one Sample Product Y variant.",
    };
  }

  try {
    const saved = await replaceSampleFreeGiftRules(session.shop, nonEmptyRows);

    return {
      status: "success",
      message: `Sample free gift rules saved (${saved.length} rule${saved.length === 1 ? "" : "s"}).`,
    };
  } catch (error) {
    return {
      status: "error",
      message:
        error instanceof Error
          ? error.message
          : "Unable to save sample free gift rules.",
    };
  }
};

export default function SampleFreeGiftRulesPage() {
  const { rules, variantOptions } = useLoaderData();
  const actionData = useActionData();
  const [rows, setRows] = useState(rules);

  const optionLabel = useMemo(() => {
    const map = new Map(
      variantOptions.map((option) => [option.value, option.label]),
    );

    return (variantId) => map.get(variantId) ?? variantId;
  }, [variantOptions]);

  // The saved rules, exactly as the checkout extension receives them.
  const savedMapping = useMemo(
    () =>
      rules
        .filter(
          (rule) =>
            rule.productXVariantIds.length > 0 &&
            rule.sampleVariantIds.length > 0,
        )
        .reduce((mapping, rule, index) => {
          mapping[`Rule ${index + 1}`] = {
            productXVariantIds: rule.productXVariantIds,
            sampleVariantIds: rule.sampleVariantIds,
            excludeVariantIds: rule.excludeVariantIds,
            productYQuantity: rule.productYQuantity,
          };

          return mapping;
        }, {}),
    [rules],
  );

  const filterOptions = (searchValue) => {
    const normalized = searchValue.trim().toLowerCase();

    if (!normalized) return variantOptions;

    return variantOptions.filter((option) =>
      option.label.toLowerCase().includes(normalized),
    );
  };

  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 removeSelection = (index, field, variantId) =>
    updateRow(index, {
      [field]: rows[index][field].filter((value) => value !== variantId),
    });

  const variantPicker = (index, row, field, searchField, label, help) => (
    <BlockStack gap="200">
      <Autocomplete
        allowMultiple
        options={filterOptions(row[searchField])}
        selected={row[field]}
        onSelect={(selected) =>
          updateRow(index, { [field]: selected, [searchField]: "" })
        }
        textField={
          <Autocomplete.TextField
            label={label}
            value={row[searchField]}
            autoComplete="off"
            placeholder="Search product variants"
            helpText={help}
            onChange={(value) => updateRow(index, { [searchField]: value })}
          />
        }
      />
      <InlineStack gap="200" wrap>
        {row[field].map((variantId) => (
          <Tag
            key={`${index}-${field}-${variantId}`}
            onRemove={() => removeSelection(index, field, variantId)}
          >
            {optionLabel(variantId)}
          </Tag>
        ))}
      </InlineStack>
    </BlockStack>
  );

  return (
    <PolarisAppProvider i18n={enTranslations}>
      <Page
        title="Sample Free Gift Rules"
        subtitle="Add a sample product automatically when a qualifying product is in the cart."
      >
        <BlockStack gap="500">
          {actionData?.message ? (
            <Banner
              tone={actionData.status === "success" ? "success" : "critical"}
            >
              <p>{actionData.message}</p>
            </Banner>
          ) : null}

          <Card>
            <BlockStack gap="300">
              <Text as="h2" variant="headingMd">
                Sample free gift rules
              </Text>
              <Text as="p" tone="subdued">
                When the cart contains any Product X variant, one Sample Product
                Y variant is added automatically, at exactly the Product Y
                Quantity set below however many Product X items are in the cart.
                If any excluded variant is in the cart, no sample is added.
              </Text>

              <Form method="post">
                <input
                  type="hidden"
                  name="rulesJson"
                  value={JSON.stringify(
                    rows.map((row) => ({
                      productXVariantIds: row.productXVariantIds,
                      sampleVariantIds: row.sampleVariantIds,
                      excludeVariantIds: row.excludeVariantIds,
                      productYQuantity: row.productYQuantity,
                    })),
                  )}
                />

                <BlockStack gap="400">
                  {rows.map((row, index) => (
                    <Card
                      key={`sample-rule-${index}`}
                      background="bg-surface-secondary"
                    >
                      <BlockStack gap="300">
                        <InlineStack align="space-between" blockAlign="center">
                          <Text as="h3" variant="headingSm">
                            Rule {index + 1}
                          </Text>
                          <Button
                            variant="tertiary"
                            tone="critical"
                            onClick={() => removeRow(index)}
                          >
                            Remove rule
                          </Button>
                        </InlineStack>

                        {variantPicker(
                          index,
                          row,
                          "productXVariantIds",
                          "productXSearch",
                          "Product X",
                          "Any of these in the cart triggers the sample.",
                        )}

                        {variantPicker(
                          index,
                          row,
                          "sampleVariantIds",
                          "sampleSearch",
                          "Sample Product Y",
                          "Only the first variant is added; the rest are alternatives kept in sync.",
                        )}

                        {variantPicker(
                          index,
                          row,
                          "excludeVariantIds",
                          "excludeSearch",
                          "Exclude if in cart",
                          "If any of these is in the cart, the sample is not added.",
                        )}

                        <InlineGrid columns={{ xs: 1, md: 2 }} gap="300">
                          <TextField
                            label="Product Y Quantity"
                            type="number"
                            min={1}
                            step={1}
                            autoComplete="off"
                            value={row.productYQuantity}
                            onChange={(value) =>
                              updateRow(index, { productYQuantity: value })
                            }
                            helpText="How many free units of Product Y to add. Product X quantity has no effect."
                          />
                        </InlineGrid>
                      </BlockStack>
                    </Card>
                  ))}

                  <InlineStack gap="300">
                    <Button onClick={addRow}>Add rule</Button>
                    <Button submit variant="primary">
                      Save rules
                    </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(savedMapping, null, 2)}
              </Box>
              <Text as="p" tone="subdued">
                Saved rules currently used at checkout. Save to refresh this
                list.
              </Text>
            </BlockStack>
          </Card>
        </BlockStack>
      </Page>
    </PolarisAppProvider>
  );
}
