// lib/gemini.js
// =========================================================================
// v1.0.24+: Thin compatibility wrapper around lib/ai.js (the router).
//
// Historically this module called Google's Gemini API directly. Now every
// function delegates to `aiGenerate()` / `aiGenerateJson()` which routes
// through `/api/v1/ai/generate` on the worker — so the call uses whatever
// provider the user configured (Gemini, Groq, Claude, OpenAI, …) or the
// 50-call free trial via Cloudflare Workers AI.
//
// The function signatures + return shapes are **identical** to the old API,
// so existing call sites work unchanged.
//
// Key-management helpers (get/save/clear/testGeminiKey) are kept for the
// dashboard's legacy "Gemini key" field — but new users don't need them
// because the router resolves the provider server-side.
// =========================================================================

import { getSetting, setSetting } from './db.js';
import { SYNC } from './config.js';
import { getSession } from './auth.js';
import { obLog } from './logger.js';
import { aiGenerate, aiGenerateJson } from './ai.js';

const KEY_LOCAL = 'gemini.apiKey';
const KEY_LAST_VERIFIED = 'gemini.verifiedAt';

// ───────────────────────── Key management (legacy) ─────────────────────────

export async function getGeminiKey() {
  return (await getSetting(KEY_LOCAL)) || null;
}

// Ask the SERVER whether this user already has a key stored (legacy columns OR
// a user_ai_providers gemini row — the worker's GET /gemini/key reports either).
// Used by onboarding + install to decide whether to ask for a key at all, so a
// returning user is never prompted for something the DB already has. (ADR-30)
// Returns { hasKey, verifiedAt, noSession?, error? }.
export async function getServerKeyStatus() {
  let token;
  try { ({ token } = await getSession()); } catch { token = null; }
  if (!token) return { hasKey: false, verifiedAt: null, noSession: true };
  try {
    const r = await fetch(`${SYNC.endpoint}/gemini/key`, { headers: { 'X-Session-Token': token } });
    if (!r.ok) return { hasKey: false, verifiedAt: null, error: `http_${r.status}` };
    const j = await r.json();
    return { hasKey: !!j.hasKey, verifiedAt: j.verifiedAt || null };
  } catch (e) {
    return { hasKey: false, verifiedAt: null, error: String(e?.message || e) };
  }
}

// Returns { ok, error?, local }. The key is ALWAYS saved locally; `ok` reflects
// whether the SERVER also persisted it (into users.* + user_ai_providers). The
// caller must surface a failed server save — a local-only key won't drive any
// server-side AI (AI-Mode, Hunter) and silently misleads the user. (ADR-29)
export async function saveGeminiKey(key) {
  await setSetting(KEY_LOCAL, key);
  try {
    const { token } = await getSession();
    if (!token) { obLog.warn('gemini_save_no_session'); return { ok: false, error: 'לא מחובר לשרת — התחבר ונסה שוב', local: true }; }
    const r = await fetch(`${SYNC.endpoint}/gemini/key`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'X-Session-Token': token },
      body: JSON.stringify({ key }),
    });
    if (!r.ok) {
      obLog.error('gemini_save_server_fail', { context: { status: r.status } });
      return { ok: false, error: `שגיאת שרת (${r.status}) — המפתח נשמר מקומית בלבד`, local: true };
    }
    return { ok: true, local: true };
  } catch (e) {
    obLog.error('gemini_save_server_exception', { context: { msg: String(e?.message || e) } });
    return { ok: false, error: `כשל רשת — המפתח נשמר מקומית בלבד`, local: true };
  }
}

export async function clearGeminiKey() {
  await setSetting(KEY_LOCAL, null);
  await setSetting(KEY_LAST_VERIFIED, null);
  try {
    const { token } = await getSession();
    if (token) {
      await fetch(`${SYNC.endpoint}/gemini/key`, {
        method: 'DELETE',
        headers: { 'X-Session-Token': token },
      });
    }
  } catch {}
}

export async function testGeminiKey() {
  // Now delegates to the router test — works for any provider the user
  // configured, not just Gemini. Keep the old name for backward compat.
  const r = await aiGenerate('Reply with exactly the single word OK.', {
    feature: 'key_test', temperature: 0, maxTokens: 10,
  });
  if (r.ok) {
    await setSetting(KEY_LAST_VERIFIED, Date.now());
    return { ok: true, modelEcho: r.text, provider: r.provider };
  }
  if (r.trialExhausted) return { ok: false, error: 'ניסיון חינמי נגמר. הוסף ספק AI בדשבורד.' };
  return { ok: false, error: r.error || 'unknown' };
}

// ───────────────────────── Generic text/vision/image ───────────────────────

export async function geminiText(prompt, { model, maxTokens = 256, temperature = 0.3 } = {}) {
  const r = await aiGenerate(prompt, {
    feature: 'generic_text', temperature, maxTokens, model,
  });
  return r.ok ? { ok: true, text: r.text } : { ok: false, error: r.error };
}

export async function geminiVision(imageDataUrl, prompt, { maxTokens = 256 } = {}) {
  const r = await aiGenerate(prompt, {
    kind: 'vision', imageDataUrl, feature: 'generic_vision', maxTokens, temperature: 0.1,
  });
  return r.ok ? { ok: true, text: r.text } : { ok: false, error: r.error };
}

export async function geminiGenerateImage(prompt) {
  const r = await aiGenerate(prompt, { kind: 'image', feature: 'image_gen', maxTokens: 1 });
  return r.ok && r.dataUrl
    ? { ok: true, dataUrl: r.dataUrl }
    : { ok: false, error: r.error || 'no image returned' };
}

// ───────────────────────── Post generation (FB + LI) ───────────────────────

export async function geminiGeneratePosts({
  briefDescription, existingExamples = [], count = 3, postType = 'text', platform = 'facebook',
}) {
  const stylePart = existingExamples.length
    ? `\nדוגמאות מקמפיין קיים (להעתיק את הסגנון):\n${existingExamples.slice(0, 3).map((e, i) => `${i + 1}. "${e.slice(0, 200)}"`).join('\n')}\n`
    : '';

  if (platform === 'linkedin') {
    const liTypeConstraint = {
      text: 'פוסט מקצועי באורך 1200-1900 תווים. שורות קצרות (1-2 משפטים), הרבה רווחים לבנים — נקרא היטב בטלפון. פתח בשורה שמושכת לחיצה ("Read more"). סיים בשאלה מכוונת תגובה.',
      colored: 'פוסט קצר עד 200 תווים (LinkedIn לא תומך ברקעים צבעוניים — יוצג כטקסט נקי, אבל קצר).',
      link: 'פוסט מקצועי 1000-1500 תווים. ⚠️ אל תכלול קישורים בגוף הפוסט (LinkedIn מוריד 60% מהחשיפה). כתב בסוף: "Link in the comments 👇" ושים את הקישור בתגובה הראשונה.',
      poll: 'מקדמה קצרה לסקר (200-400 תווים) שתוסיף הקשר ותעודד הצבעה.',
    }[postType] || 'פוסט LinkedIn מקצועי 1200-1900 תווים.';

    const prompt = `אתה כותב לפיד LinkedIn מקצועי בעברית (B2B).

המטרה: ${briefDescription}

${stylePart}

הוראות פורמט (LinkedIn 2026):
- ${liTypeConstraint}
- פתיחה: שורה ראשונה שיוצרת סקרנות (זה מה שמופיע לפני "...see more")
- שורות קצרות, רווחים לבנים — לא בלוק טקסט צפוף
- סיים בשאלה ישירה לקהל (זה מכפיל את כמות התגובות)
- 3-5 hashtags בסוף (פורמולה: 1 רחב + 2 נישתי + 1 ממותג). דוגמה: #B2B #FinTechIsrael #OrganicAI
- שפה: אישית, סיפורית, "פגשתי / למדתי / טעיתי" — לא תאגידית, לא נמלצת
- ללא אמוג'ים מוגזמים: 2-4 מקסימום (👇 ✨ 🚀 🎯 ✅)
- ללא pitch ישיר. הערך קודם. אם יש מוצר — מוזכר ברפרור, לא במרכז
- אל תוסיף קדמה כמו "וריאציה 1:" — רק את הטקסט עצמו

צור ${count} וריאציות עם זוויות שונות (סיפור אישי / טיפ מעשי / השוואה / נתון מפתיע).

החזר JSON ROW בפורמט הזה, ללא markdown ללא backticks:
{"variations":[{"title":"כותרת קצרה לזיהוי פנימי","text":"הפוסט המלא"},...]}`;

    const r = await aiGenerateJson(prompt, { feature: 'post_gen_li', temperature: 0.9, maxTokens: 4000 });
    if (!r.ok) return { ok: false, error: r.error };
    if (!Array.isArray(r.data?.variations)) return { ok: false, error: 'no variations array' };
    return { ok: true, variations: r.data.variations };
  }

  const typeConstraint = {
    text: 'פוסט רגיל באורך 100-400 תווים.',
    colored: 'פוסט קצר ובולט עד 130 תווים בלבד (יוצג עם רקע צבעוני).',
    link: 'פוסט קצר עד 200 תווים שמלווה קישור (אל תכלול את הקישור עצמו).',
  }[postType] || 'פוסט פייסבוק רגיל.';

  const prompt = `אתה כותב פוסטים לקבוצות פייסבוק עסקיות בעברית.

המטרה: ${briefDescription}

${stylePart}

הוראות:
- צור ${count} וריאציות שונות
- כל וריאציה ${typeConstraint}
- שפה ישירה, אנושית, לא רובוטית
- ללא hashtags אלא אם המטרה דורשת
- אל תוסיף קדמה כמו "וריאציה 1:" — רק את הטקסט עצמו

החזר JSON ROW בפורמט הזה, ללא markdown ללא backticks:
{"variations":[{"title":"כותרת קצרה לזיהוי פנימי","text":"הפוסט"},...]}`;

  const r = await aiGenerateJson(prompt, { feature: 'post_gen_fb', temperature: 0.85, maxTokens: 1500 });
  if (!r.ok) return { ok: false, error: r.error };
  if (!Array.isArray(r.data?.variations)) return { ok: false, error: 'no variations array' };
  return { ok: true, variations: r.data.variations };
}

// ───────────────────────── LinkedIn growth-comment generator ────────────────

export async function geminiGenerateLinkedInComment(postText, { authorName = '', userExpertise = '' } = {}) {
  if (!postText || postText.trim().length < 20) {
    return { ok: false, error: 'post-text too short for meaningful comment' };
  }
  const hebrewChars = (postText.match(/[֐-׿]/g) || []).length;
  const totalChars = postText.length;
  const isHebrew = hebrewChars / Math.max(totalChars, 1) > 0.15;

  const expertiseLine = userExpertise
    ? `\nהמומחיות שלי (לרמז בעדינות אם רלוונטי, אבל לא בכל תגובה): ${userExpertise}`
    : '';

  const prompt = isHebrew
    ? `אתה משתמש מקצועי בלינקדאין שכותב תגובות חכמות לפוסטים של אחרים. המטרה: להוסיף ערך, להפוך לזכיר, להוביל לקליק בפרופיל שלך.

הפוסט (מאת ${authorName || 'משתמש'}):
"""
${postText.slice(0, 1500)}
"""${expertiseLine}

כתב תגובה אחת בעברית — 1-3 משפטים, מקסימום 280 תווים:
- הגב לנקודה ספציפית מהפוסט (להראות שקראת)
- הוסף זווית / דוגמה / שאלה שמרחיבה את הדיון
- ללא pitch של מוצר/שירות, ללא קישורים, ללא @mentions
- ללא תחילית כמו "מסכים מאוד" או "פוסט נהדר" (גנרי, ייראה כמו בוט)
- 0-2 אמוג'ים בלבד, מקסימום
- שפה אנושית, יומיומית — לא תאגידית

החזר רק את הטקסט של התגובה. בלי הסבר, בלי גרשיים, בלי markdown.`
    : `You're a thoughtful LinkedIn user leaving a value-add comment on someone else's post. Goal: add insight, be memorable, earn a profile click.

Post (by ${authorName || 'a user'}):
"""
${postText.slice(0, 1500)}
"""${expertiseLine}

Write ONE comment in English — 1-3 sentences, max 280 chars:
- Respond to a SPECIFIC point in the post (show you read it)
- Add an angle / example / question that extends the discussion
- No product pitch, no links, no @mentions
- Don't start with "Great post" or "Totally agree" (generic, bot-flagged)
- 0-2 emojis max
- Conversational, not corporate

Return ONLY the comment text. No explanation, no quotes, no markdown.`;

  const r = await aiGenerate(prompt, { feature: 'li_comment', temperature: 0.85, maxTokens: 200 });
  if (!r.ok) return { ok: false, error: r.error };
  let text = (r.text || '').trim();
  text = text.replace(/^["'`]+|["'`]+$/g, '').replace(/^```[a-z]*\n?|\n?```$/g, '').trim();
  if (text.length < 12) return { ok: false, error: 'too-short' };
  if (text.length > 500) text = text.slice(0, 500);
  if (/^(great post|amazing|love this|מסכים לחלוטין|פוסט נהדר|תודה על השיתוף)\b/i.test(text)) {
    return { ok: false, error: 'too-generic' };
  }
  return { ok: true, text };
}

// ───────────────────────── LinkedIn reply-to-commenter generator ────────────

export async function geminiGenerateLinkedInReply(commentText, {
  commentAuthor = '', originalPostText = '', userExpertise = '',
} = {}) {
  if (!commentText || commentText.trim().length < 5) {
    return { ok: false, error: 'comment too short for meaningful reply' };
  }
  const hebrewChars = (commentText.match(/[֐-׿]/g) || []).length;
  const isHebrew = hebrewChars / Math.max(commentText.length, 1) > 0.15;

  const postContext = originalPostText
    ? (isHebrew ? `\nההקשר (הפוסט המקורי שלי):\n"""${originalPostText.slice(0, 600)}"""\n`
                : `\nContext (my original post):\n"""${originalPostText.slice(0, 600)}"""\n`)
    : '';
  const expertiseLine = userExpertise
    ? (isHebrew ? `\nהמומחיות שלי: ${userExpertise}\n` : `\nMy expertise: ${userExpertise}\n`)
    : '';

  const prompt = isHebrew
    ? `אתה מחבר הפוסט. ${commentAuthor || 'משתמש'} השאיר תגובה. כתב לו תשובה אישית.${postContext}${expertiseLine}
התגובה שלו:
"""${commentText.slice(0, 1000)}"""

כתב תשובה אחת בעברית — 1-2 משפטים, מקסימום 200 תווים:
- הכר בנקודה ספציפית שהוא העלה (להראות שקראת)
- הוסף ערך/דוגמה/שאלה ממשיכה
- אישי וחם, לא רובוטי
- אל תתחיל ב"תודה על התגובה" או "תגובה מעולה" (גנרי, ניתן לזיהוי כבוט)
- 0-1 אמוג'ים בלבד
- ללא קישורים, ללא pitch

החזר רק את הטקסט. בלי הסבר.`
    : `You're the post author. ${commentAuthor || 'A user'} just commented. Reply personally.${postContext}${expertiseLine}
Their comment:
"""${commentText.slice(0, 1000)}"""

Write ONE reply in English — 1-2 sentences, max 200 chars:
- Acknowledge their specific point (show you read it)
- Add value / example / follow-up question
- Warm and personal, not robotic
- Don't start with "Thanks for the comment" or "Great point" (bot-flagged)
- 0-1 emoji max
- No links, no pitch

Return ONLY the reply text. No explanation.`;

  const r = await aiGenerate(prompt, { feature: 'li_reply', temperature: 0.8, maxTokens: 180 });
  if (!r.ok) return { ok: false, error: r.error };
  let text = (r.text || '').trim();
  text = text.replace(/^["'`]+|["'`]+$/g, '').replace(/^```[a-z]*\n?|\n?```$/g, '').trim();
  if (text.length < 8) return { ok: false, error: 'too-short' };
  if (text.length > 400) text = text.slice(0, 400);
  if (/^(thanks for|great point|good comment|תודה על|תגובה מעולה|נקודה טובה)/i.test(text)) {
    return { ok: false, error: 'too-generic' };
  }
  return { ok: true, text };
}

// ───────────────────────── LinkedIn Profile Auditor ────────────────────────

export async function geminiAuditLinkedInProfile(profile) {
  if (!profile || !profile.name) return { ok: false, error: 'no profile data' };
  const allText = `${profile.headline || ''} ${profile.about || ''}`;
  const hebrewChars = (allText.match(/[֐-׿]/g) || []).length;
  const isHebrew = hebrewChars / Math.max(allText.length, 1) > 0.15;

  const summary = `
Name: ${profile.name}
Headline: ${profile.headline || '(empty)'}
Location: ${profile.location || '(empty)'}
Has avatar: ${profile.hasAvatar ? 'yes' : 'NO'}
Has custom banner: ${profile.hasCustomBanner ? 'yes' : 'NO (using default)'}
About length: ${profile.aboutLength} chars
About preview: "${(profile.about || '').slice(0, 600)}"
Experience entries: ${profile.experienceCount}
Education entries: ${profile.educationCount}
Skills listed: ${profile.skillCount}
Has Featured section: ${profile.hasFeatured ? 'yes' : 'NO'}
Recommendations: ${profile.recommendationCount}
Recent activity visible: ${profile.activityVisible}`;

  const prompt = isHebrew
    ? `אתה יועץ LinkedIn מומחה. אני נותן לך פרופיל אמיתי וצריך אודיט קונקרטי.

הפרופיל:
${summary}

מהן 5-7 הבעיות החשובות ביותר בפרופיל הזה, בסדר עדיפויות? לכל אחת — פעולה ספציפית מדויקת לתיקון (לא "שפר את ה-headline" — אלא <em>איך</em>). אל תיתן עצות כלליות. אל תכתוב מבוא ארוך.

החזר JSON תקני, ללא markdown, ללא הסבר:
{
  "score": 1-10 (ציון כללי),
  "summary": "1-2 משפטים על המצב הכללי",
  "items": [
    {"priority": "high|medium|low", "issue": "מה הבעיה", "action": "הפעולה הספציפית לתיקון", "section": "headline|about|experience|featured|banner|other"}
  ]
}`
    : `You're a LinkedIn profile expert. I'm giving you a real profile and need a concrete audit.

Profile:
${summary}

What are the top 5-7 most important issues with this profile, ranked? For each — a SPECIFIC actionable fix (not "improve your headline" — explain <em>how</em>). No generic advice. No long intro.

Return valid JSON only, no markdown, no explanation:
{
  "score": 1-10 (overall score),
  "summary": "1-2 sentences on overall state",
  "items": [
    {"priority": "high|medium|low", "issue": "what's wrong", "action": "specific fix", "section": "headline|about|experience|featured|banner|other"}
  ]
}`;

  const r = await aiGenerateJson(prompt, { feature: 'profile_audit', temperature: 0.4, maxTokens: 2000 });
  if (!r.ok) return { ok: false, error: r.error };
  const parsed = r.data;
  if (!Array.isArray(parsed.items)) return { ok: false, error: 'no items array' };
  return {
    ok: true,
    score: Math.max(1, Math.min(10, Number(parsed.score) || 5)),
    summary: String(parsed.summary || '').slice(0, 500),
    items: parsed.items.slice(0, 10).map(it => ({
      priority: ['high', 'medium', 'low'].includes(it.priority) ? it.priority : 'medium',
      issue: String(it.issue || '').slice(0, 200),
      action: String(it.action || '').slice(0, 400),
      section: String(it.section || 'other').slice(0, 30),
    })),
    language: isHebrew ? 'he' : 'en',
  };
}

// ───────────────────────── Pre-publish safety check ────────────────────────

export async function geminiSafetyCheck(text) {
  const prompt = `אתה מומחה למדיניות פייסבוק. דרג את הטקסט הזה לסיכון שהוא יפר את תקני הקהילה של פייסבוק (ספאם, הטעיה, תוכן אסור).

טקסט הפוסט:
"""
${text.slice(0, 1500)}
"""

החזר JSON בלי markdown:
{"score":1-10,"risk":"low|medium|high","reason":"הסבר קצר"}

- 1-3 = low (בטוח)
- 4-6 = medium (אפשרי שיתפסו)
- 7-10 = high (סיכון גבוה לסגירה/חסימה)`;

  const r = await aiGenerateJson(prompt, { feature: 'safety_check', temperature: 0.1, maxTokens: 200 });
  if (!r.ok) return { ok: false, error: r.error };
  return { ok: true, score: r.data.score, risk: r.data.risk, reason: r.data.reason };
}

// ───────────────────────── Smart post-to-group matching ────────────────────

export async function geminiPickBestPost(posts, group) {
  const postsList = posts.map((p, i) =>
    `${i + 1}. id=${p.id} | "${p.title}" — "${(p.text || '').slice(0, 150)}"`
  ).join('\n');

  const prompt = `אתה אסטרטג שיווק בפייסבוק. בחר את הפוסט שהכי מתאים לקבוצה הזו.

קבוצה: "${group.name}"${group.privacy ? ` (${group.privacy})` : ''}
מדיניות פרסום: ${group.postPolicy || 'unknown'}
מספר חברים: ${group.memberCount || 'unknown'}

פוסטים זמינים:
${postsList}

החזר JSON בלי markdown:
{"postId":"<id מהרשימה>","reason":"הסבר קצר בעברית"}`;

  const r = await aiGenerateJson(prompt, { feature: 'pick_best_post', temperature: 0.4, maxTokens: 200 });
  if (!r.ok) return { ok: false, error: r.error };
  return { ok: true, postId: r.data.postId, reason: r.data.reason };
}

// ───────────────────────── Publish-outcome classification (vision) ─────────

export async function geminiClassifyPublish(screenshotDataUrl, snippet) {
  const prompt = `אתה רואה את הדף של פייסבוק אחרי שמשתמש לחץ "פרסום" בקבוצה. הפוסט מתחיל ב: "${snippet.slice(0, 100)}"

ענה רק עם JSON בפורמט הבא, ללא טקסט נוסף, ללא markdown:
{"status":"published|pending|failed|unknown","confidence":0.0-1.0,"explanation":"..."}

הסבר:
- published = הפוסט מופיע בפיד באופן רגיל ומלא
- pending = הפוסט נשלח אבל ממתין לאישור מנהל
- failed = יש הודעת שגיאה, הפוסט לא עלה
- unknown = אין סימן ברור`;
  const r = await aiGenerateJson(prompt, {
    kind: 'vision', imageDataUrl: screenshotDataUrl,
    feature: 'classify_publish', maxTokens: 200,
  });
  if (!r.ok) return { ok: false, error: r.error };
  return { ok: true, ...r.data };
}
