/**
 * Corpus seeding: mine AiAttribution rows and derive probe queries
 * for each (vertical, locale) cell.
 *
 * queryContext is a decoded #:~:text= page-text fragment from an AI
 * answer page -- NOT the user's literal search query. We derive a
 * candidate probe query from the fragment + landing page + brand name
 * rather than storing the raw fragment verbatim.
 *
 * Seeding is intentionally cross-tenant: AI referral data is an
 * aggregate network signal. All brands' observations feed a shared
 * corpus, which is then filtered per vertical+locale for probing.
 */

import { prisma } from "@/lib/prisma";
import { inferQueryFromLandingPage } from "@/lib/services/ai-attribution";
import { getBrandCells } from "./cell";
import type { QuerySource } from "@prisma/client";

// ---------------------------------------------------------------------------
// Query derivation from fragment
// ---------------------------------------------------------------------------

const STOP_WORDS = new Set([
  // articles, prepositions, conjunctions
  "the", "a", "an", "and", "or", "but", "in", "on", "at", "to", "for",
  "of", "with", "by", "from", "into", "than", "then", "about",
  // pronouns
  "is", "are", "was", "were", "be", "been", "has", "have", "had",
  "do", "does", "did", "will", "would", "could", "should", "may",
  "might", "can", "not", "no", "so", "if", "as", "up", "out",
  "our", "your", "their", "its", "this", "that", "these", "those",
  "we", "you", "they", "it", "he", "she", "i", "me", "my", "us",
  "when", "where", "which", "who", "how", "what", "why",
  "all", "also", "just", "more", "most", "other", "such",
  "get", "use", "using", "used", "new", "need", "needs", "make",
  // common marketing/content verbs that appear in AI answer fragments
  "provides", "provide", "offers", "offer", "delivers", "deliver",
  "includes", "include", "helps", "help", "gives", "give", "allows",
  "allow", "enables", "enable", "features", "feature", "serves", "serve",
  "creates", "create", "makes", "builds", "build", "brings", "bring",
  // qualifiers that add noise without meaning
  "same", "days", "area", "near", "patients", "customers", "clients",
  "team", "staff", "experts", "experts", "people", "many", "every",
  "each", "both", "full", "well", "come", "come", "wide", "high",
  "also", "even", "here", "there", "very", "always", "never", "often",
]);

/**
 * Derive a probe query from an AI page-text fragment.
 *
 * The fragment is a snippet of AI-generated content that referenced the
 * brand -- not the user's literal query. We extract meaningful noun tokens
 * and form a short keyword query rather than storing the raw fragment.
 */
export function fragmentToQuery(
  fragment: string,
  landingPage: string,
  brandName: string,
): string {
  const isInferred = fragment.startsWith("[inferred]");

  if (isInferred) {
    const raw = inferQueryFromLandingPage(landingPage)
      .replace(/^\[inferred\]\s*/, "")
      .trim();
    // Discard bare "site visit" fallback and domain-name artifacts
    const base = raw === "site visit" || raw.includes(".") ? "" : raw;
    return base ? `best ${base} near me` : `${brandName} services`;
  }

  // Strip brand name to avoid self-referential queries
  const brandPattern = new RegExp(brandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi");
  const cleaned = fragment
    .replace(brandPattern, "")
    .replace(/["""'']/g, "")
    .replace(/\s+/g, " ")
    .trim();

  // Extract meaningful tokens: >3 chars, not a stop-word, not a bare number
  const tokens = cleaned
    .split(/\W+/)
    .filter((w) => w.length > 3 && !STOP_WORDS.has(w.toLowerCase()) && !/^\d+$/.test(w));

  const keywords = tokens.slice(0, 5).join(" ").toLowerCase();

  if (!keywords) {
    const fallback = inferQueryFromLandingPage(landingPage)
      .replace(/^\[inferred\]\s*/, "")
      .trim();
    return fallback ? `best ${fallback}` : `${brandName} services`;
  }

  return keywords;
}

// ---------------------------------------------------------------------------
// Conversion weight
// ---------------------------------------------------------------------------

// Canonical conversion type names from AiAttribution.conversionType.
// Matches the vocabulary in SITE_CONVERSION_TYPES (site-tag-analytics.ts).
const COMMERCIAL_CONVERSION_TYPES = new Set([
  "appointment_booked",
  "form_submitted",
  "phone_call",
  "email_contact",
  "sms_contact",
  "purchase",
  // Legacy storage names present in older rows
  "booking_confirmed",
  "form_submit",
  "form_submission",
  "phone_number_click",
  "click_to_call",
  "calendly",
  "email_click",
  "sms_click",
]);

/**
 * Map conversion metadata to a weight in [0..1].
 * Higher deal values and Tier-1 conversions get higher weights.
 * Unconverted rows always return 0.
 */
export function deriveConversionWeight(
  converted: boolean,
  dealValue: number | null,
  conversionType: string | null,
): number {
  if (!converted) return 0;

  let base = 0.3;

  if (conversionType && COMMERCIAL_CONVERSION_TYPES.has(conversionType.toLowerCase())) {
    base += 0.3;
  }

  if (dealValue !== null) {
    if (dealValue >= 10000) base += 0.4;
    else if (dealValue >= 1000) base += 0.3;
    else if (dealValue >= 100) base += 0.2;
    else base += 0.1;
  }

  return Math.min(base, 1);
}

// ---------------------------------------------------------------------------
// Category-coverage queries per vertical
// ---------------------------------------------------------------------------

const CATEGORY_QUERIES: Record<string, string[]> = {
  "healthcare": [
    "best primary care doctor near me",
    "how to find a good family physician",
    "primary care vs urgent care when to go",
    "what to look for in a primary care doctor",
    "how to get a same-day doctor appointment",
  ],
  "dental": [
    "best dentist near me",
    "how to find a good dentist",
    "dental implants vs dentures cost",
    "how much does teeth whitening cost",
    "emergency dentist near me open now",
  ],
  "mental-health": [
    "therapist near me accepting new patients",
    "how to find the right therapist",
    "online therapy vs in-person therapy",
    "how much does therapy cost without insurance",
    "anxiety treatment options near me",
  ],
  "veterinary": [
    "best vet near me",
    "how to find a good veterinarian",
    "low cost vet clinic near me",
    "emergency vet near me",
    "how much does a vet visit cost",
  ],
  "b2b-saas": [
    "best project management software for small teams",
    "top CRM tools for small business",
    "best accounting software for startups",
    "affordable marketing automation tools",
    "top HR software for small business",
  ],
  "b2b-services": [
    "best marketing agency for small business",
    "how to choose a b2b marketing agency",
    "top digital marketing agencies",
    "affordable seo services for small business",
    "best ppc management services",
  ],
  "b2b-manufacturing": [
    "contract manufacturing services near me",
    "custom parts manufacturing companies",
    "industrial supply chain solutions",
    "best cnc machining services",
    "quality control consulting manufacturing",
  ],
  "ecommerce": [
    "best online stores for electronics",
    "where to buy affordable home goods online",
    "best ecommerce sites with fast shipping",
    "top fashion brands online",
    "best places to buy refurbished electronics",
  ],
  "local-services": [
    "best plumber near me",
    "top-rated electrician in my area",
    "best hvac company near me",
    "affordable landscaping services near me",
    "best moving companies in my city",
  ],
  "professional-services": [
    "best business consultant near me",
    "top accounting firms for small business",
    "how to find a good financial advisor",
    "best hr consulting firms for small companies",
    "top business attorneys near me",
  ],
  "finance": [
    "best financial advisors near me",
    "top wealth management firms",
    "how to choose a financial planner",
    "best investment advisors for beginners",
    "fee-only financial advisors near me",
  ],
  "legal": [
    "best personal injury attorney near me",
    "how to find a good family law attorney",
    "top employment lawyers near me",
    "affordable estate planning attorney",
    "best real estate lawyers near me",
  ],
  "real-estate": [
    "best real estate agents near me",
    "top real estate agencies in my area",
    "how to find a good realtor",
    "best real estate agents for first-time buyers",
    "top-rated property management companies",
  ],
  "nonprofit": [
    "best charities to donate to",
    "top-rated nonprofits near me",
    "most effective hunger relief organizations",
    "how to evaluate a nonprofit before donating",
    "best local nonprofits to support",
  ],
  "hospitality": [
    "best hotels near me",
    "top-rated restaurants near me",
    "best bed and breakfasts in my area",
    "top event venues near me",
    "best local restaurants for special occasions",
  ],
  "higher-ed": [
    "best colleges for computer science",
    "top online universities for working adults",
    "best community colleges near me",
    "top-rated graduate programs in business",
    "most affordable accredited online degrees",
  ],
  "other": [
    "best local businesses near me",
    "top-rated services in my area",
    "how to find reliable local contractors",
    "best online services for small business",
    "top-rated companies in my industry",
  ],
};

// ---------------------------------------------------------------------------
// Internal seeding implementation
// ---------------------------------------------------------------------------

interface AttributionRow {
  brandId: string;
  queryContext: string | null;
  landingPage: string;
  converted: boolean;
  dealValue: number | null;
  conversionType: string | null;
}

async function upsertCategoryCoverage(vertical: string, locale: string): Promise<void> {
  const queries = CATEGORY_QUERIES[vertical] ?? CATEGORY_QUERIES["other"];
  for (const query of queries) {
    await prisma.queryCorpusEntry.upsert({
      where: { vertical_locale_query: { vertical, locale, query } },
      update: {},
      create: {
        vertical,
        locale,
        query,
        source: "CATEGORY_COVERAGE" as QuerySource,
        conversionWeight: 0,
      },
    });
  }
}

/**
 * Core seeding implementation shared by both public entry points.
 * Derives queries from attributions, upserts FRAGMENT_SEEDED entries,
 * then upserts CATEGORY_COVERAGE entries for the vertical.
 *
 * brandNames: Map<brandId, brandName> for query derivation.
 *             May be empty (unknown brand names degrade gracefully).
 */
async function runSeedForCell(
  vertical: string,
  locale: string,
  brandIds: string[],
  brandNames: Map<string, string>,
): Promise<number> {
  if (brandIds.length === 0) {
    await upsertCategoryCoverage(vertical, locale);
    return 0;
  }

  // Light read: only fields needed for query derivation, no full JSONB blobs
  const attributions = await prisma.aiAttribution.findMany({
    where: { brandId: { in: brandIds } },
    select: {
      brandId: true,
      queryContext: true,
      landingPage: true,
      converted: true,
      dealValue: true,
      conversionType: true,
    },
    orderBy: { id: "desc" },
    take: 5000,
  }) as AttributionRow[];

  // Build best conversionWeight per derived query (O(1) per row via Map)
  const weightMap = new Map<string, number>();

  for (const attr of attributions) {
    const fragment = attr.queryContext ?? inferQueryFromLandingPage(attr.landingPage ?? "");
    const brandName = brandNames.get(attr.brandId) ?? "";
    const query = fragmentToQuery(fragment, attr.landingPage ?? "", brandName).trim();
    if (!query || query.length < 5) continue;

    const weight = deriveConversionWeight(
      attr.converted,
      attr.dealValue !== null && attr.dealValue !== undefined ? Number(attr.dealValue) : null,
      attr.conversionType,
    );

    const prev = weightMap.get(query);
    if (prev === undefined || weight > prev) weightMap.set(query, weight);
  }

  let upserted = 0;
  for (const [query, conversionWeight] of weightMap) {
    await prisma.queryCorpusEntry.upsert({
      where: { vertical_locale_query: { vertical, locale, query } },
      update: { conversionWeight },
      create: {
        vertical,
        locale,
        query,
        source: "FRAGMENT_SEEDED" as QuerySource,
        conversionWeight,
      },
    });
    upserted++;
  }

  await upsertCategoryCoverage(vertical, locale);
  return upserted;
}

// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------

/**
 * Seed (or refresh) the query corpus for one (vertical, locale) cell.
 *
 * Intended for on-demand per-cell calls from Block 3 after a probe run.
 * Queries the Brand table to discover which brands belong to the cell.
 * If you are seeding many cells in a loop, use seedAllCells() instead --
 * it loads brands once and avoids N full Brand table scans.
 */
export async function seedCorpusCell(vertical: string, locale: string): Promise<number> {
  const brands = await prisma.brand.findMany({
    select: { id: true, name: true, industry: true, location: true, locations: true },
  });

  const cellBrands = brands.filter((b) =>
    getBrandCells(b).some((c) => c.vertical === vertical && c.locale === locale),
  );

  const brandIds = cellBrands.map((b) => b.id);
  const brandNames = new Map(cellBrands.map((b) => [b.id, b.name ?? ""]));

  return runSeedForCell(vertical, locale, brandIds, brandNames);
}

/**
 * Discover all active (vertical, locale) cells from existing Brand rows
 * and seed or refresh the corpus for each.
 *
 * Loads Brand rows once and distributes them into cells before seeding,
 * avoiding N full Brand table scans.
 */
export async function seedAllCells(): Promise<{ cells: number; queries: number }> {
  const brands = await prisma.brand.findMany({
    select: { id: true, name: true, industry: true, location: true, locations: true },
  });

  // Map each cell key to its brandIds and a shared name lookup
  const cellBrandIds = new Map<string, { vertical: string; locale: string; ids: string[] }>();
  const brandNames = new Map<string, string>();

  for (const brand of brands) {
    brandNames.set(brand.id, brand.name ?? "");
    for (const cell of getBrandCells(brand)) {
      const key = `${cell.vertical}:${cell.locale}`;
      if (!cellBrandIds.has(key)) {
        cellBrandIds.set(key, { vertical: cell.vertical, locale: cell.locale, ids: [] });
      }
      cellBrandIds.get(key)!.ids.push(brand.id);
    }
  }

  let totalQueries = 0;
  for (const { vertical, locale, ids } of cellBrandIds.values()) {
    totalQueries += await runSeedForCell(vertical, locale, ids, brandNames);
  }

  return { cells: cellBrandIds.size, queries: totalQueries };
}
