/**
 * GET /api/cron/rank-check — Daily automated rank check.
 *
 * Intended to be called by Vercel Cron (or equivalent scheduler).
 * Protected by CRON_SECRET — the request must include an
 * Authorization: Bearer <CRON_SECRET> header or ?secret= param.
 *
 * For each brand that has at least one active TrackedKeyword, runs
 * a rank check and logs any significant ranking changes as signals
 * into the signals system (in-memory — surfaced on next dashboard
 * load via generateSignals).
 */

import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import { checkBrand } from "@/lib/services/rank-check";
import { logCron } from "@/lib/services/cron-logger";

export const dynamic = "force-dynamic";
export const maxDuration = 300; // 5 min — can be long for many brands

const CRON_SECRET = process.env.CRON_SECRET ?? "";

/** Soft cost cap per cron invocation. DataForSEO Live SERP Advanced is
 *  ~$0.002 per check; $5 = ~2,500 checks, well above any reasonable
 *  per-day fan-out. Any brand whose check would push the running total
 *  past this cap is skipped, with a console warning so ops can see it. */
const DAILY_COST_CAP_USD = Number(process.env.DATAFORSEO_DAILY_CAP_USD ?? "5");

/** Hard per-brand keyword cap — defends against a single misconfigured
 *  brand burning the daily budget. Trims the in-memory keyword list
 *  (TrackedKeyword.isActive=true count) to this many entries before
 *  fanning out to checkBrand. */
const MAX_KEYWORDS_PER_BRAND = 100;

function authOk(req: Request): boolean {
  if (!CRON_SECRET) return false; // secret must be set
  const header = req.headers.get("authorization") ?? "";
  if (header === `Bearer ${CRON_SECRET}`) return true;
  const url = new URL(req.url);
  return url.searchParams.get("secret") === CRON_SECRET;
}

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

  return logCron("/api/cron/rank-check", async () => {
  // Find all brands with at least one active tracked keyword. Include the
  // active keyword count so we can filter out brands that exceed the
  // per-brand keyword cap before starting any DataForSEO calls.
  const brands = await prisma.brand.findMany({
    where: { trackedKeywords: { some: { isActive: true } }, isDemo: false },
    select: {
      id: true,
      domain: true,
      _count: { select: { trackedKeywords: { where: { isActive: true } } } },
    },
  });

  type BrandResult = {
    brandId: string;
    domain: string;
    checked: number;
    improved: number;
    declined: number;
    costUsd: number;
    provider: string;
    skipped?: string;
  };
  const results: BrandResult[] = [];
  let totalCostUsd = 0;

  for (const brand of brands) {
    // Cap-check: skip the brand if its expected cost (assume worst-case
    // $0.002 per active keyword) would push us past the daily cap.
    const activeCount = brand._count.trackedKeywords;
    if (activeCount > MAX_KEYWORDS_PER_BRAND) {
      console.warn(`[cron/rank-check] brand ${brand.domain} has ${activeCount} active keywords > MAX_KEYWORDS_PER_BRAND ${MAX_KEYWORDS_PER_BRAND}; skipping`);
      results.push({ brandId: brand.id, domain: brand.domain, checked: 0, improved: 0, declined: 0, costUsd: 0, provider: "skipped", skipped: "exceeds_per_brand_cap" });
      continue;
    }
    const expectedAddCost = activeCount * 0.002;
    if (totalCostUsd + expectedAddCost > DAILY_COST_CAP_USD) {
      console.warn(`[cron/rank-check] daily cap $${DAILY_COST_CAP_USD} would be exceeded by ${brand.domain} (+$${expectedAddCost.toFixed(4)} on running $${totalCostUsd.toFixed(4)}); skipping`);
      results.push({ brandId: brand.id, domain: brand.domain, checked: 0, improved: 0, declined: 0, costUsd: 0, provider: "skipped", skipped: "daily_cap_reached" });
      continue;
    }

    try {
      const summary = await checkBrand(brand.id);
      totalCostUsd += summary.costUsd;
      results.push({
        brandId: brand.id,
        domain: brand.domain,
        checked: summary.checked,
        improved: summary.improved,
        declined: summary.declined,
        costUsd: summary.costUsd,
        provider: summary.provider,
      });

      // ── Generate signals for significant changes ────────────────────
      await generateRankSignals(brand.id);
    } catch (err) {
      console.error(`[cron/rank-check] failed for ${brand.domain}`, err);
    }
  }

  console.info(`[cron/rank-check] completed: ${results.length} brands, $${totalCostUsd.toFixed(4)} total cost (cap $${DAILY_COST_CAP_USD})`);

  const payload = {
    brands: results.length,
    totalCostUsd: Math.round(totalCostUsd * 10000) / 10000,
    dailyCostCapUsd: DAILY_COST_CAP_USD,
    results,
  };
  return NextResponse.json(payload);
  });
}

/**
 * Scans the latest ranking rows for a brand and writes SiteEvent
 * records for significant changes. These are picked up by the
 * dashboard signals system on next load.
 */
async function generateRankSignals(brandId: string) {
  const keywords = await prisma.trackedKeyword.findMany({
    where: { brandId, isActive: true },
    include: { rankings: { orderBy: { checkedAt: "desc" }, take: 1 } },
  });

  for (const kw of keywords) {
    const r = kw.rankings[0];
    if (!r) continue;
    const pos = r.position;
    const prev = r.previousPosition;

    let eventType: string | null = null;
    let title = "";
    let priority: "info" | "warning" = "info";

    if (pos != null && pos <= 3 && (prev == null || prev > 3)) {
      eventType = "rank_entered_top3";
      title = `"${kw.keyword}" entered the top 3 (position ${pos})`;
    } else if (pos == null && prev != null && prev <= 10) {
      eventType = "rank_dropped_off_page1";
      title = `"${kw.keyword}" dropped off page 1 (was position ${prev})`;
      priority = "warning";
    } else if (pos != null && prev != null) {
      const change = prev - pos;
      if (change >= 5) {
        eventType = "rank_improved";
        title = `"${kw.keyword}" improved ${change} positions (now ${pos})`;
      } else if (change <= -5) {
        eventType = "rank_declined";
        title = `"${kw.keyword}" declined ${Math.abs(change)} positions (now ${pos})`;
        priority = "warning";
      }
    }

    if (eventType) {
      try {
        await prisma.siteEvent.create({
          data: {
            brandId,
            pageUrl: r.url || kw.targetUrl || "",
            eventType,
            eventData: {
              keyword: kw.keyword,
              position: pos,
              previousPosition: prev,
              priority,
              title,
            } as object,
          },
        });
      } catch {
        /* best effort — SiteEvent creation is non-critical */
      }
    }
  }
}
