/**
 * News Scout Pipeline — Phase 5: AI Image Generation
 *
 * Generates dramatic, attention-grabbing images for curated stories using
 * kie.ai API (Grok Imagine). Grok writes the image prompts from headlines
 * to ensure relevance. Canvas cards as fallback if kie.ai fails.
 *
 * ALL curated stories get custom AI-generated images — article photos from RSS
 * are only used for non-curated sidebar items.
 */

import { createCanvas, registerFont } from 'canvas';
import * as crypto from 'crypto';
import * as fs from 'fs';
import * as path from 'path';
import sharp from 'sharp';
import pool from '../../db';
import { ScoutCandidate } from './types';
import { generateKieImage } from '../../services/kie-images';

const OUTPUT_DIR = '/var/www/html/rainmaker/public/news-cards';
const MAX_GRAPHICS = 12;
const CLEANUP_DAYS = 7;

const GROK_API_KEY = process.env.GROK_API_KEY || '';
const GROK_API_URL = process.env.GROK_API_URL || 'https://api.x.ai/v1/chat/completions';
const GROK_MODEL = process.env.GROK_MODEL || 'grok-4-1-fast-reasoning';

// Sport visual themes for prompt building
const SPORT_THEMES: Record<string, string> = {
  nfl: 'American football, NFL stadium, gridiron, helmets, intense competition',
  nba: 'basketball, NBA arena, slam dunks, court action, bright arena lights',
  mlb: 'baseball, MLB diamond, pitcher mound, batting, green field',
  nhl: 'ice hockey, NHL rink, puck, skating, cold blue arena lighting',
  soccer: 'soccer, football pitch, striking the ball, packed stadium, green grass',
  mma: 'MMA octagon, UFC fight, dramatic combat, cage, spotlights',
  ncaab: 'college basketball, March Madness atmosphere, rowdy student section',
  ncaaf: 'college football, packed stadium, rivalry game energy',
};

/**
 * Ask Grok to write a FLUX image prompt from a headline + sport context.
 * Returns a short, vivid scene description optimized for image generation.
 */
async function generateImagePrompt(
  headline: string,
  sport: string,
  summary?: string,
): Promise<string> {
  if (!GROK_API_KEY) {
    return buildFallbackPrompt(headline, sport);
  }

  try {
    const res = await fetch(GROK_API_URL, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${GROK_API_KEY}`,
      },
      body: JSON.stringify({
        model: GROK_MODEL,
        messages: [
          {
            role: 'system',
            content: `You write image generation prompts for a sports news site called Rainmaker.
Given a headline and sport, write ONE short image prompt (max 80 words) that would create a dramatic,
cinematic, attention-grabbing photo for the story. Think ESPN magazine cover meets movie poster.

Rules:
- Describe the SCENE, not the text/headline
- Use vivid cinematic language: lighting, camera angles, atmosphere
- Make it provocative and eye-catching — controversy sells
- Include the sport's visual elements (equipment, venues, uniforms)
- Reference specific players/people by appearance if named in headline (do NOT put text/names in the image)
- NEVER include text, logos, watermarks, or words in the image description
- Return ONLY the prompt, no explanation`,
          },
          {
            role: 'user',
            content: `Headline: "${headline}"
Sport: ${sport}
${summary ? `Context: ${summary}` : ''}`,
          },
        ],
        temperature: 0.9,
        max_tokens: 150,
      }),
      signal: AbortSignal.timeout(10000),
    });

    if (!res.ok) throw new Error(`Grok ${res.status}`);
    const data: any = await res.json();
    const prompt = data.choices?.[0]?.message?.content?.trim();
    if (prompt && prompt.length > 20) return prompt;
  } catch (err: any) {
    console.error(`  [graphics] Grok prompt failed: ${err.message}`);
  }

  return buildFallbackPrompt(headline, sport);
}

function buildFallbackPrompt(headline: string, sport: string): string {
  const theme = SPORT_THEMES[sport] || 'sports arena, dramatic competition';
  return `Dramatic cinematic sports photography, ${theme}, intense action moment, dark moody atmosphere, volumetric stadium lighting, shallow depth of field, editorial magazine quality, inspired by: ${headline.slice(0, 100)}`;
}

// ── Canvas fallback (when kie.ai is unavailable) ──

const SPORT_COLORS: Record<string, string> = {
  nfl: '#013369', nba: '#C8102E', mlb: '#002D72', nhl: '#111111',
  soccer: '#00FF87', mma: '#D20A2E', ncaab: '#FF6B00', ncaaf: '#552583', general: '#4a9eff',
};

try {
  registerFont('/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf', {
    family: 'LiberationSans', weight: 'bold',
  });
} catch {}

function wrapText(ctx: any, text: string, maxWidth: number, maxLines: number): string[] {
  const words = text.split(' ');
  const lines: string[] = [];
  let currentLine = '';
  for (const word of words) {
    const testLine = currentLine ? `${currentLine} ${word}` : word;
    if (ctx.measureText(testLine).width > maxWidth && currentLine) {
      lines.push(currentLine);
      currentLine = word;
      if (lines.length >= maxLines) break;
    } else {
      currentLine = testLine;
    }
  }
  if (currentLine && lines.length < maxLines) lines.push(currentLine);
  return lines;
}

function generateCanvasCard(headline: string, sport: string, source: string): Buffer {
  const W = 1200, H = 630;
  const canvas = createCanvas(W, H);
  const ctx = canvas.getContext('2d');
  const accent = SPORT_COLORS[sport] || SPORT_COLORS.general;

  const grad = ctx.createLinearGradient(0, 0, 0, H);
  grad.addColorStop(0, '#0a0c12');
  grad.addColorStop(1, '#141821');
  ctx.fillStyle = grad;
  ctx.fillRect(0, 0, W, H);

  ctx.fillStyle = accent;
  ctx.fillRect(0, 0, W, 4);

  ctx.font = 'bold 20px LiberationSans, Arial, sans-serif';
  const pill = ctx.measureText(sport.toUpperCase()).width + 24;
  ctx.fillStyle = accent;
  ctx.beginPath();
  ctx.roundRect(60, 60, pill, 34, 6);
  ctx.fill();
  ctx.fillStyle = '#fff';
  ctx.fillText(sport.toUpperCase(), 72, 84);

  ctx.font = 'bold 52px LiberationSans, Arial, sans-serif';
  ctx.fillStyle = '#fff';
  const lines = wrapText(ctx, headline, W - 120, 3);
  lines.forEach((line, i) => ctx.fillText(line, 60, 160 + i * 64));

  ctx.font = 'bold 18px LiberationSans, Arial, sans-serif';
  ctx.fillStyle = 'rgba(255,255,255,0.4)';
  ctx.fillText(source.toUpperCase(), 60, H - 80);

  ctx.font = 'bold 24px LiberationSans, Arial, sans-serif';
  ctx.fillStyle = 'rgba(255,255,255,0.15)';
  const wm = 'RAINMAKER';
  ctx.fillText(wm, W - ctx.measureText(wm).width - 60, H - 40);

  ctx.fillStyle = accent;
  ctx.fillRect(60, H - 110, 80, 3);

  return canvas.toBuffer('image/png');
}

// ── Generate or retrieve image for a single item ──

async function generateImageForItem(
  guid: string,
  headlineText: string,
  sport: string,
  sourceDisplay: string,
  summary?: string,
): Promise<{ filename: string; method: 'cache' | 'kie' | 'canvas' } | null> {
  const hash = crypto.createHash('sha256').update(guid).digest('hex').slice(0, 12);
  const filename = `${hash}.webp`;
  const filepath = path.join(OUTPUT_DIR, filename);

  // Skip if already generated and fresh (<6h)
  if (fs.existsSync(filepath)) {
    const stat = fs.statSync(filepath);
    const ageHours = (Date.now() - stat.mtimeMs) / 3600000;
    if (ageHours < 6) {
      return { filename, method: 'cache' };
    }
  }

  try {
    // Step 1: Get AI image prompt from Grok
    const imagePrompt = await generateImagePrompt(headlineText, sport, summary);
    console.log(`  [graphics] Prompt for "${headlineText.slice(0, 40)}...": ${imagePrompt.slice(0, 80)}...`);

    // Step 2: Generate image via kie.ai
    const imgBuffer = await generateKieImage(imagePrompt, {
      category: 'news_curation',
      subcategory: 'image_generation',
      aspectRatio: '16:9',
    });

    if (imgBuffer) {
      const webpBuffer = await sharp(imgBuffer).resize({ width: 800, withoutEnlargement: true }).webp({ quality: 82 }).toBuffer();
      fs.writeFileSync(filepath, webpBuffer);
      console.log(`  [graphics] Generated via kie.ai: ${filename}`);
      return { filename, method: 'kie' };
    } else {
      // Last resort: Canvas card → WebP
      const canvasBuffer = generateCanvasCard(headlineText, sport, sourceDisplay);
      const webpBuffer = await sharp(canvasBuffer).webp({ quality: 80 }).toBuffer();
      fs.writeFileSync(filepath, webpBuffer);
      return { filename, method: 'canvas' };
    }
  } catch (err: any) {
    console.error(`  [graphics] Failed for "${headlineText.slice(0, 40)}": ${err.message}`);
    return null;
  }
}

// ── Main entry ──

export async function runGraphicsGenerator(candidates: ScoutCandidate[]): Promise<number> {
  // ALL curated stories get AI-generated images (override any RSS article photos)
  const toGenerate = candidates.filter(c => c.isCurated).slice(0, MAX_GRAPHICS);

  if (!fs.existsSync(OUTPUT_DIR)) {
    fs.mkdirSync(OUTPUT_DIR, { recursive: true });
  }

  let generated = 0;
  let kieCount = 0;
  let canvasCount = 0;

  for (const candidate of toGenerate) {
    const result = await generateImageForItem(
      candidate.guid,
      candidate.customHeadline || candidate.title,
      candidate.sport,
      candidate.sourceDisplay,
      candidate.customSummary,
    );

    if (result) {
      candidate.imageUrl = `/api/news-cards/${result.filename}`;
      generated++;
      if (result.method === 'kie') kieCount++;
      if (result.method === 'canvas') canvasCount++;
    }
  }

  // Cleanup old images (>7 days)
  try {
    const cutoff = Date.now() - CLEANUP_DAYS * 24 * 60 * 60 * 1000;
    const files = fs.readdirSync(OUTPUT_DIR);
    let cleaned = 0;
    for (const file of files) {
      if (!file.endsWith('.webp') && !file.endsWith('.png')) continue;
      const stat = fs.statSync(path.join(OUTPUT_DIR, file));
      if (stat.mtimeMs < cutoff) {
        fs.unlinkSync(path.join(OUTPUT_DIR, file));
        cleaned++;
      }
    }
    if (cleaned > 0) console.log(`  [graphics] Cleaned up ${cleaned} old images`);
  } catch (err: any) {
    console.error(`  [graphics] Cleanup error: ${err.message}`);
  }

  console.log(`[graphics] Generated ${generated} images (${kieCount} kie.ai, ${canvasCount} Canvas fallback)`);
  return generated;
}

/**
 * Backfill images for curated items in the DB that are missing image_url.
 * Called AFTER the main upsert so it catches items from previous runs
 * that fell out of the candidate list before getting images.
 */
export async function backfillCuratedImages(maxItems: number = 20): Promise<number> {
  if (!fs.existsSync(OUTPUT_DIR)) {
    fs.mkdirSync(OUTPUT_DIR, { recursive: true });
  }

  // Find curated items missing images, ordered by engagement (most visible first)
  const { rows } = await pool.query(`
    SELECT id, guid, COALESCE(custom_headline, title) AS headline, sport, source_display, custom_summary
    FROM rm_news_links
    WHERE is_curated = TRUE AND expires_at > NOW() AND image_url IS NULL
    ORDER BY engagement_score DESC
    LIMIT $1
  `, [maxItems]);

  if (rows.length === 0) {
    console.log('[graphics-backfill] All curated items have images');
    return 0;
  }

  console.log(`[graphics-backfill] Found ${rows.length} curated items missing images`);

  let filled = 0;
  for (const row of rows) {
    const result = await generateImageForItem(
      row.guid,
      row.headline,
      row.sport,
      row.source_display,
      row.custom_summary,
    );

    if (result) {
      const imageUrl = `/api/news-cards/${result.filename}`;
      await pool.query('UPDATE rm_news_links SET image_url = $1 WHERE id = $2', [imageUrl, row.id]);
      filled++;
    }
  }

  console.log(`[graphics-backfill] Backfilled ${filled}/${rows.length} images`);
  return filled;
}
