/**
 * Industry Intelligence Service
 * ─────────────────────────────
 * Gathers brand context from all platform data, calls Claude for
 * market intelligence + performance correlations, stores as
 * IndustryBriefing. 24-hour cache per brand.
 */

import { prisma } from "@/lib/prisma";
import { callLlm } from "@/lib/ai-avatar/llm-client";
import { logAiInteraction } from "@/lib/services/ai-interaction-log";
import { getBrandLocations } from "@/lib/brand-helpers";

// ─── Brand context gathering ────────────────────────────────────────────────

export interface BrandLocation {
  /** "Houston, TX" or just "Houston" or just "TX" — whichever is available. */
  label: string;
  city: string | null;
  state: string | null;
  /** Where this location came from. Drives prompt-side weighting. */
  source: "gbp" | "brand_profile" | "site_tag";
}

export interface BrandContext {
  brandId: string; name: string; domain: string;
  industry: string;
  /** Backwards-compat: simple string list used by the existing
   *  collectMarketIntelligence prompt. Built from `locationDetails`. */
  locations: string[];
  /** Richer location data with provenance. Drives the local-scope
   *  prompt — GBP locations are the highest-confidence signal, then
   *  brand profile, then Site Tag visitor countries. */
  locationDetails: BrandLocation[];
  /** Top countries from Site Tag sessions (last 30d). The schema
   *  only stores country at the moment, not city — we surface what
   *  we have so the local prompt can weight by where visitors
   *  actually come from. */
  topVisitorCountries: Array<{ country: string; sessions: number }>;
  recentGsc: { clicks: number; impressions: number; clicksDelta: number | null };
  recentGa4: { sessions: number; conversions: number } | null;
  topKeywords: Array<{ query: string; position: number; clicks: number }>;
  competitorDomains: string[];
}

export async function gatherBrandContext(brandId: string): Promise<BrandContext | null> {
  const brand = await prisma.brand.findUnique({ where: { id: brandId }, include: { profile: true } });
  if (!brand) return null;
  const industry = brand.profile?.industry || brand.industry;
  if (!industry) return null;
  const profileLocations = brand.profile?.locations ?? [];

  const now = new Date();
  const d30 = new Date(now.getTime() - 30 * 86400000);
  const d60 = new Date(now.getTime() - 60 * 86400000);

  const [gscCur, gscPrev, ga4, topKw, comps, gbpLocs, site] = await Promise.all([
    prisma.gscPage.aggregate({ where: { brandId, date: { gte: d30 } }, _sum: { clicks: true, impressions: true } }),
    prisma.gscPage.aggregate({ where: { brandId, date: { gte: d60, lt: d30 } }, _sum: { clicks: true, impressions: true } }),
    prisma.metricSnapshot.findFirst({ where: { brandId, date: { gte: d30 } }, orderBy: { date: "desc" } }),
    prisma.gscQuery.findMany({ where: { brandId, date: { gte: d30 } }, orderBy: { clicks: "desc" }, take: 15, select: { query: true, position: true, clicks: true } }),
    prisma.competitor.findMany({ where: { brandId, isActive: true }, select: { domain: true }, take: 5 }),
    // GBP-confirmed physical locations — the strongest local signal.
    prisma.gbpLocation.findMany({
      where: { brandId, isActive: true },
      select: { city: true, state: true, locationName: true },
    }),
    prisma.trackedSite.findUnique({ where: { brandId }, select: { id: true } }),
  ]);

  // Top visitor countries from Site Tag — only `country` is captured
  // on TrackedSession today (no city). Best-effort, swallows errors so
  // a missing tracker doesn't break briefing generation.
  let topVisitorCountries: Array<{ country: string; sessions: number }> = [];
  if (site?.id) {
    try {
      const grouped = await prisma.trackedSession.groupBy({
        by: ["country"],
        where: { trackedSiteId: site.id, country: { not: null }, startedAt: { gte: d30 } },
        _count: true,
        orderBy: { _count: { country: "desc" } },
        take: 5,
      });
      topVisitorCountries = grouped
        .filter((g) => g.country)
        .map((g) => ({ country: g.country!, sessions: g._count }));
    } catch { /* tracker schema mismatch — skip */ }
  }

  // Build the unified location list. Dedupe by city+state so the same
  // city showing up in both GBP and brand profile only counts once.
  const seen = new Set<string>();
  const locationDetails: BrandLocation[] = [];
  function add(loc: BrandLocation) {
    const key = `${(loc.city ?? "").toLowerCase()}|${(loc.state ?? "").toLowerCase()}|${loc.label.toLowerCase()}`;
    if (seen.has(key) || (!loc.city && !loc.state && !loc.label)) return;
    seen.add(key);
    locationDetails.push(loc);
  }
  // GBP wins priority — it's the verified-by-Google source of truth.
  for (const g of gbpLocs) {
    if (g.city || g.state) {
      add({
        label: [g.city, g.state].filter(Boolean).join(", ") || g.locationName,
        city: g.city ?? null,
        state: g.state ?? null,
        source: "gbp",
      });
    }
  }
  // Brand profile locations (string array — could be a city, "City, ST", or a country).
  for (const p of profileLocations) {
    add({ label: p, city: null, state: null, source: "brand_profile" });
  }
  // Brand.locations[] + legacy location field as backstop — only when
  // GBP and profile.locations both produced nothing. The length guard
  // must be OUTSIDE the loop so all locations get added, not just the
  // first one (inside-loop guard was a Tier-2C migration bug).
  if (locationDetails.length === 0) {
    for (const l of getBrandLocations(brand)) {
      add({ label: l, city: null, state: null, source: "brand_profile" });
    }
  }

  // Backwards-compat string list for the existing prompt path.
  const locations = locationDetails.length > 0
    ? locationDetails.map((l) => l.label)
    : ["United States"];

  const curClicks = gscCur._sum.clicks ?? 0;
  const prevClicks = gscPrev._sum.clicks ?? 0;
  const clicksDelta = prevClicks > 0 ? ((curClicks - prevClicks) / prevClicks) * 100 : null;

  return {
    brandId, name: brand.name, domain: brand.domain,
    industry,
    locations,
    locationDetails,
    topVisitorCountries,
    recentGsc: { clicks: curClicks, impressions: gscCur._sum.impressions ?? 0, clicksDelta },
    recentGa4: ga4 ? { sessions: ga4.sessions, conversions: ga4.conversions } : null,
    topKeywords: topKw.map((k) => ({ query: k.query, position: k.position, clicks: k.clicks })),
    competitorDomains: comps.map((c) => c.domain),
  };
}

// ─── Market intelligence via Claude ─────────────────────────────────────────

function extractJson(raw: string): string {
  let s = raw.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "");
  const f = s.indexOf("{"), l = s.lastIndexOf("}");
  if (f >= 0 && l > f) s = s.slice(f, l + 1);
  return s;
}

export type BriefingScope = "local" | "national";

/** Industry-vertical regulatory hint strings. Injected into the
 *  LOCAL prompt so the AI grounds its regulatory section in the
 *  actually-relevant regime for this brand instead of defaulting
 *  to generic "state medical board" copy regardless of industry.
 *  Keys match brand.industry values (Title Case + space-separated
 *  is the convention in BrandProfile). Unknown industries fall
 *  through to the GENERIC hint. */
const INDUSTRY_REGULATORY_HINTS: Record<string, string> = {
  "Healthcare":       "HIPAA for patient-facing content, state medical board advertising rules, CMS quality reporting, insurance network disclosures, telehealth parity laws.",
  "Dental":           "State dental board advertising rules, HIPAA for patient content, testimonial/review disclosure requirements, sedation licensing disclosures.",
  "Legal":            "State bar advertising rules (ABA Model Rule 7.1-7.5 equivalents), specialisation claim restrictions, client testimonial rules, trust-account disclosures.",
  "Real Estate":      "State real estate commission advertising rules, Fair Housing Act compliance, MLS attribution requirements, broker-disclaimer rules, RESPA.",
  "Financial":        "SEC/FINRA advertising rules, state banking/insurance commissioner rules, fiduciary disclosure, UDAAP.",
  "Home Services":    "State contractor licensing display rules, lead-based-paint RRP for pre-1978 homes, EPA refrigerant handling (HVAC), local permit disclosure.",
  "Fitness":          "State personal-trainer cert display rules, supplement disclosures per FTC/FDA, facility inspection results, liability waiver disclosures.",
  "Automotive":       "State DMV dealer licensing rules, truth-in-lending (Regulation Z) on finance copy, emissions compliance claims, odometer disclosures.",
  "E-commerce":       "FTC endorsement guides, truth-in-advertising, GDPR/CCPA privacy, state sales-tax nexus, ADA website accessibility (Title III).",
  "Restaurant":       "Local health department inspection scores, menu calorie posting (FDA 21 CFR 101.11), allergen labelling, alcohol licensing/advertising.",
};

const GENERIC_REGULATORY_HINT = "State/local licensing bodies relevant to this industry, FTC advertising rules (endorsements, testimonials), ADA website accessibility, state privacy/consumer-protection laws, any profession-specific advertising restrictions.";

export async function collectMarketIntelligence(
  industry: string,
  locations: string[],
  brandName?: string,
  services?: string[],
  performanceContext?: string,
  scope: BriefingScope = "national",
  visitorContext?: string,
  topKeywords?: Array<{ query: string; position: number; clicks: number }>,
  brandId?: string,
): Promise<Record<string, unknown>> {
  const brandCtx = brandName ? `\nBrand: ${brandName} (${industry})${services?.length ? `, services: ${services.join(", ")}` : ""}${performanceContext ? `\nCurrent performance: ${performanceContext}` : ""}` : "";
  // Resolve regulatory hint by exact industry match, then by
  // case-insensitive contains (covers "cardiology practice" →
  // "Healthcare"), then the generic fallback. Exposing the actual
  // regulators + laws in the prompt dramatically improves the
  // specificity of what the AI returns in the regulatory section.
  const regHint = (() => {
    if (INDUSTRY_REGULATORY_HINTS[industry]) return INDUSTRY_REGULATORY_HINTS[industry];
    const lower = industry.toLowerCase();
    for (const [key, hint] of Object.entries(INDUSTRY_REGULATORY_HINTS)) {
      if (lower.includes(key.toLowerCase())) return hint;
    }
    // Sub-speciality fuzzy fallback — "cardiology", "dermatology",
    // "pediatrics" etc. all roll up to healthcare regulatory for
    // practical purposes even if they weren't typed as "Healthcare"
    // in the brand profile.
    if (/cardio|derma|pediatr|ortho|oncol|neurol|surgery|clinic|hospital|medical|physician/.test(lower)) return INDUSTRY_REGULATORY_HINTS["Healthcare"];
    if (/dental|dentist|orthodont/.test(lower)) return INDUSTRY_REGULATORY_HINTS["Dental"];
    if (/law|legal|attorney|firm/.test(lower)) return INDUSTRY_REGULATORY_HINTS["Legal"];
    return GENERIC_REGULATORY_HINT;
  })();
  // Keyword-correlation context — when we have GSC top keywords,
  // ask the AI to tie seasonal + weather predictions to actual
  // brand search volume instead of hand-waving at "winter
  // conditions". Empty string when no keywords are available; the
  // prompt degrades gracefully.
  const keywordContext = topKeywords && topKeywords.length > 0
    ? `\nBrand's top GSC keywords (tie seasonal predictions to these where relevant): ${topKeywords.slice(0, 8).map((k) => `"${k.query}" (pos ${k.position.toFixed(1)}, ${k.clicks} clicks)`).join(", ")}`
    : "";

  // The LOCAL prompt asks for hyper-local intel grounded in specific
  // cities — weather, healthcare news, regulatory, community events,
  // local search trends, demographics, competitor activity. The
  // NATIONAL prompt is the original broad industry view.
  const userPrompt = scope === "local"
    ? `Generate a HYPER-LOCAL intelligence briefing for ${brandName || "this business"} (${industry}) operating in ${locations.join("; ")}. Today is ${new Date().toISOString().split("T")[0]}.${brandCtx}${visitorContext ? `\n${visitorContext}` : ""}${keywordContext}\n\nREGULATORY CONTEXT FOR THIS INDUSTRY: ${regHint}\n\nFocus on what's happening RIGHT NOW in these specific cities/regions that affects this business. Cover all of:\n\n1. LOCAL WEATHER & SEASONAL IMPACT — current/upcoming severe weather (heat, storms, flooding) in these specific cities; how the weather affects the industry's customer behaviour or service demand. Where applicable, tie the weather/seasonal pattern to specific brand keywords above and predict whether search volume for those queries should rise or fall.\n2. LOCAL ${industry.toUpperCase()} NEWS — facility openings/closings, staffing changes, new local competitors, insurance/payer/service-area shifts relevant to this industry vertical.\n3. LOCAL REGULATORY & COMPLIANCE — city/county/state-level rules grounded in the REGULATORY CONTEXT above. Cite actual regulators by name (state board, commission, agency) when known.\n4. LOCAL COMMUNITY & EVENTS — events in the named cities relevant to this industry's audience (health fairs for healthcare, CLE for legal, home-show for home services, etc.).\n5. LOCAL SEARCH TRENDS — trending searches in the city/metro; "near me" patterns; seasonal local queries; how brand keywords above are likely trending in this geo right now.\n6. LOCAL DEMOGRAPHIC SHIFTS — population growth, new housing, aging trends, insurance/payer coverage changes by zip/area as relevant to this industry's buyer journey.\n7. LOCAL COMPETITOR ACTIVITY — new competitor locations, marketing campaigns, review trends specific to these cities.\n\nReturn JSON shaped as:\n{"marketTrends":[{"title":"...","description":"...","impact":"high|medium|low","category":"weather|healthcare|regulatory|community|search_trends|demographics|competitor","location":"which named city/area this affects","timeframe":"immediate|this_week|this_month|this_quarter","suggestedAction":"concrete action for ${brandName || "the brand"}"}],"externalEvents":[{"type":"weather|regulatory|economic|health|technology|cultural","title":"...","description":"...","affectedLocations":["..."],"severity":"critical|major|moderate|minor","suggestedAction":"..."}],"seasonalInsights":[{"pattern":"...","currentPhase":"...","prediction":"...","peakPeriod":"...","actionItem":"specific action"}],"searchTrends":[{"keyword":"...","direction":"rising|declining|stable","location":"which area","opportunity":"how to capitalize"}],"predictions":[{"title":"...","description":"...","confidence":"high|medium|low","timeframe":"30|60|90 days","location":"which area","recommendation":"specific recommendation"}],"localEvents":[{"name":"...","date":"YYYY-MM-DD or month","city":"...","relevance":"why this matters for the brand"}]}\n\nBe specific. Name actual neighbourhoods, hospitals, school districts, weather patterns, regulators. No generic national-level commentary.`
    : `Research the current state of the ${industry} industry in ${locations.join(", ")}. Today is ${new Date().toISOString().split("T")[0]}.${brandCtx}\n\nFocus specifically on factors affecting ${brandName || "businesses"} in ${locations.join(", ")}. Make all insights actionable for this specific brand and location.\n\nReturn JSON: {"marketTrends":[{"title":"...","description":"...","impact":"high|medium|low","relevance":"how this specifically affects ${brandName || "the brand"}","timeframe":"immediate|short-term|long-term"}],"externalEvents":[{"type":"weather|regulatory|economic|health|technology|cultural","title":"...","description":"...","affectedLocations":["..."],"searchImpact":"...","severity":"critical|major|moderate|minor"}],"seasonalInsights":[{"pattern":"...","currentPhase":"...","prediction":"...","peakPeriod":"...","actionItem":"specific action for ${brandName || "the brand"}"}],"searchTrends":[{"keyword":"...","direction":"rising|declining|stable","opportunity":"how ${brandName || "the brand"} can capitalize"}],"predictions":[{"title":"...","description":"...","confidence":"high|medium|low","timeframe":"30|60|90 days","recommendation":"specific recommendation for ${brandName || "the brand"}"}]}\n\nBe specific with dates and data. Reference real events, weather, regulations, seasonal patterns, Google algorithm updates, and consumer behavior shifts.`;

  const result = await callLlm([
    { role: "system", content: scope === "local"
      ? "You are a hyper-local market intelligence analyst. Every insight must reference a specific named city, neighbourhood, hospital, school district, or local regulator. Respond ONLY with JSON."
      : "You are a market intelligence analyst. Respond ONLY with JSON." },
    { role: "user", content: userPrompt },
  ], () => JSON.stringify({ marketTrends: [], externalEvents: [], seasonalInsights: [], searchTrends: [], predictions: [], localEvents: [] }), { maxTokens: 4500 });

  logAiInteraction({
    brandId: brandId ?? null,
    feature: "industry_intel",
    userPrompt,
    aiResponse: result.reply,
    model: result.model,
  });

  try { return JSON.parse(extractJson(result.reply)) as Record<string, unknown>; }
  catch { return { marketTrends: [], externalEvents: [], seasonalInsights: [], searchTrends: [], predictions: [], localEvents: [], raw: result.reply.slice(0, 500) }; }
}

// ─── Performance correlation ────────────────────────────────────────────────

export async function correlatePerformance(ctx: BrandContext, events: unknown[]): Promise<unknown[]> {
  if (!events || !Array.isArray(events) || events.length === 0) return [];
  const perfSnapshot = `Clicks: ${ctx.recentGsc.clicks} (${ctx.recentGsc.clicksDelta != null ? ctx.recentGsc.clicksDelta.toFixed(1) + "% vs prev 30d" : "no comparison"})${ctx.recentGa4 ? `, Sessions: ${ctx.recentGa4.sessions}, Conversions: ${ctx.recentGa4.conversions}` : ""}`;

  const userPrompt = `Events: ${JSON.stringify(events.slice(0, 8))}\nPerformance: ${perfSnapshot}\nTop keywords: ${ctx.topKeywords.slice(0, 5).map((k) => `${k.query} (pos ${k.position})`).join(", ")}\n\nReturn JSON: {"correlations":[{"event":"...","metric":"clicks|sessions|conversions","correlation":"positive|negative|neutral","explanation":"...","normalizedPerformance":"what perf would be without this event","recovery":"expected timeline"}]}`;
  const result = await callLlm([
    { role: "system", content: "You are a performance analyst. Respond ONLY with JSON." },
    { role: "user", content: userPrompt },
  ], () => JSON.stringify({ correlations: [] }), { maxTokens: 2000 });

  logAiInteraction({
    brandId: ctx.brandId,
    feature: "industry_intel",
    userPrompt,
    aiResponse: result.reply,
    model: result.model,
    brandDomain: ctx.domain,
  });

  try {
    const parsed = JSON.parse(extractJson(result.reply)) as { correlations?: unknown[] };
    return parsed.correlations ?? [];
  } catch { return []; }
}

// ─── Generate full briefing ─────────────────────────────────────────────────

export async function generateBriefing(
  brandId: string,
  options?: { scope?: BriefingScope; force?: boolean },
): Promise<{ id: string } | null> {
  const scope: BriefingScope = options?.scope ?? "local";
  // 24-hour cache check — keyed by scope so local + national briefings
  // don't collide. The IndustryBriefing schema doesn't have a scope
  // column; we encode it in the `period` field instead, which is
  // already a free-form string ("daily" historically). Cached briefings
  // from before this change will all be `period="daily"` and never
  // match a scope-prefixed key, so the first request after deploy
  // generates fresh.
  const cacheKey = `${scope}:daily`;
  if (!options?.force) {
    const recent = await prisma.industryBriefing.findFirst({
      where: { brandId, period: cacheKey, generatedAt: { gte: new Date(Date.now() - 24 * 3600000) } },
      orderBy: { generatedAt: "desc" }, select: { id: true },
    });
    if (recent) return recent;
  }

  const ctx = await gatherBrandContext(brandId);
  if (!ctx) return null;

  // Gather brand profile for services
  const profile = await prisma.brand.findUnique({ where: { id: brandId }, include: { profile: { select: { services: true } } } });
  const services = profile?.profile?.services ?? [];
  const perfCtx = `${ctx.recentGsc.clicks} clicks${ctx.recentGsc.clicksDelta != null ? ` (${ctx.recentGsc.clicksDelta > 0 ? "+" : ""}${ctx.recentGsc.clicksDelta.toFixed(1)}% trend)` : ""}`;

  // For local scope, emphasise GBP-confirmed cities and surface the
  // top visitor countries so the AI can weight by where users
  // actually come from. For national scope, use the simple location
  // list as before.
  const promptLocations = scope === "local"
    ? ctx.locationDetails.map((l) => l.label).filter((s) => s.length > 0)
    : ctx.locations;
  const visitorContext = scope === "local" && ctx.topVisitorCountries.length > 0
    ? `Top visitor countries from website (last 30 days): ${ctx.topVisitorCountries.map((c) => `${c.country} (${c.sessions})`).join(", ")}`
    : undefined;

  const intel = await collectMarketIntelligence(
    ctx.industry,
    promptLocations.length > 0 ? promptLocations : ctx.locations,
    ctx.name,
    services,
    perfCtx,
    scope,
    visitorContext,
    // Pass top GSC keywords so the LOCAL prompt can correlate
    // weather/seasonal predictions to the brand's actual search
    // demand instead of speaking abstractly about "winter health
    // conditions". Empty / missing keywords degrade gracefully.
    ctx.topKeywords,
    ctx.brandId,
  );
  const correlations = await correlatePerformance(ctx, (intel.externalEvents as unknown[]) ?? []);

  // Executive summary — brand-specific, opens with brand name. Local
  // scope uses location-aware framing; national uses the original
  // industry framing.
  const summaryUserPrompt = `Today's ${scope} briefing for ${ctx.name} in ${ctx.industry} serving ${promptLocations.join(", ") || ctx.locations.join(", ")}.\nPerformance: ${perfCtx}, ${ctx.recentGa4 ? `${ctx.recentGa4.sessions} sessions` : "no GA4"}, top keyword: "${ctx.topKeywords[0]?.query ?? "none"}".\nKey trends: ${JSON.stringify((intel.marketTrends as unknown[])?.slice(0, 2))}.\nEvents: ${JSON.stringify((intel.externalEvents as unknown[])?.slice(0, 2))}.\n\nStart the summary with: "For ${ctx.name}..."`;
  const summaryResult = await callLlm([
    { role: "system", content: scope === "local"
      ? "Write a 2-3 sentence executive summary of this LOCAL intelligence briefing. Start with the brand name. Reference specific cities or local events. No generic national commentary."
      : "Write a 2-3 sentence executive summary. Start with the brand name. Be specific to this brand's industry, locations, and current performance. Not generic." },
    { role: "user", content: summaryUserPrompt },
  ], () => `For ${ctx.name} — configure AI API key for personalized analysis.`, { maxTokens: 300 });

  logAiInteraction({
    brandId: ctx.brandId,
    feature: "industry_intel",
    userPrompt: summaryUserPrompt,
    aiResponse: summaryResult.reply,
    model: summaryResult.model,
    brandDomain: ctx.domain,
  });

  const briefing = await prisma.industryBriefing.create({
    data: {
      brandId, industry: ctx.industry,
      // Persist the rich location list (with provenance + city/state)
      // so the UI can render location badges per intel item without
      // re-querying GBP.
      locations: (scope === "local"
        ? ctx.locationDetails
        : ctx.locations.map((l) => ({ label: l, city: null, state: null, source: "brand_profile" }))) as unknown as object,
      period: cacheKey,
      summary: summaryResult.reply.trim(),
      marketTrends: (intel.marketTrends ?? []) as object,
      externalEvents: (intel.externalEvents ?? []) as object,
      seasonalInsights: (intel.seasonalInsights ?? []) as object,
      searchTrends: (intel.searchTrends ?? []) as object,
      predictions: (intel.predictions ?? []) as object,
      performanceCorrelation: correlations as object,
      competitorActivity: ctx.competitorDomains.length ? { competitors: ctx.competitorDomains } as object : undefined,
      sources: {
        gsc: true,
        ga4: !!ctx.recentGa4,
        ai: true,
        competitors: ctx.competitorDomains.length > 0,
        gbp: ctx.locationDetails.some((l) => l.source === "gbp"),
        siteTag: ctx.topVisitorCountries.length > 0,
        scope,
        // Visitor geography (Site Tag) + service locations (GBP +
        // brand profile) persisted verbatim so the client can render
        // a "Where Your Visitors Come From" / "Where You Serve"
        // section without re-hitting the tracker. Country-level only
        // — TrackedSession.country is what the schema captures today;
        // city-level would require a Prisma migration + IP → city
        // resolver in /api/collect.
        visitorCountries: ctx.topVisitorCountries,
        serviceLocations: ctx.locationDetails,
        // Diagnostic for the "enable geo tracking" empty state.
        // `geoTrackingEnabled` = the Site Tag has sessions with a
        // resolved country. False + Site Tag installed = the
        // tracker is live but country capture is off (or all
        // sessions are anonymised); client surfaces a hint.
        geoTrackingEnabled: ctx.topVisitorCountries.length > 0,
      } as object,
    },
  });
  return { id: briefing.id };
}

// ─── Industry benchmarks (cross-brand aggregation) ──────────────────────────

export async function computeBenchmarks(industry: string, location?: string): Promise<void> {
  const brands = await prisma.brand.findMany({ where: { industry, isDemo: false }, select: { id: true } });
  if (brands.length < 5) return; // privacy threshold

  const brandIds = brands.map((b) => b.id);
  const d30 = new Date(Date.now() - 30 * 86400000);
  const period = `${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, "0")}`;

  const [gscAgg, convCount] = await Promise.all([
    prisma.gscPage.aggregate({ where: { brandId: { in: brandIds }, date: { gte: d30 } }, _avg: { ctr: true, position: true } }),
    // intentionally unfiltered: industry aggregation; cross-brand totals
    prisma.siteConversion.count({ where: { brandId: { in: brandIds }, timestamp: { gte: d30 } } }),
  ]);

  const metrics = [
    { metric: "avg_ctr", value: (gscAgg._avg.ctr ?? 0) * 100 },
    { metric: "avg_position", value: gscAgg._avg.position ?? 0 },
    { metric: "monthly_conversions_per_brand", value: convCount / brands.length },
  ];

  for (const m of metrics) {
    await prisma.industryBenchmark.upsert({
      where: { industry_location_metric_period: { industry, location: location ?? "", metric: m.metric, period } },
      create: { industry, location, metric: m.metric, value: m.value, sampleSize: brands.length, period },
      update: { value: m.value, sampleSize: brands.length },
    });
  }
}
