/**
 * Attribution Path Builder
 * ─────────────────────────
 * Computes multi-touch attribution paths for a brand's conversions.
 * Each conversion gets one AttributionPath row recording the full
 * journey from first session to converting session, including every
 * channel touchpoint along the way.
 *
 * Source tables:
 *   SiteConversion — the conversion events (deduped, authoritative)
 *   TrackedSession — session-level channel / UTM / click-ID context
 *
 * Output:
 *   AttributionPath — one row per conversion with ordered touchpoints
 *
 * Design decisions:
 *   • Touchpoints are ordered by session startedAt (chronological).
 *   • A "touchpoint" = one TrackedSession for the same visitorId.
 *     Multiple page views within a session are collapsed into one
 *     touchpoint (the session IS the touchpoint).
 *   • Channel group is computed from the session's UTM params +
 *     click IDs + referrer using the same taxonomy as GA4's Default
 *     Channel Group so cross-source comparisons read naturally.
 *   • The builder is IDEMPOTENT: it upserts by (brandId, conversionId)
 *     so re-running it rebuilds the path without creating duplicates.
 *   • Fire-and-forget safe: individual conversion path failures are
 *     logged and skipped; the builder never throws.
 */

import { prisma } from "@/lib/prisma";

// ─── Channel group classifier ───────────────────────────────────
// ─── Channel classification — comprehensive version ─────────────
//
// Covers every traffic source the platform might encounter:
//   Paid: Google Ads, Bing, Meta, TikTok, LinkedIn, Pinterest,
//         Snapchat, X, Reddit, YouTube Ads, Programmatic/DSP,
//         OTT/CTV, Audio, Video
//   Organic: Search engines (30+ TLDs), Social (15+ platforms),
//            AI/LLM referrals (ChatGPT, Perplexity, Gemini, etc.),
//            Directories/Review sites, Maps, Email, SMS
//
// Returns the top-level channel name (backward-compatible string).
// Use `classifyChannelDetailed()` for sub-channel + source detail.

interface ChannelSession {
  utmSource?: string | null;
  utmMedium?: string | null;
  utmCampaign?: string | null;
  referrer?: string | null;
  gclid?: string | null;
  fbclid?: string | null;
  msclkid?: string | null;
  ttclid?: string | null;
  li_fat_id?: string | null;
  dclid?: string | null;
  wbraid?: string | null;
  gbraid?: string | null;
}

export interface ChannelDetail {
  channel: string;
  subChannel: string;
  source: string;
}

const SEARCH_ENGINES: Record<string, string> = {
  "google": "Google", "bing": "Bing", "yahoo": "Yahoo",
  "duckduckgo": "DuckDuckGo", "ecosia": "Ecosia", "baidu": "Baidu",
  "yandex": "Yandex", "qwant": "Qwant", "brave": "Brave Search",
  "startpage": "Startpage", "aol": "AOL", "you": "You.com",
};

const SOCIAL_PLATFORMS: Record<string, string> = {
  "facebook": "Facebook", "fb": "Facebook", "instagram": "Instagram",
  "twitter": "Twitter/X", "x": "Twitter/X", "linkedin": "LinkedIn",
  "pinterest": "Pinterest", "reddit": "Reddit", "tiktok": "TikTok",
  "youtube": "YouTube", "snapchat": "Snapchat", "threads": "Threads",
  "nextdoor": "Nextdoor", "tumblr": "Tumblr", "quora": "Quora",
  "medium": "Medium",
};

const AI_REFERRERS: Record<string, string> = {
  "chat.openai.com": "ChatGPT", "chatgpt.com": "ChatGPT",
  "perplexity.ai": "Perplexity", "gemini.google.com": "Gemini",
  "claude.ai": "Claude", "copilot.microsoft.com": "Copilot",
  "phind.com": "Phind", "you.com": "You.com",
};

const DIRECTORY_SITES: Record<string, string> = {
  // Healthcare
  "yelp.com": "Yelp", "healthgrades.com": "Healthgrades",
  "zocdoc.com": "Zocdoc", "vitals.com": "Vitals",
  "realself.com": "RealSelf", "webmd.com": "WebMD",
  "ratemds.com": "RateMDs", "doximity.com": "Doximity",
  "sharecare.com": "Sharecare",
  // Legal
  "avvo.com": "Avvo", "findlaw.com": "FindLaw",
  "justia.com": "Justia", "lawyers.com": "Lawyers.com",
  "martindale.com": "Martindale", "superlawyers.com": "Super Lawyers",
  // Home Services
  "angieslist.com": "Angi", "angi.com": "Angi",
  "homeadvisor.com": "HomeAdvisor", "thumbtack.com": "Thumbtack",
  "houzz.com": "Houzz", "porch.com": "Porch", "taskrabbit.com": "TaskRabbit",
  // Automotive
  "cars.com": "Cars.com", "cargurus.com": "CarGurus",
  "autotrader.com": "AutoTrader", "edmunds.com": "Edmunds",
  "dealerrater.com": "DealerRater", "kbb.com": "Kelley Blue Book",
  // Hospitality
  "tripadvisor.com": "TripAdvisor", "opentable.com": "OpenTable",
  "resy.com": "Resy", "booking.com": "Booking.com",
  "expedia.com": "Expedia", "airbnb.com": "Airbnb",
  // General
  "bbb.org": "BBB", "trustpilot.com": "Trustpilot",
  "g2.com": "G2", "capterra.com": "Capterra",
  "glassdoor.com": "Glassdoor", "indeed.com": "Indeed",
  "yellowpages.com": "Yellow Pages", "foursquare.com": "Foursquare",
  // Real Estate
  "zillow.com": "Zillow", "realtor.com": "Realtor.com",
  "redfin.com": "Redfin", "trulia.com": "Trulia",
  // Fitness
  "classpass.com": "ClassPass", "mindbodyonline.com": "Mindbody",
};

// UTM source → channel mapping for common agency naming conventions.
// "depends_on_medium" entries are resolved by the paid-medium checks
// that run earlier in the classifier; they only reach this lookup
// when the medium is organic/unset.
const UTM_SOURCE_MAP: Record<string, { channel: string; subChannel: string }> = {
  // Google variations
  "google-ads": { channel: "Paid Search", subChannel: "Google Ads" },
  "google_ads": { channel: "Paid Search", subChannel: "Google Ads" },
  "googleads": { channel: "Paid Search", subChannel: "Google Ads" },
  "adwords": { channel: "Paid Search", subChannel: "Google Ads" },
  "gdn": { channel: "Programmatic", subChannel: "Google Display Network" },
  "google_display": { channel: "Programmatic", subChannel: "Google Display" },
  "google-shopping": { channel: "Paid Search", subChannel: "Google Shopping" },
  "google_shopping": { channel: "Paid Search", subChannel: "Google Shopping" },
  "pmax": { channel: "Paid Search", subChannel: "Google Performance Max" },
  "performance_max": { channel: "Paid Search", subChannel: "Google Performance Max" },
  "demand_gen": { channel: "Paid Social", subChannel: "Google Demand Gen" },
  "youtube_ads": { channel: "Paid Video", subChannel: "YouTube Ads" },
  "lsa": { channel: "Local", subChannel: "Google Local Services Ads" },
  "local_services": { channel: "Local", subChannel: "Google Local Services Ads" },
  // Meta variations
  "facebook-ads": { channel: "Paid Social", subChannel: "Facebook Ads" },
  "fb-ads": { channel: "Paid Social", subChannel: "Facebook Ads" },
  "fb_ads": { channel: "Paid Social", subChannel: "Facebook Ads" },
  "ig-ads": { channel: "Paid Social", subChannel: "Instagram Ads" },
  "meta-ads": { channel: "Paid Social", subChannel: "Meta Ads" },
  // LinkedIn
  "linkedin-ads": { channel: "Paid Social", subChannel: "LinkedIn Ads" },
  "linkedin_ads": { channel: "Paid Social", subChannel: "LinkedIn Ads" },
  // TikTok
  "tiktok-ads": { channel: "Paid Social", subChannel: "TikTok Ads" },
  "tiktok_ads": { channel: "Paid Social", subChannel: "TikTok Ads" },
  // Microsoft
  "bing-ads": { channel: "Paid Search", subChannel: "Microsoft Ads" },
  "bing_ads": { channel: "Paid Search", subChannel: "Microsoft Ads" },
  "microsoft-ads": { channel: "Paid Search", subChannel: "Microsoft Ads" },
  "microsoft_ads": { channel: "Paid Search", subChannel: "Microsoft Ads" },
  // Programmatic / DSP
  "dv360": { channel: "Programmatic", subChannel: "DV360" },
  "dbm": { channel: "Programmatic", subChannel: "DV360" },
  "thetradedesk": { channel: "Programmatic", subChannel: "The Trade Desk" },
  "ttd": { channel: "Programmatic", subChannel: "The Trade Desk" },
  "tradedesk": { channel: "Programmatic", subChannel: "The Trade Desk" },
  "amazon_dsp": { channel: "Programmatic", subChannel: "Amazon DSP" },
  "xandr": { channel: "Programmatic", subChannel: "Xandr" },
  "appnexus": { channel: "Programmatic", subChannel: "Xandr" },
  "adroll": { channel: "Programmatic", subChannel: "AdRoll" },
  "criteo": { channel: "Programmatic", subChannel: "Criteo" },
  "stackadapt": { channel: "Programmatic", subChannel: "StackAdapt" },
  "taboola": { channel: "Programmatic", subChannel: "Taboola" },
  "outbrain": { channel: "Programmatic", subChannel: "Outbrain" },
  // OTT / CTV
  "roku": { channel: "OTT/CTV", subChannel: "Roku" },
  "hulu": { channel: "OTT/CTV", subChannel: "Hulu" },
  "peacock": { channel: "OTT/CTV", subChannel: "Peacock" },
  "tubi": { channel: "OTT/CTV", subChannel: "Tubi" },
  "pluto": { channel: "OTT/CTV", subChannel: "Pluto TV" },
  "samsung_tv": { channel: "OTT/CTV", subChannel: "Samsung TV+" },
  "netflix": { channel: "OTT/CTV", subChannel: "Netflix" },
  "disney": { channel: "OTT/CTV", subChannel: "Disney+" },
  "hbo": { channel: "OTT/CTV", subChannel: "Max/HBO" },
  "max": { channel: "OTT/CTV", subChannel: "Max/HBO" },
  "amazon_fire": { channel: "OTT/CTV", subChannel: "Amazon Fire TV" },
  // Audio
  "spotify": { channel: "Audio", subChannel: "Spotify" },
  "pandora": { channel: "Audio", subChannel: "Pandora" },
  "iheart": { channel: "Audio", subChannel: "iHeartRadio" },
  "iheartradio": { channel: "Audio", subChannel: "iHeartRadio" },
  "siriusxm": { channel: "Audio", subChannel: "SiriusXM" },
  // Affiliate
  "shareasale": { channel: "Affiliate", subChannel: "ShareASale" },
  "cj": { channel: "Affiliate", subChannel: "Commission Junction" },
  "rakuten": { channel: "Affiliate", subChannel: "Rakuten" },
  "impact": { channel: "Affiliate", subChannel: "Impact" },
  // CRM / Email
  "hubspot": { channel: "Email", subChannel: "HubSpot" },
  "mailchimp": { channel: "Email", subChannel: "Mailchimp" },
  "klaviyo": { channel: "Email", subChannel: "Klaviyo" },
  "activecampaign": { channel: "Email", subChannel: "ActiveCampaign" },
  "sendgrid": { channel: "Email", subChannel: "SendGrid" },
  "brevo": { channel: "Email", subChannel: "Brevo" },
  "convertkit": { channel: "Email", subChannel: "ConvertKit" },
  "marketo": { channel: "Email", subChannel: "Marketo" },
  "pardot": { channel: "Email", subChannel: "Pardot" },
  // SMS
  "twilio": { channel: "SMS", subChannel: "Twilio" },
  "podium": { channel: "SMS", subChannel: "Podium" },
  "birdeye": { channel: "SMS", subChannel: "Birdeye" },
  // Offline
  "qr": { channel: "Offline", subChannel: "QR Code" },
  "qr_code": { channel: "Offline", subChannel: "QR Code" },
  "qrcode": { channel: "Offline", subChannel: "QR Code" },
  "print": { channel: "Offline", subChannel: "Print Ad" },
  "billboard": { channel: "Offline", subChannel: "Billboard" },
  "mailer": { channel: "Offline", subChannel: "Direct Mail" },
  "direct_mail": { channel: "Offline", subChannel: "Direct Mail" },
  "radio": { channel: "Offline", subChannel: "Radio" },
  "tv": { channel: "Offline", subChannel: "TV" },
  "event": { channel: "Offline", subChannel: "Event" },
  "tradeshow": { channel: "Offline", subChannel: "Trade Show" },
};

function parseRefHost(ref: string): string {
  try { return new URL(ref).hostname.replace(/^www\./, ""); } catch { return ""; }
}

function refDomainPrefix(host: string): string {
  return host.split(".")[0];
}

export function classifyChannelDetailed(session: ChannelSession): ChannelDetail {
  const src = (session.utmSource ?? "").toLowerCase();
  const med = (session.utmMedium ?? "").toLowerCase();
  const camp = (session.utmCampaign ?? "").toLowerCase();
  const refHost = session.referrer ? parseRefHost(session.referrer) : "";
  const prefix = refHost ? refDomainPrefix(refHost) : "";

  // ── OTT / CTV ──
  if (med === "ott" || med === "ctv" || med === "connected_tv" || med === "streaming"
    || camp.includes("ott") || camp.includes("ctv")
    || /^(roku|hulu|peacock|tubi|pluto|sling|fubo|paramount|samsung_tv|lg_tv|vizio|amazon_fire|apple_tv)$/.test(src)) {
    return { channel: "OTT/CTV", subChannel: "Connected TV", source: src || "ott" };
  }

  // ── Programmatic / Display ──
  if (med === "programmatic" || med === "display" || med === "banner" || med === "cpm"
    || session.dclid
    || /^(dv360|thetradedesk|ttd|amazon_dsp|xandr|adroll|criteo|taboola|outbrain|stackadapt)$/.test(src)) {
    return { channel: "Programmatic", subChannel: "Display", source: src || "programmatic" };
  }

  // ── Paid Search — Google ──
  if (session.gclid || session.wbraid || session.gbraid
    || ((src === "google" || src === "google_ads" || src === "adwords") && /^(cpc|ppc|paid)$/.test(med))) {
    return { channel: "Paid Search", subChannel: "Google Ads", source: "google" };
  }

  // ── Paid Search — Bing ──
  if (session.msclkid || ((src === "bing" || src === "microsoft_ads") && /^(cpc|ppc|paid)$/.test(med))) {
    return { channel: "Paid Search", subChannel: "Microsoft Ads", source: "bing" };
  }

  // ── Paid Social — Meta ──
  if (session.fbclid || (/^(facebook|fb|instagram|ig|meta)$/.test(src) && /^(cpc|paid|paid_social|paidsocial)$/.test(med))) {
    const sub = /^(instagram|ig)$/.test(src) ? "Instagram Ads" : "Facebook Ads";
    return { channel: "Paid Social", subChannel: sub, source: src || "meta" };
  }

  // ── Paid Social — TikTok ──
  if (session.ttclid || (/^(tiktok|tt)$/.test(src) && /^(cpc|paid|paid_social)$/.test(med))) {
    return { channel: "Paid Social", subChannel: "TikTok Ads", source: "tiktok" };
  }

  // ── Paid Social — LinkedIn ──
  if (session.li_fat_id || (/^(linkedin|li)$/.test(src) && /^(cpc|paid|paid_social|sponsored)$/.test(med))) {
    return { channel: "Paid Social", subChannel: "LinkedIn Ads", source: "linkedin" };
  }

  // ── Paid Social — other platforms ──
  if (/^(pinterest|snapchat|reddit|youtube)$/.test(src) && /^(cpc|paid|paid_social|video|trueview)$/.test(med)) {
    const nameMap: Record<string, string> = { pinterest: "Pinterest Ads", snapchat: "Snapchat Ads", reddit: "Reddit Ads", youtube: "YouTube Ads" };
    return { channel: "Paid Social", subChannel: nameMap[src] ?? `${src} Ads`, source: src };
  }

  // ── Paid Social — X/Twitter ──
  if (/^(twitter|x)$/.test(src) && /^(cpc|paid|paid_social)$/.test(med)) {
    return { channel: "Paid Social", subChannel: "X Ads", source: src };
  }

  // ── Paid Video ──
  if (/^(video|pre_roll|mid_roll|bumper)$/.test(med)) {
    return { channel: "Paid Video", subChannel: "Video Ads", source: src || "video" };
  }

  // ── Audio Ads ──
  if (med === "audio" || /^(spotify|pandora|iheart)$/.test(src)) {
    return { channel: "Audio", subChannel: "Audio Ads", source: src || "audio" };
  }

  // ── Generic paid medium catch-alls ──
  if (/^(cpc|ppc|paid|cost_per_click|paidsearch|cpv|cpa)$/.test(med)) {
    return { channel: "Paid Other", subChannel: src || "Paid Traffic", source: src || "paid" };
  }
  if (med === "paid_social" || med === "paidsocial") {
    return { channel: "Paid Social", subChannel: src || "Unknown Social", source: src || "paid_social" };
  }
  if (/^(display|banner|cpm|interstitial|rich_media)$/.test(med)) {
    return { channel: "Programmatic", subChannel: src || "Display", source: src || "display" };
  }
  if (/^(video|pre_roll|mid_roll|bumper|in_stream|outstream)$/.test(med)) {
    return { channel: "Paid Video", subChannel: src || "Video", source: src || "video" };
  }
  if (/^(affiliate|referral_partner|partner)$/.test(med)) {
    return { channel: "Affiliate", subChannel: src || "Affiliate", source: src || "affiliate" };
  }
  if (/^(social|social-network|social-media|sm)$/.test(med) && !session.fbclid) {
    return { channel: "Organic Social", subChannel: src || "Social", source: src || "social" };
  }

  // ── Email ──
  if (med === "email" || /^(email|newsletter|mailchimp|constantcontact|sendgrid|hubspot_email|klaviyo|activecampaign)$/.test(src)) {
    return { channel: "Email", subChannel: "Email Marketing", source: src || "email" };
  }

  // ── SMS ──
  if (med === "sms" || /^(sms|twilio|text|podium|birdeye)$/.test(src)) {
    return { channel: "SMS", subChannel: "Text Message", source: src || "sms" };
  }

  // ── GBP / Local / Maps (UTM-based) ──
  if (/^(gmb|gbp|google_my_business|google_business_profile|google_maps|maps)$/.test(src)
    || (src === "google" && /^(local|gmb|gbp|maps)$/.test(med))
    || camp.includes("gbp") || camp.includes("gmb") || camp.includes("google_business")
    || (session.referrer && /ludocid|\/maps\/place\/|business\.google\.com/.test(session.referrer))) {
    return { channel: "Local", subChannel: "Google Business Profile", source: "gbp" };
  }
  if (src === "google" && med === "local") {
    return { channel: "Local", subChannel: "Google Local Services", source: "google_local" };
  }

  // ── UTM source map (agency naming conventions) ──
  if (src && UTM_SOURCE_MAP[src] && UTM_SOURCE_MAP[src].channel !== "depends_on_medium") {
    const mapped = UTM_SOURCE_MAP[src];
    return { channel: mapped.channel, subChannel: mapped.subChannel, source: src };
  }

  // ── Organic Search (referrer-based) ──
  if (refHost && SEARCH_ENGINES[prefix] && !session.gclid && med !== "cpc") {
    return { channel: "Organic Search", subChannel: SEARCH_ENGINES[prefix], source: SEARCH_ENGINES[prefix].toLowerCase() };
  }
  if (med === "organic" || /^(google|bing|yahoo|duckduckgo)$/.test(src)) {
    return { channel: "Organic Search", subChannel: SEARCH_ENGINES[src] ?? src, source: src || "organic" };
  }

  // ── AI / LLM Referrals ──
  if (refHost && AI_REFERRERS[refHost]) {
    return { channel: "AI Referral", subChannel: AI_REFERRERS[refHost], source: AI_REFERRERS[refHost].toLowerCase() };
  }

  // ── Organic Social (referrer-based) ──
  if (refHost && SOCIAL_PLATFORMS[prefix] && med !== "cpc" && med !== "paid") {
    // Detect Facebook Groups / Marketplace as a sub-channel
    if (session.referrer && /facebook\.com\/(groups|marketplace)/.test(session.referrer)) {
      return { channel: "Organic Social", subChannel: "Facebook Groups/Marketplace", source: "facebook" };
    }
    return { channel: "Organic Social", subChannel: SOCIAL_PLATFORMS[prefix], source: SOCIAL_PLATFORMS[prefix].toLowerCase() };
  }
  if (SOCIAL_PLATFORMS[src]) {
    return { channel: "Organic Social", subChannel: SOCIAL_PLATFORMS[src], source: src };
  }

  // ── Directory / Review sites ──
  if (refHost && DIRECTORY_SITES[refHost]) {
    return { channel: "Directory/Review", subChannel: DIRECTORY_SITES[refHost], source: DIRECTORY_SITES[refHost].toLowerCase() };
  }
  // Directory via UTM source
  if (DIRECTORY_SITES[`${src}.com`] || DIRECTORY_SITES[`${src}.org`]) {
    const name = DIRECTORY_SITES[`${src}.com`] ?? DIRECTORY_SITES[`${src}.org`] ?? src;
    return { channel: "Directory/Review", subChannel: name, source: src };
  }

  // ── Local — Maps referrals ──
  if (session.referrer && /maps\.google|google\.com\/maps/.test(session.referrer)) {
    return { channel: "Local", subChannel: "Google Business Profile", source: "google_maps" };
  }
  if (refHost && refHost.includes("maps.apple")) {
    return { channel: "Local", subChannel: "Apple Maps", source: "apple_maps" };
  }
  if (refHost && /bing\.com\/maps/.test(refHost)) {
    return { channel: "Local", subChannel: "Bing Places", source: "bing_places" };
  }
  if (refHost && refHost.includes("waze.com")) {
    return { channel: "Local", subChannel: "Waze", source: "waze" };
  }

  // ── Referral medium with known source ──
  if (med === "referral" && src) {
    return { channel: "Referral", subChannel: src, source: src };
  }

  // ── Referral (generic external site) ──
  if (refHost) {
    return { channel: "Referral", subChannel: refHost, source: refHost };
  }

  // ── Direct ──
  if (!session.referrer && !src && !med && !session.gclid && !session.fbclid && !session.msclkid) {
    return { channel: "Direct", subChannel: "Direct Traffic", source: "direct" };
  }

  return { channel: "Other", subChannel: med || "unknown", source: src || "unknown" };
}

/** Backward-compatible wrapper — returns just the channel name string. */
export function classifyChannel(session: ChannelSession): string {
  return classifyChannelDetailed(session).channel;
}

// ─── Types ──────────────────────────────────────────────────────

interface Touchpoint {
  sessionId: string;
  channel: string;
  source: string | null;
  medium: string | null;
  campaign: string | null;
  adClickId: string | null;
  timestamp: string;
}

interface BuildResult {
  conversionsProcessed: number;
  pathsCreated: number;
  pathsUpdated: number;
  errors: number;
}

// ─── Builder ────────────────────────────────────────────────────

export async function buildAttributionPaths(
  brandId: string,
  opts: { since?: Date; limit?: number } = {},
): Promise<BuildResult> {
  const since = opts.since ?? new Date(Date.now() - 90 * 86_400_000);
  const limit = opts.limit ?? 10_000;

  // intentionally unfiltered: path-building; needs all conversions to construct attribution chains
  const conversions = await prisma.siteConversion.findMany({
    where: { brandId, timestamp: { gte: since } },
    select: {
      id: true,
      visitorId: true,
      sessionId: true,
      conversionType: true,
      conversionValue: true,
      timestamp: true,
    },
    orderBy: { timestamp: "asc" },
    take: limit,
  });

  const site = await prisma.trackedSite.findUnique({
    where: { brandId },
    select: { id: true },
  });
  if (!site) {
    return { conversionsProcessed: 0, pathsCreated: 0, pathsUpdated: 0, errors: 0 };
  }

  const visitorIds = [...new Set(conversions.map((c) => c.visitorId))];

  const allSessions = await prisma.trackedSession.findMany({
    where: {
      trackedSiteId: site.id,
      sessionId: { in: visitorIds.length > 0 ? visitorIds : ["__none__"] },
      startedAt: { gte: new Date(since.getTime() - 90 * 86_400_000) },
    },
    select: {
      id: true,
      sessionId: true,
      startedAt: true,
      entryPage: true,
      referrer: true,
      utmSource: true,
      utmMedium: true,
      utmCampaign: true,
      gclid: true,
      fbclid: true,
      msclkid: true,
      ttclid: true,
      li_fat_id: true,
      dclid: true,
      wbraid: true,
      gbraid: true,
      ipAddress: true,
      device: true,
      browser: true,
      os: true,
    },
    orderBy: { startedAt: "asc" },
    take: 100_000,
  });

  // Index sessions by sessionId for quick lookup during touchpoint
  // assembly. SiteConversion.sessionId is the raw client string
  // (the localStorage visitor UUID), while TrackedSession.sessionId
  // is also the raw client string. But SiteConversion.visitorId is
  // the localStorage UUID and the conversion's sessionId might be
  // either the visitor UUID or the per-tab session UUID depending on
  // which write path created the row. We build a lookup by BOTH
  // TrackedSession.sessionId and the visitor's ID to maximise match
  // rate.
  const sessionsByVisitorIsh = new Map<string, typeof allSessions>();
  for (const s of allSessions) {
    const key = s.sessionId;
    const arr = sessionsByVisitorIsh.get(key) ?? [];
    arr.push(s);
    sessionsByVisitorIsh.set(key, arr);
  }

  const result: BuildResult = {
    conversionsProcessed: conversions.length,
    pathsCreated: 0,
    pathsUpdated: 0,
    errors: 0,
  };

  for (const conv of conversions) {
    try {
      // Find all sessions for this visitor, ordered chronologically.
      // The visitor's sessions may span weeks — we take everything
      // from 90 days before the conversion window to the conversion
      // timestamp.
      const visitorSessions = sessionsByVisitorIsh.get(conv.visitorId) ?? [];
      const relevantSessions = visitorSessions.filter(
        (s) => s.startedAt <= conv.timestamp,
      );

      const touchpoints: Touchpoint[] = relevantSessions.map((s) => ({
        sessionId: s.sessionId,
        channel: classifyChannel(s),
        source: s.utmSource,
        medium: s.utmMedium,
        campaign: s.utmCampaign,
        adClickId: s.gclid ?? s.fbclid ?? s.msclkid ?? s.ttclid ?? s.li_fat_id ?? s.dclid ?? s.wbraid ?? s.gbraid ?? null,
        timestamp: s.startedAt.toISOString(),
      }));

      // Prepend OTT view-through impressions as the earliest
      // touchpoint when matched. The TV ad impression happened
      // BEFORE any web session — it's the true first touch.
      try {
        const ottImps = await prisma.ottImpression.findMany({
          where: { brandId, matchedVisitorId: conv.visitorId, matched: true },
          orderBy: { impressionAt: "asc" },
          select: { platform: true, impressionAt: true, campaignName: true },
          take: 5,
        });
        for (let i = ottImps.length - 1; i >= 0; i--) {
          touchpoints.unshift({
            sessionId: `ott_${ottImps[i].platform}`,
            channel: "OTT/CTV",
            source: ottImps[i].platform,
            medium: "view_through",
            campaign: ottImps[i].campaignName,
            adClickId: null,
            timestamp: ottImps[i].impressionAt.toISOString(),
          });
        }
      } catch { /* OTT data optional — don't break attribution */ }

      if (touchpoints.length === 0) {
        touchpoints.push({
          sessionId: conv.sessionId,
          channel: "Direct",
          source: null,
          medium: null,
          campaign: null,
          adClickId: null,
          timestamp: conv.timestamp.toISOString(),
        });
      }

      const firstTouch = touchpoints[0];
      const lastTouch = touchpoints[touchpoints.length - 1];
      const daysBetween = Math.max(
        0,
        Math.round(
          (conv.timestamp.getTime() - new Date(firstTouch.timestamp).getTime()) / 86_400_000,
        ),
      );

      await prisma.attributionPath.upsert({
        where: {
          brandId_conversionId: { brandId, conversionId: conv.id },
        },
        create: {
          brandId,
          conversionId: conv.id,
          visitorId: conv.visitorId,
          conversionType: conv.conversionType,
          conversionValue: conv.conversionValue,
          touchpoints: touchpoints as unknown as object,
          touchpointCount: touchpoints.length,
          firstTouchChannel: firstTouch.channel,
          lastTouchChannel: lastTouch.channel,
          convertingChannel: lastTouch.channel,
          daysBetweenFirstAndConversion: daysBetween,
        },
        update: {
          visitorId: conv.visitorId,
          conversionType: conv.conversionType,
          conversionValue: conv.conversionValue,
          touchpoints: touchpoints as unknown as object,
          touchpointCount: touchpoints.length,
          firstTouchChannel: firstTouch.channel,
          lastTouchChannel: lastTouch.channel,
          convertingChannel: lastTouch.channel,
          daysBetweenFirstAndConversion: daysBetween,
        },
      });

      result.pathsCreated++;
    } catch (err) {
      result.errors++;
      console.warn("[attribution] path build failed", {
        conversionId: conv.id,
        err: err instanceof Error ? err.message : String(err),
      });
    }
  }

  console.info("[attribution] build complete", {
    brandId,
    ...result,
  });

  return result;
}
