/**
 * GET /api/site-tag-analytics/embedded-conversions
 *
 * Analytics for iframe form completions, SMS opt-ins, and all provider-
 * attributed conversions. Powers the "Embedded Conversions" tab on the
 * Site Tag Analytics page.
 *
 * Query params:
 *   brandId  (required)
 *   days     (default 30)
 *   category (optional: 'booking' | 'form' | 'sms' | 'chat' | 'checkout' |
 *             'donation' | 'civic' | 'survey' | 'all')
 *   channel  (optional: first-touch channel filter, e.g. 'ai_search' | 'all')
 */

import { NextResponse } from "next/server";
import { z } from "zod";
import { prisma } from "@/lib/prisma";
import { validateQuery } from "@/lib/validation/request";
import { BrandIdSchema, DaysSchema } from "@/lib/validation/common-schemas";
import { withErrorHandler } from "@/lib/api/with-error-handler";
import { requireUserAndBrand } from "@/lib/auth/require";
import { countedConversionWhere } from "@/lib/conversions/counted-where";

const QuerySchema = z.object({
  brandId: BrandIdSchema,
  days: DaysSchema,
  category: z.string().default("all"),
  channel: z.string().default("all"),
});

export const dynamic = "force-dynamic";

// Maps formProvider to category when providerCategory column is null
// (rows written before the column was added).
const PROVIDER_TO_CATEGORY: Record<string, string> = {
  calendly: "booking", acuity: "booking", nexhealth: "booking",
  patientpop: "booking", simplepractice: "booking", tebra: "booking",
  athenahealth: "booking", mychart: "booking", cerner: "booking",
  doctible: "booking", squareup: "booking",
  hubspot: "form", typeform: "form", jotform: "form",
  formstack: "form", wufoo: "form", gohighlevel: "form",
  attentive: "sms", postscript: "sms", klaviyo_sms: "sms",
  smsbump: "sms", recart: "sms", voyage: "sms",
  eztexting: "sms", simpletexting: "sms", twilio: "sms",
  textmagic: "sms", textedly: "sms", slicktext: "sms",
  tatango: "sms", trumpia: "sms", salesmsg: "sms", heymarket: "sms",
  community: "sms", subtext: "sms", textrequest: "sms", sakari: "sms",
  podium_sms: "sms", birdeye_sms: "sms", sms_link: "sms",
  sms_keyword: "sms", explicit_checkbox: "sms", two_step_form: "sms",
  form_field: "sms",
  podium: "chat", intercom: "chat", drift: "chat", zendesk: "chat",
};

const CATEGORY_DISPLAY: Record<string, string> = {
  booking: "Booking", form: "Iframe Form", sms: "SMS Opt-in",
  chat: "Chat Widget", checkout: "Checkout", donation: "Donation",
  civic: "Civic", survey: "Survey", other: "Other",
};

function resolveCategory(row: { providerCategory: string | null; formProvider: string | null }): string {
  if (row.providerCategory) return row.providerCategory;
  if (row.formProvider) return PROVIDER_TO_CATEGORY[row.formProvider] ?? "other";
  return "other";
}

// Providers that are NOT cross-origin embeds — native same-origin forms,
// generic custom detectors, and gtag intercepts must not appear in this tab.
const NON_EMBEDDED_PROVIDERS = ["native", "custom", "gtag_intercept"];

function rangeStart(days: number): Date {
  const d = new Date();
  d.setUTCDate(d.getUTCDate() - days);
  return d;
}

function formatDate(d: Date): string {
  return d.toISOString().slice(0, 10);
}

export const GET = withErrorHandler(async (req: Request) => {
  const { brandId, days, category: categoryFilter, channel: channelFilter } = validateQuery(req, QuerySchema);
  await requireUserAndBrand(brandId);

  const from = rangeStart(days);
  const priorFrom = rangeStart(days * 2);

  // Build WHERE clause for category / channel filters
  const categoryWhere = categoryFilter !== "all" ? { providerCategory: categoryFilter } : {};
  const channelWhere = channelFilter !== "all" ? { firstTouchChannel: channelFilter } : {};
  const baseWhere = {
    ...countedConversionWhere(brandId, { since: from }),
    formProvider: { not: null as string | null, notIn: NON_EMBEDDED_PROVIDERS },
    ...categoryWhere,
    ...channelWhere,
  };
  const priorWhere = {
    ...countedConversionWhere(brandId),
    timestamp: { gte: priorFrom, lt: from },
    formProvider: { not: null as string | null, notIn: NON_EMBEDDED_PROVIDERS },
    ...categoryWhere,
    ...channelWhere,
  };

  const [
    currentRows,
    priorCount,
    providerGroups,
    urlGroups,
    channelGroups,
    allRows,
  ] = await Promise.allSettled([
    prisma.siteConversion.findMany({
      where: baseWhere,
      select: {
        formProvider: true,
        providerCategory: true,
        pageUrl: true,
        firstTouchChannel: true,
        conversionType: true,
        timestamp: true,
      },
      orderBy: { timestamp: "desc" },
      take: 10_000,
    }),
    prisma.siteConversion.count({ where: priorWhere }),
    prisma.siteConversion.groupBy({
      by: ["formProvider", "providerCategory"],
      where: baseWhere,
      _count: { _all: true },
      orderBy: { _count: { formProvider: "desc" } },
    }),
    prisma.siteConversion.groupBy({
      by: ["pageUrl"],
      where: baseWhere,
      _count: { _all: true },
      orderBy: { _count: { pageUrl: "desc" } },
      take: 50,
    }),
    prisma.siteConversion.groupBy({
      by: ["firstTouchChannel"],
      where: baseWhere,
      _count: { _all: true },
      orderBy: { _count: { firstTouchChannel: "desc" } },
    }),
    // For top provider/channel per URL we need all rows (capped)
    prisma.siteConversion.findMany({
      where: { ...baseWhere, pageUrl: { in: [] } }, // placeholder; overridden below
      select: { pageUrl: true, formProvider: true, firstTouchChannel: true, providerCategory: true },
    }),
  ]);

  const rows = currentRows.status === "fulfilled" ? currentRows.value : [];
  const prior = priorCount.status === "fulfilled" ? priorCount.value : 0;
  const total = rows.length;
  const trendPct = prior > 0 ? Math.round(((total - prior) / prior) * 100) : null;

  // Stats strip
  const smsRows = rows.filter((r) => resolveCategory(r) === "sms");
  const formRows = rows.filter((r) => resolveCategory(r) === "form");

  // Channel breakdown from current rows
  const channelCounts: Record<string, number> = {};
  for (const r of rows) {
    const ch = r.firstTouchChannel || "direct";
    channelCounts[ch] = (channelCounts[ch] ?? 0) + 1;
  }
  const topChannelEntry = Object.entries(channelCounts).sort((a, b) => b[1] - a[1])[0];
  const topChannel = topChannelEntry
    ? { channel: topChannelEntry[0], count: topChannelEntry[1], percentOfTotal: total > 0 ? Math.round((topChannelEntry[1] / total) * 100) : 0 }
    : null;

  // URL breakdown from current rows
  const urlCounts: Record<string, number> = {};
  for (const r of rows) {
    urlCounts[r.pageUrl] = (urlCounts[r.pageUrl] ?? 0) + 1;
  }
  const topUrlEntry = Object.entries(urlCounts).sort((a, b) => b[1] - a[1])[0];

  // Time series: group rows by date + category
  const timeSeriesMap: Record<string, Record<string, number>> = {};
  for (const r of rows) {
    const date = formatDate(r.timestamp);
    const cat = resolveCategory(r);
    if (!timeSeriesMap[date]) timeSeriesMap[date] = {};
    timeSeriesMap[date][cat] = (timeSeriesMap[date][cat] ?? 0) + 1;
  }
  const timeSeriesByCategory = Object.entries(timeSeriesMap)
    .sort(([a], [b]) => a.localeCompare(b))
    .flatMap(([date, cats]) =>
      Object.entries(cats).map(([category, count]) => ({ date, category, count })),
    );

  // Provider breakdown with trend sparkline (last 7 days by day)
  const providerRows = providerGroups.status === "fulfilled" ? providerGroups.value : [];
  // Build per-provider last-7-day sparklines from current rows
  const sparklines: Record<string, number[]> = {};
  for (const r of rows) {
    const provider = r.formProvider!;
    if (!sparklines[provider]) sparklines[provider] = Array(7).fill(0);
    const daysAgo = Math.floor((Date.now() - r.timestamp.getTime()) / 86_400_000);
    if (daysAgo < 7) sparklines[provider][6 - daysAgo]++;
  }
  // Collapse groupBy(formProvider, providerCategory) into one entry per
  // provider. The same provider can appear as multiple rows when some rows
  // have providerCategory=null and others have a value set (rows written
  // before the column was backfilled). Prefer any non-null category over null.
  const collapsedProviders = new Map<string, { count: number; category: string | null }>();
  for (const p of providerRows) {
    const key = p.formProvider ?? "unknown";
    const existing = collapsedProviders.get(key);
    if (existing) {
      existing.count += p._count._all;
      if (existing.category === null && p.providerCategory !== null) existing.category = p.providerCategory;
    } else {
      collapsedProviders.set(key, { count: p._count._all, category: p.providerCategory });
    }
  }
  const providerBreakdown = Array.from(collapsedProviders.entries())
    .sort(([, a], [, b]) => b.count - a.count)
    .map(([provider, { count, category }]) => {
      const resolvedCategory = category ?? PROVIDER_TO_CATEGORY[provider] ?? "other";
      return {
        provider,
        category: resolvedCategory,
        categoryLabel: CATEGORY_DISPLAY[resolvedCategory] ?? "Other",
        completions: count,
        trendSparkline: sparklines[provider] ?? Array(7).fill(0),
      };
    });

  // Top URLs
  const urlGroupRows = urlGroups.status === "fulfilled" ? urlGroups.value : [];
  // For each top URL, find top provider and top channel from current rows
  const topUrls = urlGroupRows.map((u) => {
    const urlRows = rows.filter((r) => r.pageUrl === u.pageUrl);
    const provByUrl: Record<string, number> = {};
    const chByUrl: Record<string, number> = {};
    const catByUrl: Record<string, number> = {};
    for (const r of urlRows) {
      const p = r.formProvider ?? "unknown";
      const ch = r.firstTouchChannel || "direct";
      const cat = resolveCategory(r);
      provByUrl[p] = (provByUrl[p] ?? 0) + 1;
      chByUrl[ch] = (chByUrl[ch] ?? 0) + 1;
      catByUrl[cat] = (catByUrl[cat] ?? 0) + 1;
    }
    const topProv = Object.entries(provByUrl).sort((a, b) => b[1] - a[1])[0];
    const topCh = Object.entries(chByUrl).sort((a, b) => b[1] - a[1])[0];
    const totalUrl = urlRows.length;
    return {
      url: u.pageUrl,
      totalConversions: u._count._all,
      categoryBreakdown: catByUrl,
      topProvider: topProv?.[0] ?? "unknown",
      topChannel: topCh?.[0] ?? "direct",
      topChannelPct: topCh && totalUrl > 0 ? Math.round((topCh[1] / totalUrl) * 100) : 0,
    };
  });

  // Channel attribution
  const channelRows = channelGroups.status === "fulfilled" ? channelGroups.value : [];
  const channelAttribution = channelRows.map((c) => ({
    channel: c.firstTouchChannel || "direct",
    count: c._count._all,
    percentOfTotal: total > 0 ? Math.round((c._count._all / total) * 100) : 0,
  }));

  // Channel table: top provider + category per channel
  const channelTable = channelAttribution.map((c) => {
    const chRows = rows.filter((r) => (r.firstTouchChannel || "direct") === c.channel);
    const provByCh: Record<string, number> = {};
    const catByCh: Record<string, number> = {};
    for (const r of chRows) {
      const p = r.formProvider ?? "unknown";
      const cat = resolveCategory(r);
      provByCh[p] = (provByCh[p] ?? 0) + 1;
      catByCh[cat] = (catByCh[cat] ?? 0) + 1;
    }
    const topProv = Object.entries(provByCh).sort((a, b) => b[1] - a[1])[0];
    const topCat = Object.entries(catByCh).sort((a, b) => b[1] - a[1])[0];
    return {
      ...c,
      topProvider: topProv?.[0] ?? "unknown",
      topCategory: topCat?.[0] ?? "other",
    };
  });

  return NextResponse.json({
    statsStrip: {
      totalEmbedded: { count: total, trendPct },
      totalIframeForms: { count: formRows.length },
      totalSmsOptins: { count: smsRows.length },
      topChannel,
      topUrl: topUrlEntry ? { url: topUrlEntry[0], count: topUrlEntry[1] } : null,
    },
    timeSeriesByCategory,
    providerBreakdown,
    topUrls,
    channelAttribution,
    channelTable,
    meta: { days, from: from.toISOString(), categoryFilter, channelFilter },
  });
});
