/**
 * GET /api/content-briefs/insights?brandId=X&keyword=Y
 *
 * One-shot data aggregator that powers every "live data" section on
 * the SEO Content Brief page: SERP Insights, Competitive Insights,
 * Local Insights, Internal Linking Opportunities, and the
 * cross-feature badges (Rank Tracker / Keyword Research). Returns a
 * single JSON payload so the page makes one network call instead of
 * five.
 *
 * Why one endpoint instead of five:
 *   - Each section needs the same brand + keyword + locations
 *     resolution. Sharing the work in one route avoids redoing it.
 *   - The page renders all sections at once when the user clicks
 *     "Generate" — there's no incremental UX to gain from streaming.
 *   - Easier to keep the data shape consistent (e.g. competitorPages
 *     scoped the same way as internalLinks).
 *
 * All sub-queries use insensitive `contains` against the keyword so
 * the user can type "cardiologist" and pick up "cardiologist near
 * me" / "cardiologist houston" / "Spanish cardiologist" matches.
 *
 * Returns 200 with empty arrays / nulls for any section that has no
 * data — never 404 / 500 unless the brand auth fails.
 */

import { NextResponse } from "next/server";
import { getCurrentUser } from "@/lib/auth";
import { prisma } from "@/lib/prisma";

export const dynamic = "force-dynamic";
export const maxDuration = 30;

interface SerpBlock {
  /** GSC connection state — drives the "Connect GSC" empty CTA on the page. */
  gscConnected: boolean;
  /** Aggregate stats for the keyword across all matching GscQuery rows. */
  totals: {
    clicks: number;
    impressions: number;
    avgPosition: number | null;
    ctr: number | null;
    matchedQueries: number;
  } | null;
  /** Top 5 actual ranking pages on the brand domain for this keyword family. */
  topPages: Array<{ pageUrl: string; clicks: number; impressions: number; position: number }>;
  /** Closely-matching query variants the brand already ranks for. */
  matchingQueries: Array<{ query: string; clicks: number; impressions: number; position: number }>;
}

interface CompetitorBlock {
  /** Competitor-domain pages that mention the keyword in title/h1/url. */
  pages: Array<{
    competitorDomain: string;
    url: string;
    title: string | null;
    wordCount: number | null;
  }>;
  /** Distinct competitor domains covering this topic. */
  domainsCovered: number;
  /** Median word count across the competitor pages — useful as a
   *  "competitive content depth" target on the brief. */
  medianWordCount: number | null;
}

interface LocalBlock {
  gbpLocations: Array<{ city: string | null; state: string | null; locationName: string }>;
  /** Currently top countries (TrackedSession.country only). Field
   *  name kept "topVisitorCities" so the brief UI doesn't have to
   *  change once city-level data lands. */
  topVisitorCities: Array<{ city: string; sessions: number }>;
  /** Suggested local keyword variations: "{keyword} {city, state}". */
  suggestedVariations: string[];
}

interface InternalLinksBlock {
  /** Existing brand pages already covering related topics. */
  pages: Array<{
    pageUrl: string;
    clicks: number;
    impressions: number;
    avgPosition: number;
    /** Suggested anchor text — defaults to the keyword + a short
     *  descriptor pulled from the URL slug. */
    suggestedAnchor: string;
  }>;
}

interface CrossFeatureBlock {
  /** Matching TrackedKeyword + latest position if the user is
   *  already tracking this keyword in Rank Tracker. */
  rankTracker: {
    trackedKeywordId: string;
    keyword: string;
    currentPosition: number | null;
    lastChecked: string | null;
  } | null;
  /** True when the brand has cached KeywordResearch for this seed,
   *  so the brief can deep-link "View full research →" instead of
   *  showing a generic "Research this keyword" CTA. */
  hasKeywordResearch: boolean;
}

export async function GET(req: Request) {
  const user = await getCurrentUser();
  if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });

  const { searchParams } = new URL(req.url);
  const brandId = searchParams.get("brandId");
  const keywordRaw = searchParams.get("keyword");
  if (!brandId) return NextResponse.json({ error: "brandId required" }, { status: 400 });
  if (!keywordRaw || keywordRaw.trim().length < 2) {
    return NextResponse.json({ error: "keyword required (min 2 chars)" }, { status: 400 });
  }
  const keyword = keywordRaw.trim();

  const brand = await prisma.brand.findUnique({
    where: { id: brandId },
    include: { organization: { include: { memberships: true } } },
  });
  if (!brand) return NextResponse.json({ error: "Brand not found" }, { status: 404 });
  if (!brand.organization.memberships.some((m) => m.userId === user.id)) {
    return NextResponse.json({ error: "Access denied" }, { status: 403 });
  }

  // Single window for everything — last 90 days. Long enough to surface
  // seasonal trends, short enough that the data still reflects current
  // ranking state.
  const since = new Date(Date.now() - 90 * 86_400_000);

  // ── Parallel data fetch. Each block degrades to its empty shape on
  //    missing data; nothing throws past this point. ─────────────────
  const [
    gscIntegration,
    matchingGscQueries,
    matchingGscPages,
    competitorPages,
    gbpLocations,
    site,
    relatedPages,
    trackedKeyword,
    keywordResearchHit,
  ] = await Promise.all([
    prisma.brandIntegration.findUnique({
      where: { brandId_integrationId: { brandId, integrationId: "gsc" } },
      select: { connected: true, status: true },
    }).catch(() => null),
    prisma.gscQuery.findMany({
      where: { brandId, query: { contains: keyword, mode: "insensitive" }, date: { gte: since } },
      orderBy: { clicks: "desc" },
      take: 50,
      select: { query: true, clicks: true, impressions: true, position: true },
    }).catch(() => []),
    prisma.gscPage.findMany({
      where: {
        brandId,
        date: { gte: since },
        // Match URLs that contain ANY token from the keyword (rough
        // but useful) — kept simple to avoid full-text-search infra.
        OR: keyword.split(/\s+/).filter((t) => t.length >= 3).map((tok) => ({
          pageUrl: { contains: tok, mode: "insensitive" as const },
        })),
      },
      orderBy: { clicks: "desc" },
      take: 20,
      select: { pageUrl: true, clicks: true, impressions: true, position: true },
    }).catch(() => []),
    prisma.competitorPage.findMany({
      where: {
        competitor: { brandId, isActive: true },
        OR: [
          { title: { contains: keyword, mode: "insensitive" } },
          { h1: { contains: keyword, mode: "insensitive" } },
          { url: { contains: keyword, mode: "insensitive" } },
        ],
        status: { notIn: ["removed"] },
      },
      include: { competitor: { select: { domain: true } } },
      take: 25,
    }).catch(() => []),
    prisma.gbpLocation.findMany({
      where: { brandId, isActive: true },
      select: { city: true, state: true, locationName: true },
    }).catch(() => []),
    prisma.trackedSite.findUnique({ where: { brandId }, select: { id: true } }).catch(() => null),
    // Internal-linking pool — pages on the brand domain already
    // covering related topics. Looser matching than matchingGscPages
    // (any URL containing any keyword token).
    prisma.gscPage.groupBy({
      by: ["pageUrl"],
      where: {
        brandId,
        date: { gte: since },
        clicks: { gt: 0 },
        OR: keyword.split(/\s+/).filter((t) => t.length >= 3).map((tok) => ({
          pageUrl: { contains: tok, mode: "insensitive" as const },
        })),
      },
      _sum: { clicks: true, impressions: true },
      _avg: { position: true },
      orderBy: { _sum: { clicks: "desc" } },
      take: 10,
    }).catch(() => []),
    // Cross-feature: Rank Tracker. Match exactly to avoid surfacing
    // the wrong tracked keyword for a substring hit.
    prisma.trackedKeyword.findFirst({
      where: { brandId, isActive: true, keyword: { equals: keyword, mode: "insensitive" } },
      include: { rankings: { orderBy: { checkedAt: "desc" }, take: 1 } },
    }).catch(() => null),
    prisma.keywordResearch.findFirst({
      where: { brandId, seedKeyword: { equals: keyword, mode: "insensitive" } },
      select: { id: true },
    }).catch(() => null),
  ]);

  // ── SERP block ────────────────────────────────────────────────────
  const gscConnected = !!(gscIntegration?.connected && gscIntegration?.status !== "expired");
  const totalClicks = matchingGscQueries.reduce((s, q) => s + q.clicks, 0);
  const totalImps = matchingGscQueries.reduce((s, q) => s + q.impressions, 0);
  const positionSum = matchingGscQueries.reduce((s, q) => s + q.position, 0);
  const serp: SerpBlock = {
    gscConnected,
    totals: matchingGscQueries.length > 0 ? {
      clicks: totalClicks,
      impressions: totalImps,
      avgPosition: matchingGscQueries.length > 0 ? Math.round((positionSum / matchingGscQueries.length) * 10) / 10 : null,
      ctr: totalImps > 0 ? Math.round((totalClicks / totalImps) * 10000) / 100 : null,
      matchedQueries: matchingGscQueries.length,
    } : null,
    topPages: matchingGscPages.slice(0, 5).map((p) => ({
      pageUrl: p.pageUrl,
      clicks: p.clicks,
      impressions: p.impressions,
      position: Math.round(p.position * 10) / 10,
    })),
    matchingQueries: matchingGscQueries.slice(0, 10).map((q) => ({
      query: q.query,
      clicks: q.clicks,
      impressions: q.impressions,
      position: Math.round(q.position * 10) / 10,
    })),
  };

  // ── Competitor block ──────────────────────────────────────────────
  const wordCounts = competitorPages
    .map((p) => p.wordCount)
    .filter((w): w is number => typeof w === "number" && w > 0)
    .sort((a, b) => a - b);
  const median = wordCounts.length > 0
    ? wordCounts[Math.floor(wordCounts.length / 2)]
    : null;
  const competitor: CompetitorBlock = {
    pages: competitorPages.slice(0, 10).map((p) => ({
      competitorDomain: p.competitor.domain,
      url: p.url,
      title: p.title,
      wordCount: p.wordCount,
    })),
    domainsCovered: new Set(competitorPages.map((p) => p.competitor.domain)).size,
    medianWordCount: median,
  };

  // ── Local block ───────────────────────────────────────────────────
  let topVisitorCities: Array<{ city: string; sessions: number }> = [];
  if (site?.id) {
    try {
      const grouped = await prisma.trackedSession.groupBy({
        by: ["country"],
        where: { trackedSiteId: site.id, country: { not: null }, startedAt: { gte: new Date(Date.now() - 30 * 86_400_000) } },
        _count: true,
        orderBy: { _count: { country: "desc" } },
        take: 5,
      });
      topVisitorCities = grouped
        .filter((g) => g.country)
        .map((g) => ({ city: g.country!, sessions: g._count }));
    } catch { /* skip */ }
  }
  // Suggested variations: combine the keyword with each unique
  // city/state pair from GBP. Caps at 8 to keep the brief tidy.
  const variations: string[] = [];
  const seenVar = new Set<string>();
  for (const g of gbpLocations) {
    const loc = [g.city, g.state].filter(Boolean).join(", ");
    if (!loc) continue;
    const v = `${keyword} ${loc.toLowerCase()}`;
    if (!seenVar.has(v)) { seenVar.add(v); variations.push(v); }
    if (variations.length >= 8) break;
  }
  // Always include the universal "near me" pattern.
  const nearMe = `${keyword} near me`;
  if (!seenVar.has(nearMe)) variations.unshift(nearMe);
  const local: LocalBlock = {
    gbpLocations,
    topVisitorCities,
    suggestedVariations: variations.slice(0, 8),
  };

  // ── Internal links block ──────────────────────────────────────────
  function suggestAnchor(pageUrl: string, kw: string): string {
    // Use the URL's last meaningful segment as the descriptor when
    // the keyword alone would be too generic. Falls back to keyword.
    try {
      const path = new URL(pageUrl).pathname;
      const segments = path.split("/").filter((s) => s && s !== "es");
      const last = segments[segments.length - 1] ?? "";
      const slug = last.replace(/[-_]/g, " ").replace(/\.[^/.]+$/, "").trim();
      if (slug.length > 5 && slug.toLowerCase() !== kw.toLowerCase()) {
        return slug;
      }
    } catch { /* fall through */ }
    return kw;
  }
  const internalLinks: InternalLinksBlock = {
    pages: relatedPages.map((p) => ({
      pageUrl: p.pageUrl,
      clicks: p._sum.clicks ?? 0,
      impressions: p._sum.impressions ?? 0,
      avgPosition: Math.round((p._avg.position ?? 0) * 10) / 10,
      suggestedAnchor: suggestAnchor(p.pageUrl, keyword),
    })),
  };

  // ── Cross-feature block ───────────────────────────────────────────
  const crossFeature: CrossFeatureBlock = {
    rankTracker: trackedKeyword
      ? {
          trackedKeywordId: trackedKeyword.id,
          keyword: trackedKeyword.keyword,
          currentPosition: trackedKeyword.rankings[0]?.position ?? null,
          lastChecked: trackedKeyword.rankings[0]?.checkedAt?.toISOString() ?? null,
        }
      : null,
    hasKeywordResearch: !!keywordResearchHit,
  };

  return NextResponse.json({
    keyword,
    serp,
    competitor,
    local,
    internalLinks,
    crossFeature,
  });
}
