/**
 * News Scout Pipeline — Orchestrator
 *
 * Runs all phases sequentially:
 *   RSS → Google News → Reddit → Twitter → Score → Curate → Graphics
 * Each phase in try/catch so failures don't kill other results.
 * Bulk upserts candidates into rm_news_links via ON CONFLICT (guid) DO UPDATE.
 *
 * Usage: npx tsx src/workers/news-scouts/index.ts
 */

import 'dotenv/config';
import pool from '../../db';
import { ScoutCandidate, ScoutRunCounters } from './types';
import { runRssScout } from './rss-scout';
import { runGnewsScout } from './gnews-scout';
import { runRedditScout } from './reddit-scout';
import { runTwitterScout } from './twitter-scout';
import { runTrendingScorer } from './trending-scorer';
import { runCurator } from './curator';
import { runGraphicsGenerator, backfillCuratedImages } from './graphics-generator';

const EXPIRE_DAYS = 7;
const ENABLE_TWITTER_SCOUT = process.env.RM_ENABLE_TWITTER_SCOUT !== 'false';

async function createScoutRun(): Promise<string> {
  const result = await pool.query(
    `INSERT INTO rm_news_scout_runs (status) VALUES ('RUNNING') RETURNING id`
  );
  return result.rows[0].id;
}

async function updateScoutRun(
  runId: string,
  status: 'COMPLETED' | 'FAILED',
  counters: ScoutRunCounters,
  durationMs: number,
  errorMessage?: string
) {
  await pool.query(
    `UPDATE rm_news_scout_runs SET
       finished_at = NOW(), status = $2,
       rss_found = $3, rss_upserted = $4,
       twitter_found = $5, twitter_upserted = $6,
       gnews_found = $7, gnews_upserted = $8,
       reddit_found = $9, reddit_upserted = $10,
       items_scored = $11, items_curated = $12,
       graphics_generated = $13, duration_ms = $14, error_message = $15
     WHERE id = $1`,
    [runId, status, counters.rssFound, counters.rssUpserted,
     counters.twitterFound, counters.twitterUpserted,
     counters.gnewsFound, counters.gnewsUpserted,
     counters.redditFound, counters.redditUpserted,
     counters.itemsScored, counters.itemsCurated,
     counters.graphicsGenerated, durationMs, errorMessage || null]
  );
}

async function upsertCandidates(candidates: ScoutCandidate[], runId: string): Promise<number> {
  let upserted = 0;

  for (const c of candidates) {
    const expiresAt = new Date(c.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,
           image_url, custom_headline, custom_summary, engagement_score,
           celebrity_names, source_type, is_curated, is_featured, is_breaking,
           scout_run_id
         ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW(), $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)
         ON CONFLICT (guid) DO UPDATE SET
           title = EXCLUDED.title,
           description = EXCLUDED.description,
           fetched_at = NOW(),
           image_url = CASE
             WHEN EXCLUDED.image_url IS NOT NULL THEN EXCLUDED.image_url
             ELSE rm_news_links.image_url
           END,
           custom_headline = COALESCE(EXCLUDED.custom_headline, rm_news_links.custom_headline),
           custom_summary = COALESCE(EXCLUDED.custom_summary, rm_news_links.custom_summary),
           engagement_score = GREATEST(EXCLUDED.engagement_score, rm_news_links.engagement_score),
           celebrity_names = COALESCE(EXCLUDED.celebrity_names, rm_news_links.celebrity_names),
           source_type = EXCLUDED.source_type,
           is_curated = EXCLUDED.is_curated OR rm_news_links.is_curated,
           is_featured = EXCLUDED.is_featured OR rm_news_links.is_featured,
           is_breaking = EXCLUDED.is_breaking OR rm_news_links.is_breaking,
           scout_run_id = EXCLUDED.scout_run_id`,
        [
          c.title, c.url, c.source, c.sourceDisplay, c.sport, c.description, c.guid,
          c.publishedAt, expiresAt,
          c.imageUrl || null, c.customHeadline || null, c.customSummary || null,
          c.engagementScore,
          c.celebrityNames && c.celebrityNames.length > 0 ? c.celebrityNames : null,
          c.sourceType, c.isCurated, c.isFeatured, c.isBreaking,
          runId,
        ]
      );
      upserted++;
    } catch (err: any) {
      console.error(`  Skip upsert "${c.title.slice(0, 50)}": ${err.message}`);
    }
  }

  return upserted;
}

async function updateTrendingScores() {
  // Enhanced trending score: clicks + engagement_score + recency boost
  await pool.query(`
    UPDATE rm_news_links
    SET trending_score = clicks + COALESCE(engagement_score, 0) +
      (1000.0 / GREATEST(EXTRACT(EPOCH FROM (NOW() - published_at)) / 3600.0, 1))
    WHERE published_at > NOW() - INTERVAL '48 hours'
  `);
}

async function cleanupExpired(): Promise<number> {
  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 SCOUT PIPELINE');
  console.log(`Date: ${new Date().toISOString()}`);
  console.log('='.repeat(60));

  const counters: ScoutRunCounters = {
    rssFound: 0, rssUpserted: 0,
    twitterFound: 0, twitterUpserted: 0,
    gnewsFound: 0, gnewsUpserted: 0,
    redditFound: 0, redditUpserted: 0,
    itemsScored: 0, itemsCurated: 0,
    graphicsGenerated: 0,
  };

  let runId: string;
  try {
    runId = await createScoutRun();
  } catch (err: any) {
    console.error('Failed to create scout run:', err.message);
    await pool.end();
    process.exit(1);
  }

  let allCandidates: ScoutCandidate[] = [];

  // Phase 1: RSS (~28 feeds)
  console.log('\n--- Phase 1: RSS Scout ---');
  try {
    const rssCandidates = await runRssScout();
    counters.rssFound = rssCandidates.length;
    allCandidates.push(...rssCandidates);
  } catch (err: any) {
    console.error('RSS scout phase failed:', err.message);
  }

  // Phase 1b: Google News (10 queries)
  console.log('\n--- Phase 1b: Google News Scout ---');
  try {
    const gnewsCandidates = await runGnewsScout();
    counters.gnewsFound = gnewsCandidates.length;
    allCandidates.push(...gnewsCandidates);
  } catch (err: any) {
    console.error('Google News scout phase failed (non-fatal):', err.message);
  }

  // Phase 1c: Reddit (8 subreddits)
  console.log('\n--- Phase 1c: Reddit Scout ---');
  try {
    const redditCandidates = await runRedditScout();
    counters.redditFound = redditCandidates.length;
    allCandidates.push(...redditCandidates);
  } catch (err: any) {
    console.error('Reddit scout phase failed (non-fatal):', err.message);
  }

  // Phase 2: Twitter (graceful 402 handling)
  console.log('\n--- Phase 2: Twitter Scout ---');
  if (!ENABLE_TWITTER_SCOUT) {
    console.log('Twitter scout disabled via RM_ENABLE_TWITTER_SCOUT=false');
  } else {
    try {
      const twitterCandidates = await runTwitterScout();
      counters.twitterFound = twitterCandidates.length;
      allCandidates.push(...twitterCandidates);
    } catch (err: any) {
      console.error('Twitter scout phase failed (non-fatal):', err.message);
    }
  }

  // Phase 3: Scoring (credibility + cross-source)
  console.log('\n--- Phase 3: Trending Scorer ---');
  try {
    allCandidates = await runTrendingScorer(allCandidates);
    counters.itemsScored = allCandidates.length;
  } catch (err: any) {
    console.error('Scoring phase failed:', err.message);
  }

  // Phase 4: Curation (Grok AI)
  console.log('\n--- Phase 4: Curator (Grok AI) ---');
  try {
    allCandidates = await runCurator(allCandidates);
    counters.itemsCurated = allCandidates.filter(c => c.isCurated).length;
  } catch (err: any) {
    console.error('Curation phase failed (non-fatal):', err.message);
  }

  // Phase 5: Graphics
  console.log('\n--- Phase 5: Graphics Generator ---');
  try {
    counters.graphicsGenerated = await runGraphicsGenerator(allCandidates);
  } catch (err: any) {
    console.error('Graphics phase failed (non-fatal):', err.message);
  }

  // Bulk upsert all candidates
  console.log('\n--- Upserting candidates ---');
  const upserted = await upsertCandidates(allCandidates, runId);

  // Calculate upserted per source type proportionally
  const rssTotal = allCandidates.filter(c => c.sourceType === 'rss').length;
  const twitterTotal = allCandidates.filter(c => c.sourceType === 'twitter').length;
  const gnewsTotal = allCandidates.filter(c => c.sourceType === 'gnews').length;
  const redditTotal = allCandidates.filter(c => c.sourceType === 'reddit').length;
  const total = rssTotal + twitterTotal + gnewsTotal + redditTotal || 1;
  counters.rssUpserted = Math.round((rssTotal / total) * upserted);
  counters.twitterUpserted = Math.round((twitterTotal / total) * upserted);
  counters.gnewsUpserted = Math.round((gnewsTotal / total) * upserted);
  counters.redditUpserted = Math.round((redditTotal / total) * upserted);
  console.log(`Upserted ${upserted} total (${counters.rssUpserted} RSS, ${counters.gnewsUpserted} GNews, ${counters.redditUpserted} Reddit, ${counters.twitterUpserted} Twitter)`);

  // Backfill images for curated items from previous runs that are missing images
  console.log('\n--- Phase 6: Graphics Backfill ---');
  try {
    await backfillCuratedImages(20);
  } catch (err: any) {
    console.error('Graphics backfill failed (non-fatal):', err.message);
  }

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

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

  const durationMs = Date.now() - startTime;
  await updateScoutRun(runId, 'COMPLETED', counters, durationMs);

  console.log(`\n${'='.repeat(60)}`);
  console.log('RESULTS:');
  console.log(`  RSS:     ${counters.rssFound} found, ${counters.rssUpserted} upserted`);
  console.log(`  GNews:   ${counters.gnewsFound} found, ${counters.gnewsUpserted} upserted`);
  console.log(`  Reddit:  ${counters.redditFound} found, ${counters.redditUpserted} upserted`);
  console.log(`  Twitter: ${counters.twitterFound} found, ${counters.twitterUpserted} upserted`);
  console.log(`  Scored:  ${counters.itemsScored}`);
  console.log(`  Curated: ${counters.itemsCurated}`);
  console.log(`  Graphics: ${counters.graphicsGenerated}`);
  console.log(`  Duration: ${(durationMs / 1000).toFixed(1)}s`);
  console.log('='.repeat(60));

  await pool.end();
}

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