/**
 * AI Strategy Recommendations Engine
 * ───────────────────────────────────
 * Analyses LLMO data across all connected features and generates
 * prioritised, actionable recommendations. Runs daily via cron
 * and on-demand from the AI Attribution tab.
 *
 * Every generator is idempotent — running twice produces the same
 * set of recommendations, not duplicates. Existing pending recs
 * with matching (brandId, type, title) are skipped.
 */

import { prisma } from "@/lib/prisma";
import { calculateContentResonance, type ContentResonance } from "@/lib/services/ai-content-resonance";

// ─── Priority scoring ──────────────────────────────────────────────────────

function calculatePriority(rec: {
  estimatedRevenue: number;
  effort: "low" | "medium" | "high";
  dataConfidence: number;
}): "critical" | "high" | "medium" | "low" {
  const effortMultiplier = rec.effort === "low" ? 3 : rec.effort === "medium" ? 2 : 1;
  const score = (rec.estimatedRevenue / 1000) * rec.dataConfidence * effortMultiplier;
  if (score >= 15) return "critical";
  if (score >= 8) return "high";
  if (score >= 3) return "medium";
  return "low";
}

// ─── Dedup helper ──────────────────────────────────────────────────────────

async function upsertRec(
  brandId: string,
  type: string,
  title: string,
  data: {
    insight: string;
    action: string;
    impact?: string;
    priority: string;
    dataPoints: Record<string, unknown>;
    relatedPages?: string[];
    relatedKeywords?: string[];
  },
): Promise<boolean> {
  const existing = await prisma.aiRecommendation.findFirst({
    where: { brandId, type, title, status: { in: ["pending", "in_progress"] } },
    select: { id: true },
  });
  if (existing) return false;

  await prisma.aiRecommendation.create({
    data: {
      brandId,
      type,
      title,
      priority: data.priority,
      insight: data.insight,
      action: data.action,
      impact: data.impact,
      dataPoints: JSON.parse(JSON.stringify(data.dataPoints)),
      relatedPages: JSON.parse(JSON.stringify(data.relatedPages ?? [])),
      relatedKeywords: JSON.parse(JSON.stringify(data.relatedKeywords ?? [])),
    },
  });
  return true;
}

// ─── Format Replication ────────────────────────────────────────────────────

async function generateFormatReplicationRecs(brandId: string): Promise<number> {
  const d30 = new Date(Date.now() - 30 * 86_400_000);
  const resonance = await calculateContentResonance(brandId, d30);
  if (resonance.length < 2) return 0;

  const topPages = resonance.filter((r) => r.aiLift >= 2 && r.aiConvRate >= 10);
  if (topPages.length === 0) return 0;

  const lowPages = resonance.filter((r) => r.aiLift < 1.2 && r.aiVisits >= 3 && r.aiConvRate < 5);
  if (lowPages.length === 0) return 0;

  const avgDealValue = await getAvgDealValue(brandId, d30);
  const best = topPages[0];
  const estimatedRevenue = lowPages.length * (best.aiVisits / 2) * (best.aiConvRate / 100) * avgDealValue;

  const priority = calculatePriority({
    estimatedRevenue,
    effort: "medium",
    dataConfidence: Math.min(best.aiVisits / 20, 1),
  });

  const created = await upsertRec(brandId, "format_replication", `Replicate ${best.pageUrl} format across ${lowPages.length} pages`, {
    insight: `Your ${best.pageUrl} page converts AI visitors at ${best.aiConvRate}% (${best.aiLift}x organic). ${lowPages.length} other pages have AI traffic but convert below 5%.`,
    action: `Rewrite the ${lowPages.length} underperforming pages to match the content format of ${best.pageUrl} — FAQ structure, heading patterns, and schema markup.`,
    impact: estimatedRevenue > 0 ? `Estimated ~$${Math.round(estimatedRevenue).toLocaleString()}/month in additional AI-attributed revenue` : undefined,
    priority,
    dataPoints: {
      topPage: best.pageUrl,
      topPageAiConvRate: best.aiConvRate,
      topPageAiLift: best.aiLift,
      underperformingPages: lowPages.map((p) => p.pageUrl),
      avgDealValue,
      estimatedRevenue: Math.round(estimatedRevenue),
    },
    relatedPages: [best.pageUrl, ...lowPages.map((p) => p.pageUrl)],
  });

  return created ? 1 : 0;
}

// ─── Citation Gap Closure ──────────────────────────────────────────────────

async function generateCitationGapRecs(brandId: string): Promise<number> {
  const d30 = new Date(Date.now() - 30 * 86_400_000);
  let count = 0;

  const citations = await prisma.aiCitation.findMany({
    where: { brandId, checkedAt: { gte: d30 } },
    orderBy: { checkedAt: "desc" },
  });

  const byKeyword = new Map<string, typeof citations>();
  for (const c of citations) {
    const list = byKeyword.get(c.keyword) ?? [];
    list.push(c);
    byKeyword.set(c.keyword, list);
  }

  for (const [keyword, kwCitations] of byKeyword) {
    const brandCited = kwCitations.some((c) => c.brandCited);
    if (brandCited) continue;

    const competitors = kwCitations
      .filter((c) => c.competitorsCited)
      .flatMap((c) => {
        const comp = c.competitorsCited as Array<{ domain?: string; position?: number }>;
        return Array.isArray(comp) ? comp : [];
      });

    if (competitors.length === 0) continue;

    const topCompetitor = competitors[0]?.domain ?? "a competitor";

    const created = await upsertRec(brandId, "citation_gap", `Not cited for "${keyword}" — competitor is`, {
      insight: `You're not cited by AI for "${keyword}" but ${topCompetitor} is. This is a gap in your AI visibility.`,
      action: `Create or optimize content targeting "${keyword}" with structured FAQ format, comprehensive coverage (2,000+ words), and schema markup.`,
      impact: "Closing citation gaps typically increases AI referral traffic by 20-40% for the targeted keyword.",
      priority: "high",
      dataPoints: {
        keyword,
        competitorsCited: competitors.slice(0, 5),
        brandCited: false,
        citationTypes: [...new Set(kwCitations.map((c) => c.citationType))],
      },
      relatedKeywords: [keyword],
    });

    if (created) count++;
    if (count >= 5) break;
  }

  return count;
}

// ─── Conversion Optimization ───────────────────────────────────────────────

async function generateConversionOptRecs(brandId: string): Promise<number> {
  const d30 = new Date(Date.now() - 30 * 86_400_000);
  let count = 0;

  const pageStats = await prisma.aiAttribution.groupBy({
    by: ["landingPage"],
    where: { brandId, timestamp: { gte: d30 } },
    _count: true,
    orderBy: { _count: { landingPage: "desc" } },
    take: 20,
  }).catch(() => []);

  const convByPage = await prisma.aiAttribution.groupBy({
    by: ["landingPage"],
    where: { brandId, timestamp: { gte: d30 }, converted: true },
    _count: true,
  }).catch(() => []);
  const convMap = new Map(convByPage.map((r) => [r.landingPage, typeof r._count === "number" ? r._count : 0]));

  const pages = pageStats.map((p) => {
    const visits = typeof p._count === "number" ? p._count : 0;
    const conversions = convMap.get(p.landingPage) ?? 0;
    const rate = visits > 0 ? Math.round((conversions / visits) * 1000) / 10 : 0;
    return { page: p.landingPage, visits, conversions, rate };
  });

  const avgRate = pages.reduce((sum, p) => sum + p.rate, 0) / (pages.length || 1);
  const highTrafficLowConv = pages.filter((p) => p.visits >= 5 && p.rate < avgRate * 0.5);
  const highPerformers = pages.filter((p) => p.visits >= 3 && p.rate > avgRate * 1.5);

  for (const page of highTrafficLowConv.slice(0, 3)) {
    const bestComparison = highPerformers[0];
    const created = await upsertRec(brandId, "conversion_optimization", `Low AI conversion on ${page.page}`, {
      insight: `${page.page} gets ${page.visits} AI referrals/month but only ${page.conversions} conversions (${page.rate}%).${bestComparison ? ` Your ${bestComparison.page} converts at ${bestComparison.rate}%.` : ""}`,
      action: "Add a sticky phone CTA, inline appointment form, and FAQ section to improve AI visitor conversion.",
      impact: `Improving to the site average (${Math.round(avgRate)}%) could add ~${Math.round(page.visits * (avgRate / 100))} conversions/month.`,
      priority: calculatePriority({
        estimatedRevenue: page.visits * (avgRate / 100) * (await getAvgDealValue(brandId, d30)),
        effort: "low",
        dataConfidence: Math.min(page.visits / 15, 1),
      }),
      dataPoints: {
        page: page.page,
        aiVisits: page.visits,
        conversions: page.conversions,
        convRate: page.rate,
        siteAvgRate: avgRate,
        comparisonPage: bestComparison?.page,
      },
      relatedPages: [page.page],
    });
    if (created) count++;
  }

  return count;
}

// ─── Content Expansion ─────────────────────────────────────────────────────

async function generateContentExpansionRecs(brandId: string): Promise<number> {
  const d30 = new Date(Date.now() - 30 * 86_400_000);
  let count = 0;

  const queries = await prisma.aiAttribution.findMany({
    where: { brandId, timestamp: { gte: d30 }, queryContext: { not: null } },
    select: { queryContext: true, landingPage: true },
  });

  const queryGroups = new Map<string, number>();
  for (const q of queries) {
    if (!q.queryContext) continue;
    const normalized = q.queryContext.replace(/^\[inferred\]\s*/i, "").toLowerCase().trim();
    if (normalized.length < 5) continue;
    queryGroups.set(normalized, (queryGroups.get(normalized) ?? 0) + 1);
  }

  const sorted = [...queryGroups.entries()].sort((a, b) => b[1] - a[1]);

  for (const [query, queryCount] of sorted.slice(0, 5)) {
    if (queryCount < 3) continue;

    const created = await upsertRec(brandId, "content_expansion", `Create content for "${query}"`, {
      insight: `AI users are asking about "${query}" (${queryCount} queries this month) but there may not be a dedicated page for this topic.`,
      action: `Create a comprehensive page targeting "${query}" with FAQ structure, schema markup, and strong CTAs.`,
      impact: `Capturing this query could generate ~${queryCount * 2} additional AI referrals/month based on current patterns.`,
      priority: calculatePriority({
        estimatedRevenue: queryCount * 2 * (await getAvgDealValue(brandId, d30)) * 0.15,
        effort: "medium",
        dataConfidence: Math.min(queryCount / 10, 1),
      }),
      dataPoints: { query, queryCount, period: "30d" },
      relatedKeywords: [query],
    });
    if (created) count++;
  }

  return count;
}

// ─── Schema Fix Recommendations ────────────────────────────────────────────

async function generateSchemaFixRecs(brandId: string): Promise<number> {
  const d30 = new Date(Date.now() - 30 * 86_400_000);

  const citations = await prisma.aiCitation.findMany({
    where: { brandId, brandCited: true, checkedAt: { gte: d30 } },
    select: { citedUrl: true },
  });
  const citedUrls = new Set(citations.map((c) => c.citedUrl).filter(Boolean));
  if (citedUrls.size === 0) return 0;

  const schemaMarkups = await prisma.schemaMarkup.findMany({
    where: { brandId },
    select: { pageUrl: true },
  }).catch(() => []);
  const pagesWithSchema = new Set(schemaMarkups.map((s) => s.pageUrl));

  const topPages = await prisma.aiAttribution.groupBy({
    by: ["landingPage"],
    where: { brandId, timestamp: { gte: d30 } },
    _count: true,
    orderBy: { _count: { landingPage: "desc" } },
    take: 30,
  }).catch(() => []);

  const pagesWithoutSchema = topPages
    .map((p) => p.landingPage)
    .filter((page) => !pagesWithSchema.has(page));

  if (pagesWithoutSchema.length === 0) return 0;

  const created = await upsertRec(brandId, "schema_fix", `${pagesWithoutSchema.length} AI landing pages missing schema markup`, {
    insight: `${pagesWithoutSchema.length} of your top AI landing pages are missing structured data. Pages with schema markup are cited by AI more frequently.`,
    action: `Add appropriate schema markup (FAQPage, MedicalProcedure, LocalBusiness, etc.) to: ${pagesWithoutSchema.slice(0, 5).join(", ")}`,
    impact: "Adding schema to these pages is a high-impact quick win for improving AI citation rates.",
    priority: calculatePriority({
      estimatedRevenue: pagesWithoutSchema.length * 500,
      effort: "low",
      dataConfidence: 0.8,
    }),
    dataPoints: {
      pagesWithoutSchema: pagesWithoutSchema.slice(0, 10),
      totalMissing: pagesWithoutSchema.length,
      pagesWithSchemaCount: pagesWithSchema.size,
      citedPageCount: citedUrls.size,
    },
    relatedPages: pagesWithoutSchema.slice(0, 10),
  });

  return created ? 1 : 0;
}

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

async function getAvgDealValue(brandId: string, since: Date): Promise<number> {
  const result = await prisma.aiAttribution.aggregate({
    where: { brandId, timestamp: { gte: since }, dealValue: { gt: 0 } },
    _avg: { dealValue: true },
  }).catch(() => ({ _avg: { dealValue: null } }));
  return result._avg.dealValue ?? 500;
}

// ─── Main engine ───────────────────────────────────────────────────────────

export async function generateRecommendations(brandId: string): Promise<{
  generated: number;
  byType: Record<string, number>;
}> {
  const d30 = new Date(Date.now() - 30 * 86_400_000);

  const totalAi = await prisma.aiAttribution.count({
    where: { brandId, timestamp: { gte: d30 } },
  }).catch(() => 0);

  // Run generators that DON'T require AI attribution data regardless
  // of how much AI traffic exists. Citation gap + schema fix rely on
  // AiCitation + SchemaMarkup data (populated by crons independent of
  // AI referral volume). Only the conversion-optimisation and format-
  // replication generators need real AI traffic to be useful.
  const generators: Array<() => Promise<number>> = [
    () => generateCitationGapRecs(brandId),
    () => generateSchemaFixRecs(brandId),
    () => generateContentExpansionRecs(brandId),
  ];

  if (totalAi >= 3) {
    generators.push(
      () => generateFormatReplicationRecs(brandId),
      () => generateConversionOptRecs(brandId),
    );
  }

  const labels = ["citation_gap", "schema_fix", "content_expansion",
    ...(totalAi >= 3 ? ["format_replication", "conversion_optimization"] : [])];

  const results = await Promise.allSettled(generators.map((fn) => fn()));

  const byType: Record<string, number> = {};
  let generated = 0;
  for (let i = 0; i < results.length; i++) {
    const result = results[i];
    const count = result.status === "fulfilled" ? result.value : 0;
    byType[labels[i]] = count;
    generated += count;
  }

  return { generated, byType };
}

export async function getRecommendations(
  brandId: string,
  opts?: { status?: string; type?: string; limit?: number },
): Promise<Array<{
  id: string;
  type: string;
  priority: string;
  status: string;
  title: string;
  insight: string;
  action: string;
  impact: string | null;
  relatedPages: unknown;
  relatedKeywords: unknown;
  createdAt: Date;
}>> {
  const where: Record<string, unknown> = { brandId };
  if (opts?.status) where.status = opts.status;
  if (opts?.type) where.type = opts.type;

  return prisma.aiRecommendation.findMany({
    where,
    orderBy: [
      { priority: "asc" },
      { createdAt: "desc" },
    ],
    take: opts?.limit ?? 50,
    select: {
      id: true,
      type: true,
      priority: true,
      status: true,
      title: true,
      insight: true,
      action: true,
      impact: true,
      relatedPages: true,
      relatedKeywords: true,
      createdAt: true,
    },
  });
}

export async function updateRecommendationStatus(
  id: string,
  status: "in_progress" | "completed" | "dismissed",
): Promise<void> {
  const data: Record<string, unknown> = { status };
  if (status === "completed") data.completedAt = new Date();
  if (status === "dismissed") data.dismissedAt = new Date();
  await prisma.aiRecommendation.update({ where: { id }, data });
}

export async function getRecommendationCounts(brandId: string): Promise<{
  total: number;
  critical: number;
  pending: number;
}> {
  const [total, critical, pending] = await Promise.all([
    prisma.aiRecommendation.count({ where: { brandId, status: { in: ["pending", "in_progress"] } } }).catch(() => 0),
    prisma.aiRecommendation.count({ where: { brandId, priority: "critical", status: "pending" } }).catch(() => 0),
    prisma.aiRecommendation.count({ where: { brandId, status: "pending" } }).catch(() => 0),
  ]);
  return { total, critical, pending };
}
