import type { ActionFunctionArgs } from "react-router";
import { authenticate, unauthenticated } from "../shopify.server";
import prisma from "../db.server";

// Compute "YYYY-MM-DD" in the shop's timezone — matches how api.current-deal builds analyticsId
function dateKeyInTz(date: Date, tz: string): string {
  return new Intl.DateTimeFormat("en-CA", {
    timeZone: tz,
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
  }).format(date); // e.g. "2026-05-19"
}

function getTzParts(date: Date, tz: string) {
  const fmt = new Intl.DateTimeFormat("en-US", {
    timeZone: tz,
    hour: "2-digit",
    minute: "2-digit",
    hourCycle: "h23",
    weekday: "short",
  });
  const parts = fmt.formatToParts(date);
  const get = (type: string) => parts.find((p) => p.type === type)?.value ?? "0";
  const dow = ({ Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6 } as Record<string, number>)[get("weekday")] ?? 0;
  return {
    dayOfWeek: dow,
    hour: parseInt(get("hour"), 10),
    minute: parseInt(get("minute"), 10),
  };
}

export const action = async ({ request }: ActionFunctionArgs) => {
  const { topic, shop, payload } = await authenticate.webhook(request);

  // ── Mandatory GDPR compliance webhooks ───────────────────────────────────
  if (topic === "CUSTOMERS_DATA_REQUEST") {
    // A customer requested their data. Log it — respond 200 to acknowledge.
    console.log(`[GDPR] customers/data_request for shop: ${shop}`);
    return new Response("Acknowledged", { status: 200 });
  }

  if (topic === "CUSTOMERS_REDACT") {
    // A customer requested deletion of their data.
    // This app stores no personal customer data — nothing to delete.
    console.log(`[GDPR] customers/redact for shop: ${shop}`);
    return new Response("Acknowledged", { status: 200 });
  }

  if (topic === "SHOP_REDACT") {
    // A shop uninstalled and requested full data deletion (48h after uninstall).
    // Delete all data associated with this shop.
    console.log(`[GDPR] shop/redact for shop: ${shop}`);
    try {
      await prisma.dealAnalytics.deleteMany({ where: { shop } });
      await prisma.dealSlot.deleteMany({ where: { shop } });
      await prisma.processedOrder.deleteMany({ where: { shop } });
      await prisma.appSettings.deleteMany({ where: { shop } });
    } catch (e) {
      console.error("[GDPR] shop/redact cleanup error:", e);
    }
    return new Response("Acknowledged", { status: 200 });
  }
  // ─────────────────────────────────────────────────────────────────────────

  if (topic === "APP_UNINSTALLED") {
    // Merchant uninstalled the app. Clean up any live Shopify automatic discounts
    // immediately while we still have a valid access token (SHOP_REDACT fires 48h later).
    console.log(`[Uninstall] app/uninstalled for shop: ${shop}`);
    try {
      const activeSlots = await prisma.dealSlot.findMany({
        where: { shop, shopifyDiscountId: { not: null } },
        select: { shopifyDiscountId: true },
      });

      if (activeSlots.length > 0) {
        const { admin } = await unauthenticated.admin(shop);
        await Promise.allSettled(
          activeSlots
            .filter((s) => s.shopifyDiscountId)
            .map((s) =>
              admin.graphql(
                `#graphql
                mutation DeleteDiscount($id: ID!) {
                  discountAutomaticDelete(id: $id) {
                    deletedAutomaticDiscountId
                    userErrors { message }
                  }
                }`,
                { variables: { id: s.shopifyDiscountId! } }
              )
            )
        );
        console.log(`[Uninstall] Deleted ${activeSlots.length} active discount(s) for shop: ${shop}`);
      }
    } catch (e) {
      console.error("[Uninstall] Failed to clean up discounts:", e);
    }
    return new Response("OK", { status: 200 });
  }

  if (topic === "ORDERS_CREATE") {
    const order = payload as any;

    // ── Idempotency guard — skip if this order was already processed ──────────
    const processedId = `${shop}_${order.id}`;
    const alreadyProcessed = await prisma.processedOrder.findUnique({ where: { id: processedId } });
    if (alreadyProcessed) {
      return new Response("Already processed", { status: 200 });
    }
    // Mark as processed immediately to prevent race conditions
    await prisma.processedOrder.create({ data: { id: processedId, shop, orderId: String(order.id) } });
    // ─────────────────────────────────────────────────────────────────────────

    const orderCreatedAt = new Date(order.created_at);

    // Get shop timezone
    const settings = await prisma.appSettings.findUnique({ where: { shop } });
    const timezone = settings?.timezone ?? "UTC";

    // All time values in the shop's timezone
    const { dayOfWeek, hour: orderHour, minute: orderMinute } = getTzParts(orderCreatedAt, timezone);
    const orderMinutes = orderHour * 60 + orderMinute;
    const dateKey = dateKeyInTz(orderCreatedAt, timezone); // "YYYY-MM-DD" in shop tz

    // Find candidate deal slots active at order time.
    // Priority: date-specific deals (specificDate === dateKey) over recurring (specificDate null).
    const candidateDeals = await prisma.dealSlot.findMany({
      where: {
        shop,
        isActive: true,
        startHour: { lte: orderHour },
        OR: [
          { specificDate: dateKey },                    // one-time deal for this exact date
          { specificDate: null, dayOfWeek },            // recurring deal on this weekday
        ],
      },
      orderBy: [
        { specificDate: "desc" },   // prefer date-specific over recurring
        { startHour: "desc" },
      ],
    });

    // Pick the slot whose time window actually covers the order minute
    const activeDeal = candidateDeals.find((deal) => {
      const dealStartMinutes = deal.startHour * 60 + deal.startMinute;
      const dealEndMinutes = dealStartMinutes + deal.durationMinutes;
      return orderMinutes >= dealStartMinutes && orderMinutes < dealEndMinutes;
    });

    if (!activeDeal) {
      return new Response("No active deal at order time", { status: 200 });
    }

    // Match line items against the deal's product
    const lineItems: any[] = order.line_items || [];
    const dealProductNumericId = activeDeal.productId.replace("gid://shopify/Product/", "");

    let matchedUnits = 0;
    let matchedRevenue = 0;

    for (const item of lineItems) {
      if (String(item.product_id) === dealProductNumericId) {
        matchedUnits += item.quantity;
        matchedRevenue += parseFloat(item.price) * item.quantity;
      }
    }

    if (matchedUnits === 0) {
      return new Response("Order contained no deal products", { status: 200 });
    }

    // analyticsId must match exactly what api.current-deal.tsx creates: `${dealId}_${dateKey}`
    const analyticsId = `${activeDeal.id}_${dateKey}`;
    const runDate = new Date(`${dateKey}T00:00:00.000Z`);

    await prisma.dealAnalytics.upsert({
      where: { id: analyticsId },
      create: {
        id: analyticsId,
        shop,
        dealSlotId: activeDeal.id,
        runDate,
        unitsSold: matchedUnits,
        revenue: matchedRevenue,
        impressions: 0,
      },
      update: {
        unitsSold: { increment: matchedUnits },
        revenue:   { increment: matchedRevenue },
      },
    });
  }

  return new Response("OK", { status: 200 });
};
