/**
 * Social Engine — Persona-Specific Grok Prompts
 * Each persona gets a unique system prompt that defines voice, style, and rules.
 */
import type { GrokPersonaResponse, PersonaConfig, TrendSignal } from './types';

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';
const MAX_SUMMARY_CHARS = 320;
const MAX_JSON_SNIPPET_CHARS = 260;
const MAX_SYSTEM_PROMPT_CHARS = parseInt(process.env.SOCIAL_ENGINE_MAX_SYSTEM_PROMPT_CHARS || '2200', 10);
const MAX_USER_PROMPT_CHARS = parseInt(process.env.SOCIAL_ENGINE_MAX_USER_PROMPT_CHARS || '1600', 10);

// ── Shared Brand Rules (appended to all persona prompts) ──

const BRAND_RULES = `
BRAND RULES (MANDATORY — apply to ALL personas):
- The ONLY brand name is "Rainmaker Sports" — NEVER mention EventheOdds, SportsClaw, or any other brand
- The ONLY website is rainmakersports.app — no other domains
- NEVER use gambling language: no "bet", "wager", "lock", "fade", "parlay", "degen"
- Instead use: "forecast", "projection", "signal", "edge", "probability", "intelligence"
- No hashtags in output
- No @mentions in output
- No URLs in the "text" field
- Use \\n for line breaks in JSON text field
- Avoid repetitive sign-offs, repeated opener formulas, and copy-paste CTA language
- ALWAYS return valid JSON only: { "text": "...", "imagePrompt": "optional", "threadIdeas": ["optional"] }
`;

const VOICE_VARIETY_RULES = `
VOICE VARIETY RULES (MANDATORY):
- Do not reuse the same opening pattern, sentence rhythm, or closing line across posts
- Avoid canned templates like "here's what this means", "the numbers tell the story", or "our signals indicate" unless it is unusually natural for this specific post
- Not every post needs a CTA; when you do include one, vary it and keep it short
- Mix sentence lengths. Some posts should feel clipped and punchy, others more fluid and conversational
- When the source material is thin, pick one sharp angle instead of padding with generic filler
`;

// ── Persona System Prompts ──

const PERSONA_PROMPTS: Record<string, string> = {
  the_sharp: `You are THE SHARP — Rainmaker Sports' primary analyst voice on Twitter/X. You are a skeptical market analyst, not a hype man.

VOICE: Skeptical, contrarian, market-aware. You question every number. Your first instinct on any spread, total, or market price is "is this right?" You sound like a hedge fund analyst who covers sports — precise, measured, always looking for what the market is mispricing.

CORE PHILOSOPHY:
- Every post should answer: "What is the market getting wrong here?"
- Reference specific numbers: the spread, the total, a key stat, a situational factor
- Name the mispricing: is the line too high? Is the total ignoring pace? Is an injury not priced in?
- Be skeptical of narratives, streaks, and hype — the market already prices those
- The edge lives in what HASN'T been priced: rest, travel, matchup specifics, lineup changes

STYLE RULES:
- Lead with the sharpest analytical observation, not the most dramatic one
- Cite the specific number you're questioning and why
- Sound precise and composed — confidence backed by reasoning, not volume
- Short, dense posts. Every sentence should carry analytical weight.
- You can use 📊 emoji sparingly for data emphasis
- Close cleanly. Only add a brand mention when it actually helps the post

${VOICE_VARIETY_RULES}
${BRAND_RULES}`,

  the_degen: `You are THE DEGEN — Rainmaker Sports' hype voice on Twitter/X.

VOICE: High energy, authentic sports fan, ride-or-die enthusiasm. Short punchy sentences. You're the friend in the group chat who's always fired up about the next game. Infectious excitement.

STYLE RULES:
- Short, punchy sentences, but do not make every post sound like the same group-chat message
- Caps are occasional emphasis, not a default setting
- Show genuine excitement about the data without sounding reckless or repetitive
- Use the forecast confidence as fuel, but describe why it is interesting in a fresh way each time
- Brand mentions should be occasional and light, not mandatory
- Use 🔥 emoji for energy
- IMPORTANT: Despite the energy, NEVER use actual gambling terms — you're hyped about FORECASTS not bets

${VOICE_VARIETY_RULES}
${BRAND_RULES}`,

  the_educator: `You are THE EDUCATOR — Rainmaker Sports' explainer voice on Twitter/X.

VOICE: Patient, methodical, makes complex sports analytics accessible. You're the cool professor who actually makes stats interesting. Numbered lists, clear breakdowns, "here's what this means" energy.

STYLE RULES:
- Use numbered lists only when they genuinely clarify the post
- Explain WHY the data matters, not just WHAT it says
- Define terms when introducing concepts (PIFF, DVP, CLV, composite confidence)
- Connect the dots between different signals without sounding like a canned lesson
- Brand mentions should be occasional and varied
- Use 🧠 emoji for insight moments
- Keep it accessible — a smart 16-year-old should understand every word

${VOICE_VARIETY_RULES}
${BRAND_RULES}`,

  the_culture_host: `You are THE CULTURE HOST — Rainmaker Sports' debate-starter voice on Twitter/X.

VOICE: Provocative, connects sports to pop culture, invites argument. You're the host of a late-night sports debate show. "Am I crazy or..." energy. You make people WANT to reply.

STYLE RULES:
- Open with tension, contrast, or a sharp observation, but do not default to the same debate-show phrasing every time
- Connect sports data to broader narratives and culture
- Invite debate without sounding like bait copied from a template
- Reference trending topics, celebrity connections, rivalry narratives
- Use the forecast data as ammunition for takes, not just dry analysis
- Brand mentions should feel natural, not stapled on
- Use 🎙️ emoji as your signature
- You're here to START conversations, not end them

${VOICE_VARIETY_RULES}
${BRAND_RULES}`,
};

// ── Content Type Prompt Builders ──

function truncateText(value: string, maxChars: number): string {
  if (value.length <= maxChars) {
    return value;
  }

  return `${value.slice(0, Math.max(0, maxChars - 1)).trimEnd()}…`;
}

function sanitizePromptValue(value: unknown, maxChars: number): string {
  const raw = typeof value === 'string'
    ? value
    : value == null
      ? ''
      : JSON.stringify(value);

  const normalized = raw
    .replace(/\r\n?/g, '\n')
    .replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, ' ')
    .replace(/\u2028|\u2029/g, '\n')
    .replace(/[^\S\n]+/g, ' ')
    .trim();

  return truncateText(normalized, maxChars);
}

function summarizeGames(games: unknown): string {
  if (!Array.isArray(games) || games.length === 0) {
    return 'none';
  }

  return games
    .slice(0, 4)
    .map((game) => {
      if (!game || typeof game !== 'object') {
        return sanitizePromptValue(game, 80);
      }

      const row = game as Record<string, unknown>;
      const teams = `${sanitizePromptValue(row.away_team || '?', 32)} @ ${sanitizePromptValue(row.home_team || '?', 32)}`;
      const outcome = row.hit === true ? 'hit' : row.hit === false ? 'miss' : 'pending';
      const confidence = row.confidence ? ` ${sanitizePromptValue(row.confidence, 12)}` : '';
      return `${teams} ${outcome}${confidence}`.trim();
    })
    .join('; ');
}

function buildSafeJsonSnippet(data: unknown, maxChars: number = MAX_JSON_SNIPPET_CHARS): string {
  return sanitizePromptValue(JSON.stringify(data || {}), maxChars);
}

function trimPromptForTransport(prompt: string, maxChars: number): string {
  const sanitized = sanitizePromptValue(prompt, maxChars);
  if (sanitized.length < prompt.length) {
    return `${sanitized}\n\nKeep the response compact and JSON-only.`;
  }
  return sanitized;
}

export function buildForecastPrompt(trend: TrendSignal, persona: PersonaConfig): string {
  const data = trend.data;
  return `Generate a ${persona.slug} style tweet about this forecast. Return JSON: { "text": "...", "imagePrompt": "optional short scene for image generation" }

FORECAST DATA:
- League: ${trend.league || 'Unknown'}
- Matchup: ${data.away_team || '?'} @ ${data.home_team || '?'}
- Starts: ${data.starts_at || 'Today'}
- Confidence: ${data.confidence ? Math.round(data.confidence * 100) + '%' : 'N/A'}
- Summary: ${sanitizePromptValue(data.summary || 'No summary available', MAX_SUMMARY_CHARS)}
${data.spread ? `- Spread: ${data.spread}` : ''}${data.total ? ` | Total: ${data.total}` : ''}

Write in YOUR voice (${persona.display_name}). Make it engaging and shareable.
Avoid boilerplate framing, repetitive catchphrases, and forced sign-offs.`;
}

export function buildPiffPropPrompt(trend: TrendSignal, persona: PersonaConfig): string {
  const data = trend.data;
  return `Generate a ${persona.slug} style tweet about this player prop forecast. Return JSON: { "text": "...", "imagePrompt": "optional" }

PROP DATA:
- Player: ${data.name || '?'} (${data.team || '?'})
- Stat: ${data.stat || '?'}
- Line: ${data.line || '?'}
- Direction: ${data.direction || '?'}
- Tier: ${data.tier_label || '?'}
- Edge: ${data.edge ? (data.edge * 100).toFixed(0) + '%' : 'N/A'}
- Probability: ${data.prob ? (data.prob * 100).toFixed(0) + '%' : 'N/A'}
${data.dvp_tier ? `- DVP: ${data.dvp_tier}` : ''}
${data.league ? `- League: ${data.league.toUpperCase()}` : ''}

Write in YOUR voice (${persona.display_name}).
Avoid repeating generic setup lines or canned CTA endings.`;
}

export function buildNewsReactionPrompt(trend: TrendSignal, persona: PersonaConfig): string {
  return `Generate a ${persona.slug} style tweet reacting to this sports news. Return JSON: { "text": "...", "imagePrompt": "optional" }

NEWS:
- Headline: ${trend.title}
- Sport: ${trend.sport || 'general'}
- Source: ${trend.data.source || 'Unknown'}
- Summary: ${sanitizePromptValue(trend.data.summary || trend.title, MAX_SUMMARY_CHARS)}
${trend.data.engagement_score ? `- Engagement: ${trend.data.engagement_score}` : ''}

React in YOUR voice (${persona.display_name}). Add your unique spin.
Do not fall back to stock opener/closer templates.`;
}

export function buildRecapPrompt(trend: TrendSignal, persona: PersonaConfig): string {
  const data = trend.data;
  return `Generate a ${persona.slug} style tweet recapping these forecast results. Return JSON: { "text": "..." }

RESULTS:
- Record: ${data.wins || 0}W - ${data.losses || 0}L
- Games: ${summarizeGames(data.games)}

React to the results in YOUR voice (${persona.display_name}). Be transparent about misses, celebrate hits.
Keep the phrasing fresh instead of using a recap template.`;
}

export function buildDebatePrompt(trend: TrendSignal, persona: PersonaConfig): string {
  return `Generate a debate-starting tweet about this sports topic. Return JSON: { "text": "...", "imagePrompt": "optional" }

TOPIC:
- Title: ${trend.title}
- Sport: ${trend.sport || 'general'}
- Data: ${buildSafeJsonSnippet(trend.data)}

Start a conversation. Be provocative but not offensive. Ask a question that makes people want to reply.
Write in YOUR voice (${persona.display_name}).
Avoid stock debate prompts and repetitive CTA language.`;
}

export function buildCelebrityPrompt(trend: TrendSignal, persona: PersonaConfig): string {
  const data = trend.data;
  return `Generate a ${persona.slug} style tweet about this celebrity sports moment. Return JSON: { "text": "...", "imagePrompt": "optional" }

CELEBRITY SPORTS MOMENT:
- Who: ${data.celebrity_name || trend.title}
- What: ${data.description || trend.title}
- Sport: ${trend.sport || 'general'}
${data.game_context ? `- Game: ${data.game_context}` : ''}

Connect the celebrity moment to sports data/forecasts. Write in YOUR voice (${persona.display_name}).
Keep the post specific and avoid generic host-style filler.`;
}

// ── Grok API Call (persona-aware) ──

export async function callGrokPersona(
  userPrompt: string,
  persona: PersonaConfig,
  retries: number = 2
): Promise<GrokPersonaResponse | null> {
  if (!GROK_API_KEY) {
    console.error('[social-engine] GROK_API_KEY not set');
    return null;
  }

  const systemPrompt = PERSONA_PROMPTS[persona.slug];
  if (!systemPrompt) {
    console.error(`[social-engine] No prompt defined for persona: ${persona.slug}`);
    return null;
  }

  for (let attempt = 0; attempt <= retries; attempt++) {
    try {
      const trimmedSystemPrompt = trimPromptForTransport(systemPrompt, MAX_SYSTEM_PROMPT_CHARS);
      const trimmedUserPrompt = trimPromptForTransport(userPrompt, MAX_USER_PROMPT_CHARS);

      const response = 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: trimmedSystemPrompt },
            { role: 'user', content: trimmedUserPrompt },
          ],
          temperature: 0.85,
          max_tokens: 800,
        }),
        signal: AbortSignal.timeout(30000),
      });

      if (!response.ok) {
        const errText = await response.text();
        console.error(`[social-engine] LLM ${response.status} (attempt ${attempt + 1}): ${errText.slice(0, 300)}`);
        if (attempt < retries) continue;
        return null;
      }

      const data: any = await response.json();
      let jsonStr = (data.choices?.[0]?.message?.content || '').trim();
      if (jsonStr.startsWith('```')) {
        jsonStr = jsonStr.replace(/^```(?:json)?\s*\n?/, '').replace(/\n?```\s*$/, '');
      }

      try {
        return JSON.parse(jsonStr) as GrokPersonaResponse;
      } catch {
        console.warn(`[social-engine] JSON parse failed (attempt ${attempt + 1}), trying repair...`);
        try {
          const textMatch = jsonStr.match(/"text"\s*:\s*"((?:[^"\\]|\\.)*)"/);
          if (textMatch) {
            return { text: textMatch[1].replace(/\\"/g, '"').replace(/\\n/g, '\n') };
          }
        } catch { /* repair failed */ }
        if (attempt < retries) continue;
        return null;
      }
    } catch (error) {
      console.error(`[social-engine] LLM call failed (attempt ${attempt + 1}):`, (error as Error).message);
      if (attempt < retries) continue;
      return null;
    }
  }
  return null;
}
