import { NextRequest, NextResponse } from 'next/server';
import { assertCronAuthorized } from '@/lib/cron/auth';
import { withJobRun } from '@/lib/cron/jobRun';
import { prisma } from '@/lib/db/client';
import { gameRecordToFacts, applyPinnedFacts, mergeRawgEnrichment } from '@/lib/content/mapper';
import { generateGameContent } from '@/lib/content/generator';
import { evaluateContentQuality, sourceDataIsSufficientForPublish } from '@/lib/content/qualityGate';
import { enrichFromRawg } from '@/lib/rawg/client';
import { getPinnedFacts, getForceRegenerateSlugs, loadOverrides } from '@/lib/config/overrides';

export const runtime = 'nodejs';
export const maxDuration = 300; // AI generation is the slow step; keep batches small (see BATCH_SIZE)

const BATCH_SIZE = 8; // deliberately small: bounds per-run Anthropic spend and keeps maxDuration safe
const MAX_ATTEMPTS_BEFORE_PAUSING = 5;

export async function POST(req: NextRequest) {
  const authError = assertCronAuthorized(req);
  if (authError) return authError;

  const outcome = await withJobRun('generate-content', async () => {
    const forceSlugs = await getForceRegenerateSlugs();
    const { excludedSlugs } = await loadOverrides();

    const candidates = await prisma.game.findMany({
      where: {
        status: { not: 'EXCLUDED' },
        slug: { notIn: excludedSlugs },
        OR: [
          { status: 'NEEDS_REFRESH' },
          { status: 'DISCOVERED', generationAttempts: { lt: MAX_ATTEMPTS_BEFORE_PAUSING } },
          // forceRegenerate should work even on an already-PUBLISHED page
          // (e.g. a human just added a pinnedFacts correction) — it's
          // intentionally not restricted to the other two statuses. It
          // does skip anything mid-generation right now to avoid racing
          // with itself.
          { slug: { in: forceSlugs }, status: { not: 'GENERATING' } },
        ],
      },
      take: BATCH_SIZE,
      orderBy: [{ status: 'asc' }, { igdbRatingCount: 'desc' }],
      include: { content: true },
    });

    let itemsOk = 0;
    let itemsFailed = 0;
    const detail: Record<string, string> = {};

    for (const game of candidates) {
      await prisma.game.update({ where: { id: game.id }, data: { status: 'GENERATING' } });

      try {
        // Gather the same inputs a human editor would: source facts,
        // manual corrections, supplemental data, and recent news context.
        const pinned = await getPinnedFacts(game.slug);
        const recentNews = await prisma.gameNewsItem.findMany({
          where: { gameId: game.id },
          include: { newsItem: true },
          orderBy: { newsItem: { publishedAt: 'desc' } },
          take: 5,
        });

        let facts = gameRecordToFacts(
          game,
          recentNews.map((n) => n.newsItem.rewrittenSummary ?? n.newsItem.title),
        );

        const sourceCheck = sourceDataIsSufficientForPublish(facts);
        if (!sourceCheck.ok) {
          // Try RAWG before giving up — this is exactly the case RAWG
          // enrichment exists for.
          const rawg = await enrichFromRawg(facts.name).catch(() => null);
          facts = mergeRawgEnrichment(facts, rawg);
        }

        facts = applyPinnedFacts(facts, pinned);

        const finalSourceCheck = sourceDataIsSufficientForPublish(facts);
        if (!finalSourceCheck.ok) {
          await prisma.game.update({
            where: { id: game.id },
            data: {
              status: 'PENDING_REVIEW',
              qualityFailReasons: [`source-data-thin: ${finalSourceCheck.reason}`],
              generationAttempts: { increment: 1 },
              lastGeneratedAt: new Date(),
            },
          });
          detail[game.slug] = 'skipped-thin-source';
          itemsOk++; // not a system failure — correct behavior per spec
          continue;
        }

        const generated = await generateGameContent(facts);

        // Compare against recently published pages in the same primary
        // genre to catch templated near-duplicates.
        const primaryGenre = facts.genres[0];
        const comparisonSet = await prisma.gameContent.findMany({
          where: {
            game: {
              status: 'PUBLISHED',
              ...(primaryGenre ? { genres: { has: primaryGenre } } : {}),
            },
            contentFingerprint: { not: null },
          },
          select: { contentFingerprint: true },
          take: 30,
          orderBy: { updatedAt: 'desc' },
        });

        const gate = evaluateContentQuality(
          facts,
          generated,
          comparisonSet.map((c) => c.contentFingerprint as string),
        );

        const wasPreviouslyPublished = game.publishedAt !== null;

        // Only overwrite stored content when the new version actually
        // clears the gate, OR this game has never published before (in
        // which case there's nothing live to protect, and saving the
        // draft lets a human inspect why it failed via qualityFailReasons
        // instead of burning another generation attempt from scratch).
        // A failed *re*-generation of an already-published page leaves
        // the last good GameContent row untouched — see refresh-stale's
        // "stays live with old content" comment, which this makes true.
        if (gate.passed || !wasPreviouslyPublished) {
          await prisma.gameContent.upsert({
            where: { gameId: game.id },
            create: {
              gameId: game.id,
              overview: generated.overview,
              gameplay: generated.gameplay,
              features: generated.features,
              tipsAndTricks: generated.tipsAndTricks,
              beginnerGuide: generated.beginnerGuide,
              howToPlay: generated.howToPlay,
              alternatives: generated.alternatives,
              faqs: generated.faqs,
              pros: generated.pros,
              cons: generated.cons,
              metaTitle: generated.metaTitle,
              metaDescription: generated.metaDescription,
              keywords: generated.keywords,
              contentFingerprint: gate.fingerprint,
            },
            update: {
              overview: generated.overview,
              gameplay: generated.gameplay,
              features: generated.features,
              tipsAndTricks: generated.tipsAndTricks,
              beginnerGuide: generated.beginnerGuide,
              howToPlay: generated.howToPlay,
              alternatives: generated.alternatives,
              faqs: generated.faqs,
              pros: generated.pros,
              cons: generated.cons,
              metaTitle: generated.metaTitle,
              metaDescription: generated.metaDescription,
              keywords: generated.keywords,
              contentFingerprint: gate.fingerprint,
              version: { increment: 1 },
            },
          });
        }

        await prisma.game.update({
          where: { id: game.id },
          data: {
            status: gate.passed ? 'PUBLISHED' : 'PENDING_REVIEW',
            qualityScore: gate.score,
            qualityFailReasons: gate.reasons,
            publishedAt: gate.passed ? new Date() : game.publishedAt,
            lastGeneratedAt: new Date(),
            generationAttempts: { increment: 1 },
          },
        });

        detail[game.slug] = gate.passed ? `published (score ${gate.score})` : `pending-review (score ${gate.score})`;
        itemsOk++;
      } catch (err) {
        itemsFailed++;
        detail[game.slug] = `error: ${err instanceof Error ? err.message : String(err)}`;
        await prisma.game.update({
          where: { id: game.id },
          data: { status: 'PENDING_REVIEW', generationAttempts: { increment: 1 } },
        }).catch(() => {});
        console.error(`[generate-content] Failed on "${game.name}" (${game.slug}):`, err);
      }
    }

    return { itemsSeen: candidates.length, itemsOk, itemsFailed, detail };
  });

  return NextResponse.json(outcome);
}

// Vercel Cron invokes scheduled routes with GET; scripts/run-cron.ts (for
// VPS/manual use) uses POST. Both are accepted and do the same thing.
export const GET = POST;
