/**
 * AI-Powered Keyword Suggestions
 * ───────────────────────────────
 * Combines Site Tag traffic data, GSC query data, and AI analysis to
 * generate categorized keyword suggestions for the Rank Tracker.
 *
 * Categories:
 *   branded        — contains the brand name or variations
 *   nonBranded     — general industry/service keywords
 *   localVariations — geo-modified keywords (city, "near me")
 *   spanishKeywords — Spanish-language keywords (detected from /es/ pages)
 *   strikingDistance — GSC queries at position 4–20 (page 1–2 opportunity)
 */

import { prisma } from "@/lib/prisma";
import { getTopQueries } from "@/lib/services/gsc-sync";
import { callLlm } from "@/lib/ai-avatar/llm-client";
import { getBrandLocations, getBrandServices } from "@/lib/brand-helpers";

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

export interface KeywordSuggestion {
  keyword: string;
  source: "gsc" | "sitetag" | "ai" | "gsc+sitetag";
  impressions: number | null;
  currentPosition: number | null;
  priority: "high" | "medium" | "low" | "track";
  reason: string;
  targetUrl?: string;
  suggestedTags?: string[];
}

export interface KeywordSuggestions {
  branded: KeywordSuggestion[];
  nonBranded: KeywordSuggestion[];
  localVariations: KeywordSuggestion[];
  spanishKeywords: KeywordSuggestion[];
  strikingDistance: KeywordSuggestion[];
}

// ─── Helpers ────────────────────────────────────────────────────────────────

/** Extract a human-readable topic from a URL slug. */
function topicFromUrl(url: string): string {
  try {
    const path = new URL(url.startsWith("http") ? url : `https://x.com${url}`).pathname;
    return path
      .replace(/\/$/, "")
      .split("/")
      .pop()!
      .replace(/\.[^.]+$/, "")
      .replace(/[-_]/g, " ")
      .replace(/\d{4,}/g, "")
      .replace(/\s+/g, " ")
      .trim()
      .toLowerCase();
  } catch {
    return "";
  }
}

/** Build brand-name variations for branded keyword detection. */
function brandVariations(name: string): string[] {
  const lower = name.toLowerCase();
  const words = lower.split(/\s+/).filter((w) => w.length > 1);
  const variations = [lower];
  // Initials (e.g., "Modern Heart And Vascular" → "mhav")
  if (words.length >= 2) {
    variations.push(words.map((w) => w[0]).join(""));
  }
  // First two words (e.g., "modern heart")
  if (words.length >= 2) {
    variations.push(words.slice(0, 2).join(" "));
  }
  // Without common suffixes
  const stripped = lower.replace(/\s+(inc|llc|ltd|co|corp|group|associates|clinic|center|medical|health)\b/gi, "").trim();
  if (stripped !== lower && stripped.length >= 3) variations.push(stripped);
  return [...new Set(variations)].filter((v) => v.length >= 2);
}

function isBranded(keyword: string, variations: string[]): boolean {
  const lower = keyword.toLowerCase();
  return variations.some((v) => lower.includes(v));
}

function isSpanish(keyword: string): boolean {
  return /[áéíóúñ¿¡]/.test(keyword) || /\b(de|del|la|las|los|en|para|con|que|como|por)\b/i.test(keyword);
}

function isLocal(keyword: string): boolean {
  return /\b(near me|near by|nearby|in\s[A-Z]|tx|texas|houston|humble|katy|sugar\s?land|spring|tomball|woodlands|cypress|pearland|pasadena|baytown|beaumont|conroe|league\scity|clear\slake|webster|friendswood|missouri\scity|richmond|rosenberg|galveston|san\santonio|dallas|austin|fort\sworth)\b/i.test(keyword);
}

// ─── Data collection ────────────────────────────────────────────────────────

/**
 * Gather keyword suggestions from all available data sources for a brand.
 * Does NOT require AI if GSC + Site Tag data is sufficient — the AI call
 * is a best-effort enrichment that fails silently.
 */
export async function generateKeywordSuggestions(
  brandId: string,
  limit: number = 100,
  includeAi: boolean = true,
): Promise<KeywordSuggestions> {
  const brand = await prisma.brand.findUnique({
    where: { id: brandId },
    select: { name: true, domain: true, industry: true, location: true, locations: true, serviceType: true, primaryServices: true },
  });
  if (!brand) throw new Error("Brand not found");

  const variations = brandVariations(brand.name);
  const alreadyTracked = new Set(
    (await prisma.trackedKeyword.findMany({
      where: { brandId, isActive: true },
      select: { keyword: true },
    })).map((k) => k.keyword.toLowerCase()),
  );

  const all: KeywordSuggestion[] = [];

  // ── 1. GSC queries ────────────────────────────────────────────────
  try {
    const gscQueries = await getTopQueries(brandId, 200, 28);
    for (const q of gscQueries) {
      if (alreadyTracked.has(q.query.toLowerCase())) continue;
      const priority = q.position <= 3 ? "track" as const
        : q.position <= 10 ? "high" as const
        : q.position <= 20 ? "medium" as const
        : "low" as const;
      const reason = q.position <= 3
        ? `Already top 3 — track to defend position ${q.position}`
        : q.position <= 10
        ? `Page 1 at position ${q.position} — ${q.impressions.toLocaleString()} impressions`
        : q.position <= 20
        ? `Striking distance at position ${q.position} — optimize to reach page 1`
        : `Position ${q.position} — ${q.impressions.toLocaleString()} impressions`;
      all.push({
        keyword: q.query,
        source: "gsc",
        impressions: q.impressions,
        currentPosition: q.position,
        priority,
        reason,
      });
    }
  } catch { /* GSC not connected — skip */ }

  // ── 2. Site Tag: top pages → keyword extraction ───────────────────
  try {
    const siteId = await prisma.trackedSite.findUnique({
      where: { brandId },
      select: { id: true },
    });
    if (siteId) {
      const since = new Date(Date.now() - 30 * 86400000);
      // Top pages by pageview
      const pageEvents = await prisma.trackedEvent.findMany({
        where: { trackedSiteId: siteId.id, eventType: "page_view", timestamp: { gte: since } },
        select: { pageUrl: true },
      });
      const pageCounts = new Map<string, number>();
      for (const e of pageEvents) {
        const url = e.pageUrl;
        pageCounts.set(url, (pageCounts.get(url) ?? 0) + 1);
      }
      const topPages = [...pageCounts.entries()]
        .sort(([, a], [, b]) => b - a)
        .slice(0, 30);

      for (const [url, views] of topPages) {
        const topic = topicFromUrl(url);
        if (topic.length < 3 || alreadyTracked.has(topic)) continue;
        // Check if this topic is already covered by a GSC suggestion
        if (all.some((s) => s.keyword.toLowerCase() === topic)) {
          // Upgrade source to gsc+sitetag
          const existing = all.find((s) => s.keyword.toLowerCase() === topic);
          if (existing) existing.source = "gsc+sitetag";
          continue;
        }
        const isSpanishPage = /\/es\/|\/espanol\//i.test(url);
        all.push({
          keyword: topic,
          source: "sitetag",
          impressions: null,
          currentPosition: null,
          priority: views >= 100 ? "high" : "medium",
          reason: `${views.toLocaleString()} pageviews in last 30 days`,
          targetUrl: new URL(url).pathname,
          suggestedTags: isSpanishPage ? ["spanish"] : undefined,
        });
      }
    }
  } catch { /* Site Tag not set up — skip */ }

  // ── 3. Top converting pages → keywords ────────────────────────────
  try {
    const since = new Date(Date.now() - 30 * 86400000);
    // Include every stored conversionType that resolves to a Tier-1
    // canonical we care about for keyword-intent ranking — form +
    // phone. Previously the filter missed "phone_number_click" and
    // "click_to_call" legacy phone storage types, so pages whose
    // calls were captured via the form-phone.js text-pattern
    // detector never surfaced in keyword suggestions.
    // intentionally unfiltered: internal service; uses raw counts intentionally
    const conversions = await prisma.siteConversion.findMany({
      where: {
        brandId,
        conversionType: { in: [
          "form_submit", "form_submitted", "form_submission",
          "phone_call", "phone_number_click", "click_to_call",
        ] },
        timestamp: { gte: since },
      },
      select: { pageUrl: true },
    });
    const convPageCounts = new Map<string, number>();
    for (const c of conversions) {
      convPageCounts.set(c.pageUrl, (convPageCounts.get(c.pageUrl) ?? 0) + 1);
    }
    for (const [url, convCount] of [...convPageCounts.entries()].sort(([, a], [, b]) => b - a).slice(0, 15)) {
      const topic = topicFromUrl(url);
      if (topic.length < 3 || alreadyTracked.has(topic)) continue;
      if (all.some((s) => s.keyword.toLowerCase() === topic)) {
        const existing = all.find((s) => s.keyword.toLowerCase() === topic);
        if (existing) {
          existing.priority = "high";
          existing.reason = `${convCount} conversions + ${existing.reason}`;
        }
        continue;
      }
      all.push({
        keyword: topic,
        source: "sitetag",
        impressions: null,
        currentPosition: null,
        priority: "high",
        reason: `Top converting page — ${convCount} conversions in 30 days`,
        targetUrl: new URL(url).pathname,
      });
    }
  } catch { /* skip */ }

  // ── 4. AI enrichment — local variations + long-tail + gaps ────────
  // Skipped when includeAi=false so the fast path (GSC + Site Tag only)
  // returns instantly while the caller fetches AI suggestions separately.
  if (includeAi) try {
    const topKeywords = all.slice(0, 20).map((s) => s.keyword).join(", ");
    const prompt = `You are an SEO keyword strategist. Given this brand and their top keywords, suggest additional keywords they should track.

Brand: ${brand.name}
Domain: ${brand.domain}
Industry: ${brand.industry || "unknown"}
Service: ${getBrandServices(brand).combined[0] ?? "unknown"}
Location: ${getBrandLocations(brand)[0] ?? "unknown"}
Top keywords: ${topKeywords || "none available"}

Return a JSON array of objects: [{"keyword":"...","reason":"...","type":"local|longtail|gap|spanish"}]
Rules:
- Suggest 10-20 keywords max
- Include 3-5 local/geo-modified variations if location is known
- Include 2-3 Spanish keywords if the brand has Spanish content
- Include long-tail question keywords ("what causes...", "how to treat...")
- Only return the JSON array, no other text`;

    const result = await callLlm(
      [
        { role: "system", content: "Respond only with a valid JSON array." },
        { role: "user", content: prompt },
      ],
      () => "[]",
      { maxTokens: 1500 },
    );

    let aiSuggestions: Array<{ keyword: string; reason: string; type: string }> = [];
    try {
      const text = result.reply.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "");
      const f = text.indexOf("["), l = text.lastIndexOf("]");
      if (f >= 0 && l > f) aiSuggestions = JSON.parse(text.slice(f, l + 1));
    } catch { /* parse failed — skip AI suggestions */ }

    for (const ai of aiSuggestions) {
      if (!ai.keyword || alreadyTracked.has(ai.keyword.toLowerCase())) continue;
      if (all.some((s) => s.keyword.toLowerCase() === ai.keyword.toLowerCase())) continue;
      all.push({
        keyword: ai.keyword,
        source: "ai",
        impressions: null,
        currentPosition: null,
        priority: ai.type === "gap" ? "high" : "medium",
        reason: ai.reason || "AI-suggested keyword",
        suggestedTags: ai.type === "spanish" ? ["spanish"] : ai.type === "local" ? ["local"] : undefined,
      });
    }
  } catch { /* AI not available — skip */ }

  // ── 5. Categorize ─────────────────────────────────────────────────
  const result: KeywordSuggestions = {
    branded: [],
    nonBranded: [],
    localVariations: [],
    spanishKeywords: [],
    strikingDistance: [],
  };

  for (const s of all) {
    // Auto-assign tags
    if (!s.suggestedTags) s.suggestedTags = [];
    if (isBranded(s.keyword, variations)) {
      s.suggestedTags.push("branded");
      result.branded.push(s);
    } else if (isSpanish(s.keyword) || s.suggestedTags.includes("spanish")) {
      if (!s.suggestedTags.includes("spanish")) s.suggestedTags.push("spanish");
      result.spanishKeywords.push(s);
    } else if (s.currentPosition && s.currentPosition >= 4 && s.currentPosition <= 20) {
      s.suggestedTags.push("striking-distance");
      result.strikingDistance.push(s);
    } else if (isLocal(s.keyword) || s.suggestedTags.includes("local")) {
      if (!s.suggestedTags.includes("local")) s.suggestedTags.push("local");
      result.localVariations.push(s);
    } else {
      result.nonBranded.push(s);
    }
  }

  // Sort each category by priority then impressions
  const priorityOrder = { track: 0, high: 1, medium: 2, low: 3 };
  const sorter = (a: KeywordSuggestion, b: KeywordSuggestion) =>
    priorityOrder[a.priority] - priorityOrder[b.priority] ||
    (b.impressions ?? 0) - (a.impressions ?? 0);

  result.branded.sort(sorter);
  result.nonBranded.sort(sorter);
  result.localVariations.sort(sorter);
  result.spanishKeywords.sort(sorter);
  result.strikingDistance.sort(sorter).splice(limit);

  return result;
}
