/**
 * Email Report Generator — assembles a branded intelligence email from
 * all platform data sources + optional Claude AI enrichment.
 */

import { prisma } from "@/lib/prisma";
import { callLlm } from "@/lib/ai-avatar/llm-client";
import { buildReportEmail, type EmailReportData } from "./email-template";
import { getPageViewCount, getSessionCount } from "./platform-metrics";
import { sendEmail, computeNextSendAt, type SendResult } from "./email-sender";

const APP_URL = process.env.NEXT_PUBLIC_APP_URL || "https://app.meseo.io";

function daysForFrequency(f: string): number {
  if (f === "daily") return 1;
  if (f === "weekly") return 7;
  if (f === "biweekly") return 14;
  return 30;
}

function formatPeriod(days: number): string {
  const end = new Date();
  const start = new Date(end.getTime() - days * 86400000);
  const fmt = (d: Date) => d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
  return `${fmt(start)} – ${fmt(end)}`;
}

interface AiContent { summary?: string; wins?: string[]; concerns?: string[]; recommendation?: string }

async function runAiAnalysis(data: EmailReportData): Promise<AiContent> {
  const snapshot = [
    data.gsc && `GSC: ${data.gsc.clicks} clicks (${data.gsc.clicksDelta?.toFixed(1) ?? "?"}%), ${data.gsc.impressions} impressions, CTR ${data.gsc.ctr.toFixed(2)}%, avg pos ${data.gsc.position.toFixed(1)}`,
    data.ga4 && `GA4: ${data.ga4.sessions} sessions (${data.ga4.sessionsDelta?.toFixed(1) ?? "?"}%), ${data.ga4.conversions} conversions`,
    data.siteTag && `Site Tag: ${data.siteTag.sessions} sessions, ${data.siteTag.pageViews} page views, ${data.siteTag.formStarts} form starts, ${data.siteTag.outboundClicks} outbound clicks${data.siteTag.conversionRate != null ? `, ${data.siteTag.conversionRate.toFixed(2)}% conv rate` : ""}`,
    data.auditHealth != null && `Crawl health: ${data.auditHealth}/100, ${data.newIssues ?? 0} new issues`,
    data.rankChanges?.length && `Rank changes: ${data.rankChanges.map((r) => `"${r.keyword}" ${r.from ?? "—"}→${r.to ?? "—"}`).join(", ")}`,
    data.conversions && `Conversions: ${data.conversions.total} total`,
  ].filter(Boolean).join("\n");

  const result = await callLlm(
    [
      { role: "system", content: "You are an SEO strategist writing a brief email report. Respond ONLY with JSON: {\"summary\":\"...\",\"wins\":[\"...\"],\"concerns\":[\"...\"],\"recommendation\":\"...\"}. Be specific with numbers." },
      { role: "user", content: `Brand: ${data.brandName} (${data.brandDomain})\nPeriod: ${data.period}\n\n${snapshot}\n\nProvide: 1) A 2-3 sentence executive summary. 2) Top 3 wins. 3) Top 3 concerns. 4) One key recommendation.` },
    ],
    () => JSON.stringify({ summary: "Report generated. Configure ANTHROPIC_API_KEY for AI analysis.", wins: [], concerns: [], recommendation: "" }),
    { maxTokens: 1500 },
  );

  try {
    let s = result.reply.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 JSON.parse(s) as AiContent;
  } catch { return { summary: result.reply.slice(0, 500) }; }
}

async function runMarketInsights(brandName: string, industry: string | null, frequency: string): Promise<string> {
  const result = await callLlm(
    [
      { role: "system", content: "You are a market analyst. Be concise (3-4 sentences). No JSON, just plain text." },
      { role: "user", content: `Given the brand "${brandName}" in the ${industry || "general"} industry, what market trends, seasonal patterns, or external factors should they be aware of this ${frequency} period?` },
    ],
    () => "Market insights require an AI API key.",
    { maxTokens: 500 },
  );
  return result.reply;
}

/**
 * Generate report data, build HTML, send email, and record the result.
 */
export async function generateAndSendReport(scheduleId: string): Promise<{ success: boolean; error?: string }> {
  const schedule = await prisma.emailReportSchedule.findUnique({
    where: { id: scheduleId },
    include: { brand: { include: { profile: true } } },
  });
  if (!schedule) return { success: false, error: "Schedule not found" };

  const brand = schedule.brand;
  const days = daysForFrequency(schedule.frequency);
  const since = new Date(Date.now() - days * 86400000);
  // Which report sections did the user opt into? Falls back to the
  // legacy "include everything" behaviour when a stored schedule
  // has an empty sections array (pre-migration data).
  const scheduleSections = new Set<string>(schedule.sections.length > 0 ? schedule.sections : ["gsc", "ga4", "site_tag", "audit", "rank", "conversions", "lifecycle", "ux"]);

  // Resolve the brand's TrackedSite once up front — all Site Tag
  // aggregations below key off its id. Null when the brand hasn't
  // installed the tag; the Site Tag section gracefully no-ops in
  // that case.
  const trackedSite = scheduleSections.has("site_tag")
    ? await prisma.trackedSite.findUnique({ where: { brandId: brand.id }, select: { id: true } }).catch(() => null)
    : null;

  // ── Pull data sources in parallel ──────────────────────────────────
  // Site Tag aggregations (pageViews / sessions / formStarts /
  // outboundClicks / topPages) only run when the schedule includes
  // the site_tag section AND a TrackedSite exists. Keeps cron cost
  // down for brands who haven't installed the tag or who explicitly
  // excluded it from the report.
  const siteTagDisabled = !trackedSite;
  const [
    gscAgg, ga4Agg, audit, rankKws, newPages, dead404, conversions, uxEvents,
    siteTagPageViews, siteTagSessions, siteTagFormStarts, siteTagOutbound, siteTagTopPages,
  ] = await Promise.all([
    prisma.gscPage.aggregate({ where: { brandId: brand.id, date: { gte: since } }, _sum: { clicks: true, impressions: true }, _avg: { ctr: true, position: true } }),
    prisma.metricSnapshot.findFirst({ where: { brandId: brand.id, source: "ga4", date: { gte: since } }, orderBy: { date: "desc" } }),
    prisma.technicalAudit.findFirst({ where: { brandId: brand.id }, orderBy: { createdAt: "desc" }, select: { crawlHealth: true, createdAt: true } }),
    prisma.trackedKeyword.findMany({ where: { brandId: brand.id, isActive: true }, include: { rankings: { orderBy: { checkedAt: "desc" }, take: 2 } }, take: 50 }),
    prisma.pageRecord.findMany({ where: { brandId: brand.id, firstSeenAt: { gte: since } }, select: { url: true }, take: 10 }),
    prisma.pageRecord.findMany({ where: { brandId: brand.id, currentStatus: 404 }, select: { url: true }, take: 10 }),
    // intentionally unfiltered: internal service; uses raw counts intentionally
    prisma.siteConversion.findMany({ where: { brandId: brand.id, timestamp: { gte: since } }, select: { lastTouchChannel: true } }),
    prisma.siteEvent.findMany({ where: { brandId: brand.id, eventType: { in: ["rage_click", "js_error"] }, timestamp: { gte: since } }, select: { eventType: true } }),
    // Site Tag — page views + sessions via shared helpers so the
    // weekly email cites the same numbers the dashboard shows.
    siteTagDisabled ? Promise.resolve(0) : getPageViewCount(brand.id, since, { trackedSiteId: trackedSite!.id }),
    siteTagDisabled ? Promise.resolve(0) : getSessionCount(brand.id, since, { trackedSiteId: trackedSite!.id }),
    // Site Tag — form_start count
    siteTagDisabled ? Promise.resolve(0) : prisma.trackedEvent.count({ where: { trackedSiteId: trackedSite!.id, eventType: "form_start", timestamp: { gte: since } } }),
    // Site Tag — outbound_click count (SiteEvent, not TrackedEvent:
    // outbound clicks are stored server-side via the dedicated
    // /api/collect event type that lands in SiteEvent).
    siteTagDisabled ? Promise.resolve(0) : prisma.siteEvent.count({ where: { brandId: brand.id, eventType: "outbound_click", timestamp: { gte: since } } }),
    // Site Tag — top pages by page_view count
    siteTagDisabled ? Promise.resolve([]) : prisma.trackedEvent.groupBy({
      by: ["pageUrl"],
      where: { trackedSiteId: trackedSite!.id, eventType: "page_view", timestamp: { gte: since } },
      _count: true,
      orderBy: { _count: { pageUrl: "desc" } },
      take: 5,
    }).catch(() => [] as Array<{ pageUrl: string; _count: number }>),
  ]);

  // ── Assemble data ──
  const ga4Data = ga4Agg ? { sessions: ga4Agg.sessions, conversions: ga4Agg.conversions, newUsers: ga4Agg.users } : null;
  const convByChannel = new Map<string, number>();
  for (const c of conversions) { const ch = c.lastTouchChannel || "direct"; convByChannel.set(ch, (convByChannel.get(ch) || 0) + 1); }

  const rankChanges = rankKws
    .filter((kw) => kw.rankings.length >= 2 && (kw.rankings[0].position !== kw.rankings[1].position))
    .map((kw) => ({ keyword: kw.keyword, from: kw.rankings[1]?.position ?? null, to: kw.rankings[0]?.position ?? null }))
    .slice(0, 10);

  const reportData: EmailReportData = {
    brandName: brand.name,
    brandDomain: brand.domain,
    brandLogoUrl: brand.profile?.logoUrl,
    period: formatPeriod(days),
    frequency: schedule.frequency,
    appUrl: `${APP_URL}/dashboard`,
    gsc: gscAgg._sum.clicks != null ? {
      clicks: gscAgg._sum.clicks ?? 0, impressions: gscAgg._sum.impressions ?? 0,
      ctr: (gscAgg._avg.ctr ?? 0) * 100, position: gscAgg._avg.position ?? 0,
    } : undefined,
    ga4: ga4Data ? { sessions: ga4Data.sessions, conversions: ga4Data.conversions, newUsers: ga4Data.newUsers } : undefined,
    auditHealth: audit?.crawlHealth ?? undefined,
    rankChanges: rankChanges.length > 0 ? rankChanges : undefined,
    newPages: newPages.length > 0 ? newPages.map((p) => p.url) : undefined,
    pages404: dead404.length > 0 ? dead404.map((p) => p.url) : undefined,
    conversions: conversions.length > 0 ? { total: conversions.length, byChannel: Array.from(convByChannel.entries()).map(([channel, count]) => ({ channel, count })).sort((a, b) => b.count - a.count) } : undefined,
    uxSignals: { rageClicks: uxEvents.filter((e) => e.eventType === "rage_click").length, jsErrors: uxEvents.filter((e) => e.eventType === "js_error").length },
    // Site Tag first-party analytics — only populated when the
    // schedule opts in AND the brand has a TrackedSite installed AND
    // we actually saw session activity in the period. Conversion rate
    // uses the SiteConversion count (same signal the dashboard shows)
    // divided by tag-reported sessions so the number matches what
    // users see in Site Tag Analytics Overview.
    siteTag: scheduleSections.has("site_tag") && !siteTagDisabled && siteTagSessions > 0 ? {
      pageViews: siteTagPageViews,
      sessions: siteTagSessions,
      formStarts: siteTagFormStarts,
      outboundClicks: siteTagOutbound,
      conversionRate: siteTagSessions > 0 ? (conversions.length / siteTagSessions) * 100 : null,
      topPages: (siteTagTopPages as Array<{ pageUrl: string; _count: number }>).map((r) => ({ url: r.pageUrl, views: r._count })),
    } : undefined,
  };

  // ── AI Performance data ──
  try {
    const aiReferrals = await prisma.aiAttribution.count({ where: { brandId: brand.id, timestamp: { gte: since } } });
    const aiConversions = await prisma.aiAttribution.count({ where: { brandId: brand.id, timestamp: { gte: since }, converted: true } });
    const aiCitations = await prisma.aiCitation.count({ where: { brandId: brand.id, brandCited: true, checkedAt: { gte: since } } });
    const topAiPage = await prisma.aiAttribution.groupBy({
      by: ["landingPage"], where: { brandId: brand.id, timestamp: { gte: since } },
      _count: true, orderBy: { _count: { landingPage: "desc" } }, take: 1,
    }).catch(() => []);

    if (aiReferrals > 0 || aiCitations > 0) {
      reportData.aiPerformance = {
        referrals: aiReferrals,
        conversions: aiConversions,
        newCitations: aiCitations,
        topPage: topAiPage[0]?.landingPage ?? null,
      };
    }
  } catch { /* ignore — AI tables may not exist yet */ }

  // ── AI enrichment ──
  if (schedule.includeAiAnalysis) {
    const ai = await runAiAnalysis(reportData);
    reportData.aiSummary = ai.summary;
    reportData.aiWins = ai.wins;
    reportData.aiConcerns = ai.concerns;
    reportData.aiRecommendation = ai.recommendation;
  }
  if (schedule.includeMarketInsights) {
    reportData.marketInsights = await runMarketInsights(brand.name, brand.industry, schedule.frequency);
  }
  if (schedule.includeCompetitorScan) {
    // Stub — would integrate with competitor monitoring when richer data exists.
    reportData.competitorActivity = undefined;
  }

  // ── Build + send ──
  const html = buildReportEmail(reportData);
  const subject = `${brand.name} ${schedule.frequency.charAt(0).toUpperCase() + schedule.frequency.slice(1)} Report — ${reportData.period}`;

  let result: SendResult;
  try {
    result = await sendEmail(schedule.recipients, subject, html);
  } catch (err) {
    result = { success: false, error: err instanceof Error ? err.message : "Send failed" };
  }

  // ── Record + update schedule ──
  await prisma.sentEmailReport.create({
    data: {
      scheduleId: schedule.id,
      brandId: brand.id,
      recipients: schedule.recipients,
      subject,
      htmlContent: html,
      reportData: reportData as unknown as object,
      status: result.success ? "sent" : "failed",
      error: result.error ?? null,
    },
  });

  const nextSendAt = computeNextSendAt(schedule.frequency, schedule.timeOfDay, schedule.dayOfWeek, schedule.dayOfMonth);
  await prisma.emailReportSchedule.update({
    where: { id: schedule.id },
    data: { lastSentAt: new Date(), nextSendAt },
  });

  return result;
}
