import express from "express";
import shopify from "../shopify.js";
import {
  listGroups,
  getGroup,
  createGroup,
  updateGroup,
  deleteGroup,
  getGroupedProductIds,
} from "../db.js";
import { detectGroups, suggestStyle } from "../auto-group.js";
import { syncGroupMetafields, clearGroupMetafields } from "../metafields.js";

// Keep the storefront-facing metafield in sync with a group's DB state:
// published -> write to members; otherwise -> clear from members.
async function syncGroup(session, group, removedProductIds = []) {
  if (removedProductIds.length) {
    await clearGroupMetafields(session, removedProductIds);
  }
  if (group && group.status === "published") {
    await syncGroupMetafields(session, group);
  } else if (group) {
    await clearGroupMetafields(
      session,
      (group.members || []).map((m) => m.product_id)
    );
  }
}

/**
 * Admin API, mounted under /api and protected by validateAuthenticatedSession.
 * The authenticated shop is always on res.locals.shopify.session.shop.
 */
const router = express.Router();

const shopOf = (res) => res.locals.shopify.session.shop;

/* ---------- Products list / search (for the group picker) ---------- */

router.get("/products", async (req, res) => {
  const client = new shopify.api.clients.Graphql({
    session: res.locals.shopify.session,
  });
  const term = (req.query.q || "").toString();
  const after = req.query.after ? `, after: "${req.query.after}"` : "";
  const query = term ? `title:*${term}*` : "";

  const data = await client.request(
    `query ($q: String!) {
      products(first: 25, query: $q${after}) {
        pageInfo { hasNextPage endCursor }
        edges { node {
          id
          title
          handle
          featuredImage { url }
        } }
      }
    }`,
    { variables: { q: query } }
  );

  const conn = data.data.products;
  res.json({
    products: conn.edges.map((e) => ({
      id: e.node.id,
      title: e.node.title,
      handle: e.node.handle,
      image: e.node.featuredImage?.url || "",
    })),
    pageInfo: conn.pageInfo,
  });
});

/* ---------- Product groups (combined-listing swatches) ---------- */

router.get("/groups", async (_req, res) => {
  res.json({ groups: await listGroups(shopOf(res)) });
});

router.get("/groups/:id", async (req, res) => {
  const group = await getGroup(shopOf(res), req.params.id);
  if (!group) return res.status(404).json({ error: "not found" });
  res.json({ group });
});

router.post("/groups", async (req, res) => {
  if (!req.body.name?.trim())
    return res.status(422).json({ errors: ["name is required"] });
  const group = await createGroup(shopOf(res), req.body);
  await syncGroup(res.locals.shopify.session, group);
  res.status(201).json({ group });
});

router.put("/groups/:id", async (req, res) => {
  const existing = await getGroup(shopOf(res), req.params.id);
  if (!existing) return res.status(404).json({ error: "not found" });

  const group = await updateGroup(shopOf(res), req.params.id, req.body);

  // Members removed in this edit must have their metafield cleared.
  const newIds = new Set((group.members || []).map((m) => m.product_id));
  const removed = (existing.members || [])
    .map((m) => m.product_id)
    .filter((id) => !newIds.has(id));

  await syncGroup(res.locals.shopify.session, group, removed);
  res.json({ group });
});

router.delete("/groups/:id", async (req, res) => {
  const existing = await getGroup(shopOf(res), req.params.id);
  if (existing) {
    // Clear metafields BEFORE removing the DB rows, so we still know the members.
    await clearGroupMetafields(
      res.locals.shopify.session,
      (existing.members || []).map((m) => m.product_id)
    );
  }
  await deleteGroup(shopOf(res), req.params.id);
  res.status(204).end();
});

// Clean up orphaned metafields: products that still carry our linked_products
// metafield but are no longer in any group (e.g. a group deleted before the
// delete bug was fixed). Scans the catalog via the metafield and clears them.
router.post("/groups/cleanup-orphans", async (req, res) => {
  const shop = shopOf(res);
  const client = new shopify.api.clients.Graphql({
    session: res.locals.shopify.session,
  });
  const grouped = await getGroupedProductIds(shop);

  const orphans = [];
  let after = null;
  for (let page = 0; page < 20; page++) {
    const data = await client.request(
      `query ($after: String) {
        products(first: 100, after: $after) {
          pageInfo { hasNextPage endCursor }
          edges { node {
            id
            metafield(namespace: "linked_products", key: "group") { id }
          } }
        }
      }`,
      { variables: { after } }
    );
    const conn = data.data.products;
    for (const e of conn.edges) {
      if (e.node.metafield && !grouped.has(e.node.id)) orphans.push(e.node.id);
    }
    if (!conn.pageInfo.hasNextPage) break;
    after = conn.pageInfo.endCursor;
  }

  await clearGroupMetafields(res.locals.shopify.session, orphans);
  res.json({ cleared: orphans.length });
});

// Force-rewrite metafields for a group (creates the definition if missing).
// Useful after upgrading, or if a group was published before the definition
// existed. Returns the metafield read back from the first member to confirm.
router.post("/groups/:id/resync", async (req, res) => {
  const group = await getGroup(shopOf(res), req.params.id);
  if (!group) return res.status(404).json({ error: "not found" });
  await syncGroup(res.locals.shopify.session, group);

  // Read it back so the UI can confirm Liquid will see it.
  const client = new shopify.api.clients.Graphql({
    session: res.locals.shopify.session,
  });
  const first = group.members?.[0];
  let readback = null;
  if (first) {
    const data = await client.request(
      `query ($id: ID!) {
        product(id: $id) {
          metafield(namespace: "linked_products", key: "group") {
            type
            value
            definition { id access { storefront } }
          }
        }
      }`,
      { variables: { id: first.product_id } }
    );
    readback = data.data.product?.metafield || null;
  }
  res.json({ ok: true, status: group.status, readback });
});

/* ---------- Auto-detect groups by shared title prefix ----------
 * Fetches the catalog, detects sibling products, and creates any new groups as
 * DRAFT (hidden from the storefront until the merchant publishes them). Skips
 * products already in a group so re-running is safe.
 */
async function fetchAllProducts(session) {
  const client = new shopify.api.clients.Graphql({ session });
  const out = [];
  let after = null;
  // Cap at a few pages to stay well within rate limits for the MVP.
  for (let page = 0; page < 10; page++) {
    const data = await client.request(
      `query ($after: String) {
        products(first: 100, after: $after) {
          pageInfo { hasNextPage endCursor }
          edges { node { id title handle featuredImage { url } } }
        }
      }`,
      { variables: { after } }
    );
    const conn = data.data.products;
    for (const e of conn.edges) {
      out.push({
        id: e.node.id,
        title: e.node.title,
        handle: e.node.handle,
        image: e.node.featuredImage?.url || "",
      });
    }
    if (!conn.pageInfo.hasNextPage) break;
    after = conn.pageInfo.endCursor;
  }
  return out;
}

// Preview without saving (so the UI can show what WILL be created).
router.get("/groups/auto/preview", async (_req, res) => {
  const shop = shopOf(res);
  const products = await fetchAllProducts(res.locals.shopify.session);
  const grouped = await getGroupedProductIds(shop);
  const detected = detectGroups(products)
    .map((g) => ({
      ...g,
      members: g.members.filter((m) => !grouped.has(m.productId)),
    }))
    .filter((g) => g.members.length >= 2)
    .map((g) => ({ ...g, style: suggestStyle(g) }));
  res.json({ detected });
});

// Create all detected groups as drafts. Returns how many were created.
router.post("/groups/auto", async (req, res) => {
  const shop = shopOf(res);
  const products = await fetchAllProducts(res.locals.shopify.session);
  const grouped = await getGroupedProductIds(shop);
  const detected = detectGroups(products);

  let created = 0;
  for (const g of detected) {
    const members = g.members.filter((m) => !grouped.has(m.productId));
    if (members.length < 2) continue;
    await createGroup(shop, {
      name: g.base,
      label: req.body?.label || "Color",
      style: suggestStyle({ ...g, members }),
      members,
      status: "draft",
      source: "auto",
    });
    created++;
  }
  res.json({ created, groups: await listGroups(shop) });
});

export default router;