/**
 * Rainmaker Twitter Bot — Content Generator V2
 *
 * Pulls forecasts from rm_forecast_cache and PIFF 3.0 data
 * to generate tweet content via Grok.
 *
 * V2 additions: threads, momentum, live edge alerts, CTA,
 * prime-time reminders, educational content.
 */
import pool from '../db/index';
import {
  callGrok,
  forecastPickPrompt,
  forecastThreadPrompt,
  piffPropPrompt,
  dailySlatePrompt,
  recapPrompt,
  momentumPrompt,
  liveEdgePrompt,
  primeTimePrompt,
  ctaPrompt,
  educationalPrompt,
} from './twitter-prompts';
import {
  isDuplicateForecast,
  isDuplicateContent,
  getRecentlySettledForecasts,
  getForecastRecordSince,
  getCurrentWinStreak,
  getRecentDayRecord,
  getLastNRecord,
  hasAlertedGame,
} from './twitter-data-queries';
import type { TweetContent, GrokTweetResponse, GrokThreadResponse } from './types';

import { getTopPiffLegs, refreshPiffCache, type PiffLeg } from './piff-loader';

// ── Helpers ─────────────────────────────────────────────────

async function passesQualityGates(text: string): Promise<boolean> {
  if (!text || text.length === 0 || text.length > 4000) return false;
  if (await isDuplicateContent(text)) {
    console.log('[rm-content] Duplicate content, skipping');
    return false;
  }
  return true;
}

function getDayOfYear(): number {
  const now = new Date();
  const start = new Date(now.getFullYear(), 0, 0);
  const diff = now.getTime() - start.getTime();
  return Math.floor(diff / (1000 * 60 * 60 * 24));
}

// ── Shared: Fetch Top Forecasts ─────────────────────────────

async function fetchUpcomingForecasts(limit: number = 8, minHoursAhead: number = 0, maxHoursAhead: number = 24) {
  return pool.query(`
    SELECT id, league, home_team, away_team, starts_at, composite_confidence,
           forecast_data->>'summary' as summary,
           (odds_data->'spread'->'home'->>'line')::numeric as spread,
           (odds_data->'total'->'over'->>'line')::numeric as total,
           (odds_data->'moneyline'->>'home')::numeric as home_ml,
           (odds_data->'moneyline'->>'away')::numeric as away_ml
    FROM rm_forecast_cache
    WHERE starts_at >= NOW() + INTERVAL '${minHoursAhead} hours'
      AND starts_at <= NOW() + INTERVAL '${maxHoursAhead} hours'
      AND composite_confidence IS NOT NULL
    ORDER BY composite_confidence DESC
    LIMIT ${limit}
  `);
}

// ═══════════════════════════════════════════════════════════════
// ORIGINAL GENERATORS (kept for backwards compat)
// ═══════════════════════════════════════════════════════════════

// ── Forecast Picks (standalone tweets — fallback) ───────────

export async function generateForecastPicks(): Promise<TweetContent[]> {
  const tweets: TweetContent[] = [];

  try {
    const result = await fetchUpcomingForecasts(8);
    console.log(`[rm-content] Found ${result.rows.length} forecasts for today`);

    for (const row of result.rows) {
      if (tweets.length >= 4) break;
      if (await isDuplicateForecast(row.id)) {
        console.log(`[rm-content] Skipping duplicate forecast ${row.id}`);
        continue;
      }
      if (!row.summary) continue;

      try {
        const prompt = forecastPickPrompt({
          league: row.league,
          homeTeam: row.home_team,
          awayTeam: row.away_team,
          startsAt: row.starts_at,
          summary: row.summary,
          confidence: row.composite_confidence,
          spread: row.spread,
          total: row.total,
          homeML: row.home_ml,
          awayML: row.away_ml,
        });

        const grokResult = await callGrok<GrokTweetResponse>(prompt);

        if (grokResult?.text && await passesQualityGates(grokResult.text)) {
          tweets.push({
            text: grokResult.text,
            contentType: 'forecast_pick',
            league: row.league,
            forecastId: row.id,
            gameKey: `${row.league}:${row.away_team}@${row.home_team}`,
            pickDirection: grokResult.pickSide,
          });
          console.log(`[rm-content] Generated forecast tweet: ${row.away_team} @ ${row.home_team} (${row.league}, ${Math.round(row.composite_confidence * 100)}%)`);
        }
      } catch (err) {
        console.error(`[rm-content] Forecast pick failed for ${row.home_team}:`, (err as Error).message);
      }
    }
  } catch (err) {
    console.error('[rm-content] Failed to load forecasts:', (err as Error).message);
  }

  console.log(`[rm-content] Generated ${tweets.length} forecast pick tweets`);
  return tweets;
}

// ═══════════════════════════════════════════════════════════════
// NEW V2 GENERATORS
// ═══════════════════════════════════════════════════════════════

// ── Forecast Thread (anchor + replies) ──────────────────────

export async function generateForecastThread(): Promise<TweetContent[]> {
  const tweets: TweetContent[] = [];

  try {
    const result = await fetchUpcomingForecasts(4);
    if (result.rows.length === 0) {
      console.log('[rm-content] No forecasts for thread');
      return tweets;
    }

    // Pick the highest-confidence game that hasn't been tweeted
    let chosen = null;
    for (const row of result.rows) {
      if (await isDuplicateForecast(row.id)) continue;
      if (!row.summary) continue;
      chosen = row;
      break;
    }

    if (!chosen) {
      console.log('[rm-content] All top forecasts already tweeted, falling back to standalone');
      return generateForecastPicks();
    }

    const prompt = forecastThreadPrompt({
      league: chosen.league,
      homeTeam: chosen.home_team,
      awayTeam: chosen.away_team,
      startsAt: chosen.starts_at,
      summary: chosen.summary,
      confidence: chosen.composite_confidence,
      spread: chosen.spread,
      total: chosen.total,
      homeML: chosen.home_ml,
      awayML: chosen.away_ml,
    });

    const grokResult = await callGrok<GrokThreadResponse>(prompt, 2, 1200);

    if (grokResult?.tweets && grokResult.tweets.length >= 2) {
      for (let i = 0; i < grokResult.tweets.length; i++) {
        const t = grokResult.tweets[i];
        if (t.text && await passesQualityGates(t.text)) {
          tweets.push({
            text: t.text,
            contentType: 'forecast_thread',
            league: chosen.league,
            forecastId: i === 0 ? chosen.id : undefined,
            gameKey: `${chosen.league}:${chosen.away_team}@${chosen.home_team}`,
            pickDirection: t.pickSide,
          });
        }
      }
      console.log(`[rm-content] Generated forecast thread: ${tweets.length} tweets for ${chosen.away_team} @ ${chosen.home_team}`);
    } else {
      // Fallback: Grok returned single tweet or bad format, use standalone
      console.log('[rm-content] Thread generation returned bad format, falling back to standalone');
      const fallback = await callGrok<GrokTweetResponse>(forecastPickPrompt({
        league: chosen.league,
        homeTeam: chosen.home_team,
        awayTeam: chosen.away_team,
        startsAt: chosen.starts_at,
        summary: chosen.summary,
        confidence: chosen.composite_confidence,
        spread: chosen.spread,
        total: chosen.total,
        homeML: chosen.home_ml,
        awayML: chosen.away_ml,
      }));

      if (fallback?.text && await passesQualityGates(fallback.text)) {
        tweets.push({
          text: fallback.text,
          contentType: 'forecast_pick',
          league: chosen.league,
          forecastId: chosen.id,
          gameKey: `${chosen.league}:${chosen.away_team}@${chosen.home_team}`,
          pickDirection: fallback.pickSide,
        });
      }
    }
  } catch (err) {
    console.error('[rm-content] Forecast thread failed:', (err as Error).message);
  }

  return tweets;
}

// ── PIFF Prop Picks ─────────────────────────────────────────

export async function generatePiffPropPicks(): Promise<TweetContent[]> {
  const tweets: TweetContent[] = [];

  try {
    refreshPiffCache();
    const topLegs = getTopPiffLegs(8);

    if (topLegs.length === 0) {
      console.log('[rm-content] No PIFF T1/T2 legs available');
      return tweets;
    }

    console.log(`[rm-content] PIFF: Found ${topLegs.length} T1/T2 legs`);

    const gamesResult = await pool.query(`
      SELECT home_team, away_team, league, starts_at
      FROM rm_forecast_cache
      WHERE starts_at >= NOW() AND starts_at <= NOW() + INTERVAL '24 hours'
    `);
    const gamesByTeam = new Map<string, { opponent: string; league: string; gameDate: string }>();
    for (const g of gamesResult.rows) {
      gamesByTeam.set(g.home_team?.toUpperCase(), { opponent: g.away_team, league: g.league, gameDate: g.starts_at });
      gamesByTeam.set(g.away_team?.toUpperCase(), { opponent: g.home_team, league: g.league, gameDate: g.starts_at });
    }

    for (const leg of topLegs) {
      if (tweets.length >= 3) break;

      try {
        const teamKey = leg.team.toUpperCase();
        const gameInfo = gamesByTeam.get(teamKey);

        const prompt = piffPropPrompt({
          name: leg.name,
          team: leg.team,
          stat: leg.stat,
          line: leg.line,
          direction: leg.direction || 'over',
          tier_label: leg.tier_label || 'T2_STRONG',
          edge: leg.edge,
          prob: leg.prob,
          dvp_tier: leg.dvp_tier,
          league: leg.league || gameInfo?.league,
          opponent: leg.opponent || gameInfo?.opponent,
          gameDate: gameInfo?.gameDate,
        });

        const grokResult = await callGrok<GrokTweetResponse>(prompt);

        if (grokResult?.text && await passesQualityGates(grokResult.text)) {
          tweets.push({
            text: grokResult.text,
            contentType: 'piff_prop',
            league: leg.league || gameInfo?.league,
            gameKey: `prop:${leg.team}:${leg.name}:${leg.stat}`,
            pickDirection: leg.direction,
          });
          console.log(`[rm-content] PIFF prop: ${leg.name} ${leg.direction} ${leg.line} ${leg.stat} [${leg.tier_label}] +${(leg.edge * 100).toFixed(0)}%`);
        }
      } catch (err) {
        console.error(`[rm-content] PIFF prop failed for ${leg.name}:`, (err as Error).message);
      }
    }
  } catch (err) {
    console.error('[rm-content] PIFF prop generation failed:', (err as Error).message);
  }

  console.log(`[rm-content] Generated ${tweets.length} PIFF prop tweets`);
  return tweets;
}

// ── Daily Slate Overview (UPGRADED) ─────────────────────────

export async function generateDailySlate(): Promise<TweetContent[]> {
  const tweets: TweetContent[] = [];

  try {
    const result = await fetchUpcomingForecasts(6);

    if (result.rows.length < 2) {
      console.log('[rm-content] Not enough forecasts for daily slate');
      return tweets;
    }

    const prompt = dailySlatePrompt(result.rows.map(r => ({
      league: r.league,
      homeTeam: r.home_team,
      awayTeam: r.away_team,
      confidence: r.composite_confidence,
      summary: r.summary || '',
    })));

    const grokResult = await callGrok<GrokTweetResponse>(prompt);

    if (grokResult?.text && await passesQualityGates(grokResult.text)) {
      tweets.push({
        text: grokResult.text,
        contentType: 'daily_slate',
      });
      console.log(`[rm-content] Generated daily slate tweet (${result.rows.length} games)`);
    }
  } catch (err) {
    console.error('[rm-content] Daily slate failed:', (err as Error).message);
  }

  return tweets;
}

// ── Daily Forecast Recap (UPGRADED with momentum) ───────────

export async function generateDailyRecap(): Promise<TweetContent[]> {
  const tweets: TweetContent[] = [];

  try {
    const settled = await getRecentlySettledForecasts();
    if (settled.length === 0) {
      console.log('[rm-content] No recently settled forecasts for recap');
      return tweets;
    }

    const record = await getForecastRecordSince('2026-02-28');
    const streak = await getCurrentWinStreak();

    const results = settled.map(f => {
      let homeScore = 0, awayScore = 0;
      if (f.actual_score) {
        const parts = f.actual_score.match(/(\d+)\s*[-–]\s*(\d+)/);
        if (parts) {
          awayScore = parseInt(parts[1]);
          homeScore = parseInt(parts[2]);
        }
      }
      return {
        league: f.league,
        homeTeam: f.home_team,
        awayTeam: f.away_team,
        homeScore,
        awayScore,
        ourCall: f.winner_pick || undefined,
        hit: f.outcome === 'win',
      };
    });

    const prompt = recapPrompt(results.slice(0, 8), record, streak);

    const grokResult = await callGrok<GrokTweetResponse>(prompt);

    if (grokResult?.text && await passesQualityGates(grokResult.text)) {
      let text = grokResult.text;
      // Append overall record if we have enough data and it's not already in the text
      if (record.total >= 3 && !text.includes(`${record.wins}-${record.losses}`)) {
        text += `\n\nSeason record: ${record.wins}-${record.losses} (${record.winPct}%)`;
      }

      tweets.push({
        text,
        contentType: 'recap',
      });
      console.log(`[rm-content] Generated daily recap: ${results.length} settled, record ${record.wins}-${record.losses}, streak ${streak}`);
    }
  } catch (err) {
    console.error('[rm-content] Daily recap failed:', (err as Error).message);
  }

  return tweets;
}

// ── Momentum / Heater Post ──────────────────────────────────

export async function generateMomentumPost(): Promise<TweetContent[]> {
  const tweets: TweetContent[] = [];

  try {
    const streak = await getCurrentWinStreak();
    const lastN = await getLastNRecord(10);
    const todayRecord = await getRecentDayRecord();

    // Only fire if we have a real streak going
    if (streak < 3 && lastN.wins < 7) {
      console.log(`[rm-content] No momentum trigger (streak=${streak}, last10=${lastN.wins}-${lastN.losses})`);
      return tweets;
    }

    console.log(`[rm-content] Momentum trigger! streak=${streak}, last10=${lastN.wins}-${lastN.losses}`);

    const prompt = momentumPrompt({
      streak,
      lastN,
      todayRecord: todayRecord.total > 0 ? todayRecord : undefined,
    });

    const grokResult = await callGrok<GrokTweetResponse>(prompt);

    if (grokResult?.text && await passesQualityGates(grokResult.text)) {
      tweets.push({
        text: grokResult.text,
        contentType: 'momentum',
      });
      console.log(`[rm-content] Generated momentum post: streak=${streak}`);
    }
  } catch (err) {
    console.error('[rm-content] Momentum post failed:', (err as Error).message);
  }

  return tweets;
}

// ── Live Edge Alert (reactive, runs every cycle) ────────────

export async function generateLiveEdgeAlert(): Promise<TweetContent[]> {
  const tweets: TweetContent[] = [];

  try {
    // Look for high-confidence games starting in 1-4 hours
    const result = await fetchUpcomingForecasts(4, 1, 4);

    if (result.rows.length === 0) {
      return tweets;
    }

    for (const row of result.rows) {
      if (tweets.length >= 2) break;

      // Only alert on very high confidence games (top quartile)
      if (row.composite_confidence < 0.72) continue;

      const gameKey = `edge:${row.league}:${row.away_team}@${row.home_team}`;

      // Skip if we already alerted on this game today
      if (await hasAlertedGame(gameKey)) continue;

      if (!row.summary) continue;

      try {
        const prompt = liveEdgePrompt({
          league: row.league,
          homeTeam: row.home_team,
          awayTeam: row.away_team,
          startsAt: row.starts_at,
          confidence: row.composite_confidence,
          spread: row.spread,
          total: row.total,
          summary: row.summary,
        });

        const grokResult = await callGrok<GrokTweetResponse>(prompt);

        if (grokResult?.text && await passesQualityGates(grokResult.text)) {
          tweets.push({
            text: grokResult.text,
            contentType: 'live_edge',
            league: row.league,
            forecastId: row.id,
            gameKey,
            pickDirection: grokResult.pickSide,
          });
          console.log(`[rm-content] Live edge alert: ${row.away_team} @ ${row.home_team} (${Math.round(row.composite_confidence * 100)}%)`);
        }
      } catch (err) {
        console.error(`[rm-content] Live edge alert failed for ${row.home_team}:`, (err as Error).message);
      }
    }
  } catch (err) {
    console.error('[rm-content] Live edge alert generation failed:', (err as Error).message);
  }

  return tweets;
}

// ── Prime Time Reminder (6 PM pre-game) ─────────────────────

export async function generatePrimeTimeReminder(): Promise<TweetContent[]> {
  const tweets: TweetContent[] = [];

  try {
    // Games starting in the next 1-5 hours (6 PM → games at 7-11 PM)
    const result = await fetchUpcomingForecasts(4, 1, 5);

    if (result.rows.length === 0) {
      console.log('[rm-content] No upcoming games for prime time reminder');
      return tweets;
    }

    const prompt = primeTimePrompt(result.rows.map(r => ({
      league: r.league,
      homeTeam: r.home_team,
      awayTeam: r.away_team,
      startsAt: r.starts_at,
      confidence: r.composite_confidence,
      summary: r.summary || '',
    })));

    const grokResult = await callGrok<GrokTweetResponse>(prompt);

    if (grokResult?.text && await passesQualityGates(grokResult.text)) {
      tweets.push({
        text: grokResult.text,
        contentType: 'prime_reminder',
      });
      console.log(`[rm-content] Generated prime time reminder (${result.rows.length} games)`);
    }
  } catch (err) {
    console.error('[rm-content] Prime time reminder failed:', (err as Error).message);
  }

  return tweets;
}

// ── CTA / Conversion Tweet ──────────────────────────────────

export async function generateCTAPost(): Promise<TweetContent[]> {
  const tweets: TweetContent[] = [];

  try {
    const record = await getForecastRecordSince('2026-02-28');
    const dayOfYear = getDayOfYear();

    const prompt = ctaPrompt(dayOfYear, record.total >= 5 ? record : undefined);

    const grokResult = await callGrok<GrokTweetResponse>(prompt);

    if (grokResult?.text && await passesQualityGates(grokResult.text)) {
      tweets.push({
        text: grokResult.text,
        contentType: 'cta',
      });
      console.log(`[rm-content] Generated CTA tweet (angle rotation day ${dayOfYear})`);
    }
  } catch (err) {
    console.error('[rm-content] CTA post failed:', (err as Error).message);
  }

  return tweets;
}

// ── Educational Content ─────────────────────────────────────

export async function generateEducationalPost(): Promise<TweetContent[]> {
  const tweets: TweetContent[] = [];

  try {
    const dayOfYear = getDayOfYear();
    const prompt = educationalPrompt(dayOfYear);

    const grokResult = await callGrok<GrokTweetResponse>(prompt);

    if (grokResult?.text && await passesQualityGates(grokResult.text)) {
      tweets.push({
        text: grokResult.text,
        contentType: 'educational',
      });
      console.log(`[rm-content] Generated educational tweet (topic rotation day ${dayOfYear})`);
    }
  } catch (err) {
    console.error('[rm-content] Educational post failed:', (err as Error).message);
  }

  return tweets;
}
