import 'server-only';
import Parser from 'rss-parser';
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { prisma } from '../db/client';
import { rewriteNewsExcerpt } from '../content/generator';

// Sources are config, not code — see config/rss-sources.json. This keeps
// "add a feed" a one-line JSON edit instead of a deploy, consistent with
// the rest of the CMS-less design.
//
// IMPORTANT: the feed URLs shipped in config/rss-sources.json are
// best-effort and were not fetched live while building this project (this
// environment has no outbound network access). Verify each URL resolves
// and still serves the expected feed before relying on it in production —
// publishers do move/retire RSS endpoints.

interface RssSourceConfig {
  name: string;
  url: string;
}

const parser = new Parser();

async function loadSources(): Promise<RssSourceConfig[]> {
  const raw = await readFile(path.join(process.cwd(), 'config', 'rss-sources.json'), 'utf-8');
  const parsed = JSON.parse(raw) as { sources: RssSourceConfig[] };
  return parsed.sources;
}

/** Very small, dependency-free keyword matcher: does this headline/summary
 * plausibly mention a known game? Good enough for phase 1 — swap for a
 * proper NER/entity-linking pass if false-positive/negative rates matter
 * more at scale (see README > Phase 2). */
async function findMentionedGameIds(text: string): Promise<string[]> {
  const lower = text.toLowerCase();
  // Only match against games we already track, in reasonably sized
  // batches, to avoid an unbounded table scan holding the text in memory.
  const candidates = await prisma.game.findMany({
    where: { status: { in: ['PUBLISHED', 'NEEDS_REFRESH', 'DISCOVERED'] } },
    select: { id: true, name: true },
    take: 5000,
  });
  return candidates.filter((g) => lower.includes(g.name.toLowerCase())).map((g) => g.id);
}

export interface FetchRssResult {
  itemsSeen: number;
  itemsStored: number;
  itemsLinked: number;
  errors: string[];
}

export async function fetchAndIngestRss(maxItemsPerFeed = 20): Promise<FetchRssResult> {
  const sources = await loadSources();
  const result: FetchRssResult = { itemsSeen: 0, itemsStored: 0, itemsLinked: 0, errors: [] };

  for (const source of sources) {
    try {
      const feed = await parser.parseURL(source.url);
      const items = (feed.items ?? []).slice(0, maxItemsPerFeed);

      for (const item of items) {
        result.itemsSeen++;
        if (!item.link || !item.title) continue;

        const exists = await prisma.newsItem.findUnique({ where: { sourceUrl: item.link } });
        if (exists) continue;

        // contentSnippet/summary is the ONLY thing we keep, and only as an
        // internal "factual gist" input for rewriting — see rewriteNewsExcerpt.
        // We deliberately do not fetch or store the full article body.
        const gist = (item.contentSnippet ?? item.title).slice(0, 500);
        const rewritten = await rewriteNewsExcerpt(item.title, gist).catch(() => null);

        const newsItem = await prisma.newsItem.create({
          data: {
            sourceUrl: item.link,
            sourceName: source.name,
            title: item.title,
            publishedAt: item.isoDate ? new Date(item.isoDate) : new Date(),
            rawExcerpt: gist,
            rewrittenSummary: rewritten,
          },
        });
        result.itemsStored++;

        const gameIds = await findMentionedGameIds(`${item.title} ${gist}`);
        if (gameIds.length > 0) {
          await prisma.gameNewsItem.createMany({
            data: gameIds.map((gameId) => ({ gameId, newsItemId: newsItem.id })),
            skipDuplicates: true,
          });
          // A game with fresh news is worth a content refresh even if its
          // core metadata hash hasn't changed.
          await prisma.game.updateMany({
            where: { id: { in: gameIds }, status: 'PUBLISHED' },
            data: { status: 'NEEDS_REFRESH' },
          });
          result.itemsLinked += gameIds.length;
        }
      }
    } catch (err) {
      result.errors.push(`${source.name}: ${err instanceof Error ? err.message : String(err)}`);
    }
  }

  return result;
}
