/**
 * News Aggregator Worker
 *
 * Fetches RSS feeds from major sports news sources, upserts into rm_news_links,
 * updates trending scores, and cleans up expired rows (7-day TTL).
 *
 * Usage: npx tsx src/workers/news-aggregator.ts
 */

import 'dotenv/config';
import pool from '../db';
import RSSParser from 'rss-parser';

const parser = new RSSParser({
  timeout: 15000,
  headers: {
    'User-Agent': 'Rainmaker/1.0 (Sports News Aggregator)',
  },
});

interface FeedConfig {
  url: string;
  source: string;
  sourceDisplay: string;
  sport: string;
}

const FEEDS: FeedConfig[] = [
  // ESPN
  { url: 'https://www.espn.com/espn/rss/nfl/news', source: 'espn', sourceDisplay: 'ESPN', sport: 'nfl' },
  { url: 'https://www.espn.com/espn/rss/nba/news', source: 'espn', sourceDisplay: 'ESPN', sport: 'nba' },
  { url: 'https://www.espn.com/espn/rss/mlb/news', source: 'espn', sourceDisplay: 'ESPN', sport: 'mlb' },
  { url: 'https://www.espn.com/espn/rss/nhl/news', source: 'espn', sourceDisplay: 'ESPN', sport: 'nhl' },
  { url: 'https://www.espn.com/espn/rss/mma/news', source: 'espn', sourceDisplay: 'ESPN', sport: 'mma' },
  { url: 'https://www.espn.com/espn/rss/soccer/news', source: 'espn', sourceDisplay: 'ESPN', sport: 'soccer' },
];

const EXPIRE_DAYS = 7;

function sleep(ms: number) {
  return new Promise(r => setTimeout(r, ms));
}

async function fetchFeed(feed: FeedConfig): Promise<number> {
  let upserted = 0;
  try {
    const parsed = await parser.parseURL(feed.url);
    const items = parsed.items || [];

    for (const item of items) {
      const title = (item.title || '').trim();
      const url = (item.link || '').trim();
      const guid = (item.guid || item.link || '').trim();
      const description = (item.contentSnippet || item.content || '').trim().slice(0, 500);
      const publishedAt = item.pubDate ? new Date(item.pubDate) : new Date();

      if (!title || !url || !guid) continue;

      const expiresAt = new Date(publishedAt.getTime() + EXPIRE_DAYS * 24 * 60 * 60 * 1000);

      try {
        await pool.query(
          `INSERT INTO rm_news_links (title, url, source, source_display, sport, description, guid, published_at, expires_at, fetched_at)
           VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW())
           ON CONFLICT (guid) DO UPDATE SET
             title = EXCLUDED.title,
             description = EXCLUDED.description,
             fetched_at = NOW()`,
          [title, url, feed.source, feed.sourceDisplay, feed.sport, description, guid, publishedAt, expiresAt]
        );
        upserted++;
      } catch (err: any) {
        // Skip individual item errors (e.g. constraint violations)
        console.error(`  Skip item "${title.slice(0, 50)}": ${err.message}`);
      }
    }
  } catch (err: any) {
    console.error(`  Feed error [${feed.source}/${feed.sport}]: ${err.message}`);
  }
  return upserted;
}

async function updateTrendingScores() {
  // trending_score = clicks + (1000 / hours_since_published)
  // Gives recency boost that decays over time
  await pool.query(`
    UPDATE rm_news_links
    SET trending_score = clicks + (1000.0 / GREATEST(EXTRACT(EPOCH FROM (NOW() - published_at)) / 3600.0, 1))
    WHERE published_at > NOW() - INTERVAL '48 hours'
  `);
}

async function cleanupExpired() {
  const result = await pool.query(`
    DELETE FROM rm_news_links WHERE expires_at < NOW()
  `);
  return result.rowCount || 0;
}

async function main() {
  const startTime = Date.now();
  console.log('='.repeat(60));
  console.log('RAINMAKER NEWS AGGREGATOR');
  console.log(`Date: ${new Date().toISOString()}`);
  console.log('='.repeat(60));

  let totalUpserted = 0;

  for (const feed of FEEDS) {
    console.log(`\nFetching ${feed.sourceDisplay} ${feed.sport.toUpperCase()}...`);
    const count = await fetchFeed(feed);
    console.log(`  Upserted ${count} items`);
    totalUpserted += count;
    // Small delay between feeds to be polite
    await sleep(500);
  }

  console.log(`\nTotal upserted: ${totalUpserted}`);

  // Update trending scores
  console.log('\nUpdating trending scores...');
  await updateTrendingScores();
  console.log('  Done');

  // Cleanup expired
  console.log('\nCleaning up expired rows...');
  const cleaned = await cleanupExpired();
  console.log(`  Removed ${cleaned} expired rows`);

  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
  console.log(`\nCompleted in ${elapsed}s`);
  console.log('='.repeat(60));

  await pool.end();
}

main().catch(err => {
  console.error('Fatal error:', err);
  process.exit(1);
});
