/**
 * GET /api/cron/ai-citation-check
 *
 * Weekly cron that checks DataForSEO SERP results for AI Overview,
 * Featured Snippet, and People Also Ask presence for each brand's
 * tracked keywords. Creates AiCitation records.
 *
 * Schedule: "0 9 * * 1" (Monday 9am UTC)
 */

import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import { detectCompetitiveChanges, writeCompetitiveSignals } from "@/lib/intelligence/competitive-ai-alerts";
import { logCron } from "@/lib/services/cron-logger";
import { brandLocationCode } from "@/lib/services/dataforseo-locations";
import { getBrandLocations } from "@/lib/brand-helpers";

// ─── SERP item extraction helpers ─────────────────────────────────────────
// DataForSEO returns SERP features as typed items. Each type carries a
// different payload shape — ai_overview has a `references` array, PAA
// has nested `items`, featured_snippet has inline fields. The helpers
// below extract brand citation + snippet + competitors uniformly.

interface Citation {
  citationType: "ai_overview" | "featured_snippet" | "people_also_ask";
  channel: "llmo" | "geo" | "aeo";
  brandCited: boolean;
  citedUrl: string | null;
  citedPosition: number | null;
  citedSnippet: string | null;
  competitorsCited: Array<{ domain: string; position: number; url: string | null }>;
}

function matchesBrand(urlOrDomain: string | null | undefined, brandDomain: string): boolean {
  if (!urlOrDomain) return false;
  const lower = urlOrDomain.toLowerCase();
  return lower === brandDomain || lower.includes(`//${brandDomain}`) || lower.includes(`.${brandDomain}`) || lower.startsWith(`${brandDomain}/`) || lower.endsWith(`.${brandDomain}`) || lower === brandDomain;
}

function extractHost(url: string | null | undefined): string | null {
  if (!url) return null;
  try { return new URL(url).hostname.replace(/^www\./, "").toLowerCase(); }
  catch { return null; }
}

/**
 * Walks a SERP item and extracts citation info. Returns null when the
 * item isn't a feature we track. For ai_overview + featured_snippet +
 * people_also_ask items, returns the brand-citation status plus the
 * full list of competitor domains that appeared in the same feature.
 */
function extractCitation(item: Record<string, unknown>, brandDomain: string): Citation | null {
  const type = item.type as string;

  let citationType: Citation["citationType"];
  let channel: Citation["channel"];
  if (type === "ai_overview") { citationType = "ai_overview"; channel = "geo"; }
  else if (type === "featured_snippet") { citationType = "featured_snippet"; channel = "aeo"; }
  else if (type === "people_also_ask") { citationType = "people_also_ask"; channel = "aeo"; }
  else return null;

  // Gather sources. Shape varies by type — try known fields.
  interface Source { url: string | null; domain: string | null; title: string | null; description: string | null; position: number | null }
  const sources: Source[] = [];

  // ai_overview: references array + items array + inline text
  if (type === "ai_overview") {
    const refs = (item.references as Array<Record<string, unknown>> | undefined) ?? [];
    for (let i = 0; i < refs.length; i++) {
      const r = refs[i];
      sources.push({
        url: typeof r.url === "string" ? r.url : null,
        domain: typeof r.domain === "string" ? r.domain : (typeof r.source === "string" ? r.source : extractHost(typeof r.url === "string" ? r.url : null)),
        title: typeof r.title === "string" ? r.title : null,
        description: typeof r.text === "string" ? r.text : (typeof r.description === "string" ? r.description : null),
        position: i + 1,
      });
    }
    const nestedItems = (item.items as Array<Record<string, unknown>> | undefined) ?? [];
    for (let i = 0; i < nestedItems.length; i++) {
      const n = nestedItems[i];
      const url = typeof n.url === "string" ? n.url : null;
      if (!url && !n.domain) continue;
      sources.push({
        url,
        domain: typeof n.domain === "string" ? n.domain : extractHost(url),
        title: typeof n.title === "string" ? n.title : null,
        description: typeof n.description === "string" ? n.description : (typeof n.text === "string" ? n.text : null),
        position: i + 1,
      });
    }
  } else if (type === "featured_snippet") {
    sources.push({
      url: typeof item.url === "string" ? item.url : null,
      domain: typeof item.domain === "string" ? item.domain : extractHost(typeof item.url === "string" ? item.url : null),
      title: typeof item.title === "string" ? item.title : null,
      description: typeof item.description === "string" ? item.description : null,
      position: 1,
    });
  } else if (type === "people_also_ask") {
    const paaItems = (item.items as Array<Record<string, unknown>> | undefined) ?? [];
    for (let i = 0; i < paaItems.length; i++) {
      const p = paaItems[i];
      const expanded = (p.expanded_element as Array<Record<string, unknown>> | undefined) ?? [];
      for (const e of expanded) {
        sources.push({
          url: typeof e.url === "string" ? e.url : null,
          domain: typeof e.domain === "string" ? e.domain : extractHost(typeof e.url === "string" ? e.url : null),
          title: typeof e.title === "string" ? e.title : null,
          description: typeof e.description === "string" ? e.description : null,
          position: i + 1,
        });
      }
    }
  }

  // Find brand source + collect competitors
  let brandSource: Source | null = null;
  const competitors: Citation["competitorsCited"] = [];
  for (const s of sources) {
    const host = s.domain?.toLowerCase().replace(/^www\./, "") ?? extractHost(s.url);
    if (!host) continue;
    const isBrand = host === brandDomain || host.endsWith(`.${brandDomain}`);
    if (isBrand && !brandSource) {
      brandSource = s;
    } else if (!isBrand) {
      if (competitors.find((c) => c.domain === host)) continue;
      competitors.push({ domain: host, position: s.position ?? 0, url: s.url });
    }
  }

  return {
    citationType,
    channel,
    brandCited: !!brandSource,
    citedUrl: brandSource?.url ?? null,
    citedPosition: brandSource?.position ?? null,
    citedSnippet: brandSource?.description?.slice(0, 500) ?? null,
    competitorsCited: competitors.slice(0, 10),
  };
}

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

const DFSEO_LOGIN = process.env.DATAFORSEO_LOGIN ?? "";
const DFSEO_PASS = process.env.DATAFORSEO_PASSWORD ?? "";

export async function GET(req: Request) {
  const cronSecret = process.env.CRON_SECRET;
  if (!cronSecret) {
    return NextResponse.json({ error: "Server misconfigured: CRON_SECRET unset" }, { status: 500 });
  }
  const authHeader = req.headers.get("authorization");
  const querySecret = new URL(req.url).searchParams.get("secret");
  if (authHeader !== `Bearer ${cronSecret}` && querySecret !== cronSecret) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  if (!DFSEO_LOGIN || !DFSEO_PASS) {
    return NextResponse.json({ error: "DataForSEO credentials not configured" }, { status: 200 });
  }

  return logCron("/api/cron/ai-citation-check", async () => {
  // Find brands with tracked keywords
  const brands = await prisma.brand.findMany({
    where: { status: { not: "deleted" }, isDemo: false },
    select: {
      id: true,
      name: true,
      domain: true,
      location: true,
      locations: true,
      trackedKeywords: {
        where: { isActive: true },
        select: { keyword: true },
        take: 20,
      },
    },
  });

  const results: Record<string, { keywords: number; citations: number }> = {};

  for (const brand of brands) {
    if (brand.trackedKeywords.length === 0) continue;
    const brandResult = { keywords: brand.trackedKeywords.length, citations: 0 };

    const locationCode = brandLocationCode(getBrandLocations(brand)[0] ?? null);
    for (const kw of brand.trackedKeywords.slice(0, 10)) {
      try {
        const res = await fetch("https://api.dataforseo.com/v3/serp/google/organic/live/advanced", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "Authorization": `Basic ${Buffer.from(`${DFSEO_LOGIN}:${DFSEO_PASS}`).toString("base64")}`,
          },
          body: JSON.stringify([{
            keyword: kw.keyword,
            location_code: locationCode,
            language_code: "en",
            device: "desktop",
          }]),
        });

        if (!res.ok) continue;
        const data = await res.json();
        const items = data?.tasks?.[0]?.result?.[0]?.items ?? [];

        const domain = brand.domain.replace(/^www\./, "").toLowerCase();
        const checkedAt = new Date();

        for (const item of items) {
          const citation = extractCitation(item as Record<string, unknown>, domain);
          if (!citation) continue;

          const { citationType, channel, brandCited, citedUrl, citedPosition, citedSnippet, competitorsCited } = citation;

          await prisma.aiCitation.upsert({
            where: {
              brandId_keyword_searchEngine_checkedAt: {
                brandId: brand.id,
                keyword: kw.keyword,
                searchEngine: "google",
                checkedAt,
              },
            },
            create: {
              brandId: brand.id,
              keyword: kw.keyword,
              searchEngine: "google",
              citationType,
              channel,
              brandCited,
              citedUrl,
              citedPosition,
              citedSnippet,
              competitorsCited: competitorsCited.length > 0 ? JSON.parse(JSON.stringify(competitorsCited)) : undefined,
              checkedAt,
            },
            update: {
              citationType, channel, brandCited, citedUrl, citedPosition, citedSnippet,
              competitorsCited: competitorsCited.length > 0 ? JSON.parse(JSON.stringify(competitorsCited)) : undefined,
            },
          });

          if (brandCited) brandResult.citations++;

          // DataForSEO → Site Tag correlation: when DataForSEO confirms
          // the brand appears in an AI Overview for this keyword AND
          // citedUrl is known, back-classify recent google-referrer
          // sessions that landed on that URL as Google AI Overview
          // clicks. Text fragments are the stronger primary signal but
          // aren't always present; this correlation catches clicks
          // where Google omitted the fragment.
          if (brandCited && citationType === "ai_overview" && citedUrl) {
            try {
              const site = await prisma.trackedSite.findUnique({
                where: { brandId: brand.id },
                select: { id: true },
              });
              if (site) {
                const sevenDaysAgo = new Date(Date.now() - 7 * 86_400_000);
                const urlPath = (() => {
                  try { return new URL(citedUrl).pathname; } catch { return citedUrl; }
                })();
                const candidates = await prisma.trackedSession.findMany({
                  where: {
                    trackedSiteId: site.id,
                    startedAt: { gte: sevenDaysAgo },
                    referrer: { contains: "google.", mode: "insensitive" },
                    OR: [
                      { entryPage: citedUrl },
                      { entryPage: { contains: urlPath } },
                    ],
                  },
                  select: { id: true, sessionId: true, entryPage: true, startedAt: true, pageCount: true, referrer: true },
                  take: 50,
                });
                for (const s of candidates) {
                  const existing = await prisma.aiAttribution.findFirst({
                    where: { brandId: brand.id, sessionId: s.sessionId },
                    select: { id: true },
                  });
                  if (existing) continue;
                  const conversion = await prisma.siteConversion.findFirst({
                    where: { brandId: brand.id, sessionId: s.sessionId },
                    select: { conversionType: true, pageUrl: true, id: true, timestamp: true },
                  }).catch(() => null);
                  await prisma.aiAttribution.create({
                    data: {
                      brandId: brand.id,
                      sessionId: s.sessionId,
                      channel: "geo",
                      aiPlatform: "google_ai_overview",
                      referrerUrl: (s.referrer ?? "").slice(0, 2048),
                      queryContext: `[correlated] ${kw.keyword}`.slice(0, 500),
                      landingPage: s.entryPage.slice(0, 2048),
                      pagesViewed: s.pageCount,
                      timestamp: s.startedAt,
                      converted: !!conversion,
                      conversionType: conversion?.conversionType ?? null,
                      conversionPage: conversion?.pageUrl ?? null,
                      conversionId: conversion?.id ?? null,
                      conversionTimestamp: conversion?.timestamp ?? null,
                    },
                  }).catch(() => null);
                }
              }
            } catch { /* non-fatal — correlation is a best-effort enhancement */ }
          }
        }
      } catch (err) {
        console.error(`[ai-citation] ${brand.name}/${kw.keyword} failed:`, err instanceof Error ? err.message : String(err));
      }
    }

    results[brand.name] = brandResult;
  }

  // After citation checks, run competitive change detection
  const competitiveResults: Record<string, { created: number; deduped: number }> = {};
  for (const brand of brands) {
    try {
      const signals = await detectCompetitiveChanges(brand.id);
      if (signals.length > 0) {
        competitiveResults[brand.name] = await writeCompetitiveSignals(brand.id, signals);
      }
    } catch { /* ignore individual brand failures */ }
  }

  const payload = { brands: brands.length, results, competitiveAlerts: competitiveResults };
  return NextResponse.json(payload);
  });
}
