const { googleAI } = require("@genkit-ai/googleai");
const { genkit, z } = require("genkit");
const { GEMINI_API_KEY } = require("../config/keys");
const admin = require("firebase-admin");
const { Buffer } = require("buffer");
const { setTimeout } = require("timers");

exports.generateAI = async ({ system = "", prompt = "", schema }) => {
  const genkitInstance = genkit({
    plugins: [googleAI({ apiKey: GEMINI_API_KEY })],
    model: googleAI.model("gemini-2.5-flash"),
  });

  if (prompt?.length < 1) {
    throw new Error(
      "Vous devez spécifier un prompt pour effectuer cette action.",
    );
  }

  const { output } = await genkitInstance.generate({
    system,
    prompt,
    output: { schema },
  });

  return output;
};

// Analyse la toxicité des paroles d'une chanson pour modération.
// Entrée: { title: string, lyrics: string | {couplet?, refrain?} | Array<{type, lyrics}> }
// Sortie (schéma strict): indicateurs de toxicité, raisons, extraits et décision de blocage.
// Cloud Function plus nuancée : bloque le réellement problématique, laisse passer le reste
exports.analyseLyrics = async ({ title = "", lyrics }) => {
  // --- Normalisation inchangée (robuste aux différents formats) ---
  const normalizeLyrics = (raw) => {
    if (!raw) return "";
    if (typeof raw === "string") return raw;
    if (Array.isArray(raw)) {
      return raw
        .map((s) => {
          if (!s) return "";
          const label = s.type ? String(s.type).toUpperCase() : "SECTION";
          return `[${label}]\n${s.lyrics || ""}`;
        })
        .filter(Boolean)
        .join("\n\n");
    }
    if (typeof raw === "object") {
      const parts = [];
      if (raw.couplet) parts.push(`[COUPLET]\n${raw.couplet}`);
      if (raw.refrain) parts.push(`[REFRAIN]\n${raw.refrain}`);
      return parts.join("\n\n");
    }
    return String(raw || "");
  };

  const lyricsText = normalizeLyrics(lyrics).trim();

  if (!lyricsText) {
    throw new Error(
      "analyseLyrics: vous devez fournir des paroles (lyrics) non vides.",
    );
  }

  // --- Schéma de sortie (inchangé pour compatibilité) ---
  const moderationSchema = z.object({
    title: z.string().describe("Titre analysé (copie du titre fourni)"),
    flagged: z
      .boolean()
      .describe("Vrai si au moins un signalement de toxicité est détecté."),
    blocked: z
      .boolean()
      .describe(
        "Vrai si la publication doit être BLOQUÉE (cas graves et non ambigus).",
      ),
    score: z.number().min(0).max(1).describe("Score global de toxicité (0-1)"),
    reasons: z.array(z.string()).describe("Raisons concises de la décision."),
    excerpts: z
      .array(
        z.object({
          quote: z.string().describe("Extrait exact problématique"),
          category: z
            .string()
            .describe(
              "Catégorie principale (ex: racism, nazism, sexism, homophobia, harassment, self-harm incitement, terrorism, minors-sexual-content, etc.)",
            ),
          severity: z
            .string()
            .describe("none|low|medium|high|critical pour l'extrait"),
        }),
      )
      .max(10)
      .describe("Jusqu'à 10 extraits problématiques"),
    success: z.boolean().describe("Toujours true si l'analyse a abouti"),
  });

  // --- Politique : davantage de place à la création tout en restant sûre ---
  const system = `
Tu es un modérateur de contenu spécialisé dans l'analyse de paroles de chanson.
Objectif: protéger les utilisateurs en BLOQUANT uniquement les cas réellement problématiques et non ambigus, tout en LAISSANT PASSER les œuvres qui utilisent la narration, la satire, la critique ou la représentation de personnages.

Sois STRICT sur les risques graves, mais NE SOIS PAS PUNITIF envers l'expression artistique.
Évalue TOUJOURS: 1) l'intention, 2) le contexte (récit, citation, dénonciation, rôle/persona), 3) la cible, 4) la probabilité de tort réel.

Retourne UNIQUEMENT un JSON conforme au schéma. Pas de texte hors JSON.
`.trim();

  // Règles détaillées injectées dans le prompt utilisateur pour guider la décision
  const policy = `
RUBRIQUE DE DÉCISION (résumée):

A. QUAND BLOQUER (blocked = true) — cas graves et clairs (pas ambigus):
  1) Sexualité impliquant des mineurs (référence explicite, érotisation, incitation).
  2) Apologie explicite et sérieuse de terrorisme/extrémisme réel, ou appel à rejoindre/soutenir.
  3) Appels explicites à la violence ou à un préjudice grave contre une personne/groupe réel.
  4) Déshumanisation évidente et sérieuse de groupes protégés couplée à menace/incitation.
  5) Instruction ou encouragement clair à commettre des crimes violents.
  6) Nazisme/fascisme réel promu (praise/advocacy) ou symboles glorifiés sans critique.
  7) Partage de contenu sexuel non consenti réel (ex: doxxing sexuel, vengeance) ou exploitation.

B. QUAND NE PAS BLOQUER (blocked = false, mais flagged peut être true):
  - Œuvres qui mettent en scène un personnage toxique pour le critiquer, le montrer ou le contextualiser.
  - Citations, descriptions historiques, satire, ironie, mises en garde, ou re-appropriation par les communautés visées.
  - Métaphores violentes ou langage dur SANS cible réelle ni intention d'incitation.
  - Références à des thèmes sombres dans un but narratif/éducatif/critique, sans promotion réelle.

C. SCORE ET SEVERITY:
  - Calibre "score" selon l'impact potentiel et la clarté du tort.
  - Indice: low≈0.1–0.3, medium≈0.3–0.6, high≈0.6–0.85, critical>0.85.
  - Utilise severity par extrait pour refléter le pire passage.

D. RELECTURE DE CONTEXTE:
  - Demande-toi si le passage est: (i) narration/roleplay/critique —> plutôt allow; (ii) injonction réelle —> plutôt block.
  - Si ambigu OU probablement artistique, NE PAS bloquer: flagged=true si nécessaire, blocked=false.
`.trim();

  const userPrompt = `
Analyse de paroles de chanson
Titre: ${title || "Sans titre"}

Paroles:
"""
${lyricsText}
"""

Consigne:
- Applique la RUBRIQUE DE DÉCISION ci-dessous pour autoriser la création artistique tout en bloquant les cas réellement dangereux.
- "blocked" = true UNIQUEMENT pour les cas A.1 à A.7 (non ambigus).
- Si le contenu semble artistique (récit, personnage, satire, citation, dénonciation) et ne fait pas d'apologie/incitation réelle: "blocked" = false (tu peux mettre "flagged" = true + "score" adapté).
- Donne des "excerpts" courts (citations EXACTES) si possible.

${policy}
`.trim();

  const { output } = await genkit({
    plugins: [googleAI({ apiKey: GEMINI_API_KEY })],
    model: googleAI.model("gemini-2.5-flash"),
  }).generate({
    system,
    prompt: userPrompt,
    output: { schema: moderationSchema },
  });

  return output;
};

exports.generateImageV2 = async (prompt, size = 1024) => {
  if (typeof prompt !== "string" || prompt.trim().length < 1) {
    throw new Error("Vous devez spécifier un prompt (string) non vide.");
  }

  try {
    console.log("🚀 [generateImageV2] Démarrage de la génération d'image", {
      promptLength: prompt.trim().length,
      size,
    });

    // Ajouter explicitement la taille au prompt si elle n'est pas mentionnée
    if (!prompt.includes("1024x1024") && !prompt.includes(size + "x" + size)) {
      prompt = `Image carrée ${size}x${size} pixels. ${prompt}`;
    }

    const ai = genkit({
      plugins: [googleAI({ apiKey: GEMINI_API_KEY })],
    });

    const imageModel = googleAI.model("gemini-2.5-flash-image-preview", {
      responseModalities: ["IMAGE"],
      // L'API n'accepte pas generationConfig comme paramètre direct
      // Les paramètres de génération doivent être passés lors de l'appel generate()
    });

    console.log("🔄 [generateImageV2] Envoi du prompt au modèle");

    // Ajouter un timeout et des tentatives
    let attempts = 0;
    const maxAttempts = 3;
    let res;

    while (attempts < maxAttempts) {
      try {
        attempts++;
        console.log(
          `🔁 [generateImageV2] Tentative ${attempts}/${maxAttempts}`,
        );

        // Utiliser Promise.race pour ajouter un timeout
        const timeout = new Promise((_, reject) => {
          setTimeout(() => reject(new Error("Timeout dépassé (30s)")), 30000);
        });

        res = await Promise.race([
          ai.generate({
            model: imageModel,
            prompt: prompt.trim(),
            // Paramètres de génération passés dans un objet generation_config
            generation_config: {
              temperature: 0.7,
              top_k: 40,
              top_p: 0.95,
              max_output_tokens: 8192,
            },
          }),
          timeout,
        ]);

        if (res?.media?.url) {
          console.log(
            "✅ [generateImageV2] Image générée avec succès à la tentative",
            attempts,
          );
          break; // Sortir de la boucle si on a une image
        } else {
          console.warn(
            `⚠️ [generateImageV2] Tentative ${attempts} sans média, nouvelle tentative...`,
          );
          // Attendre un peu avant de réessayer
          await new Promise((resolve) => setTimeout(resolve, 2000));
        }
      } catch (err) {
        console.error(
          `❌ [generateImageV2] Erreur à la tentative ${attempts}:`,
          err.message,
        );
        if (attempts >= maxAttempts) throw err;
        // Attendre un peu plus longtemps après une erreur
        await new Promise((resolve) => setTimeout(resolve, 3000));
      }
    }

    console.log("📊 [generateImageV2] Réponse finale", {
      hasMedia: !!res?.media,
      hasUrl: !!res?.media?.url,
      responseKeys: Object.keys(res || {}).join(", "),
    });

    const media = res?.media;
    if (!media || !media.url) {
      console.error(
        "❌ [generateImageV2] Pas de média dans la réponse après toutes les tentatives",
        {
          response: JSON.stringify(res || {}).substring(0, 500),
          prompt: prompt.substring(0, 200) + "...",
        },
      );
      throw new Error(
        "La génération n'a pas retourné de média image après plusieurs tentatives.",
      );
    }

    const dataUrl = String(media.url);
    const commaIdx = dataUrl.indexOf(",");
    if (commaIdx === -1) throw new Error("FORMAT_DATA_URL_INVALIDE");
    const header = dataUrl.substring(0, commaIdx);
    const b64 = dataUrl.substring(commaIdx + 1);

    let mimeType = media.contentType || "image/png";
    const headerMatch = header.match(/^data:([^;]+);base64$/i);
    if (headerMatch && headerMatch[1]) mimeType = headerMatch[1];

    const buffer = Buffer.from(b64, "base64");
    const bucket = admin.storage().bucket();
    const ext =
      mimeType === "image/jpeg"
        ? "jpg"
        : mimeType === "image/webp"
          ? "webp"
          : "png";
    const token = require("crypto").randomUUID();
    const path = `generated/images/${Date.now()}-${token}.${ext}`;

    console.log(
      "💾 [generateImageV2] Sauvegarde de l'image dans Firebase Storage",
    );
    await bucket.file(path).save(buffer, {
      resumable: false,
      metadata: {
        contentType: mimeType,
        cacheControl: "public, max-age=31536000",
        metadata: { firebaseStorageDownloadTokens: token },
      },
    });

    const publicUrl = `https://firebasestorage.googleapis.com/v0/b/${bucket.name}/o/${encodeURIComponent(
      path,
    )}?alt=media&token=${token}`;

    console.log("✅ [generateImageV2] Image générée avec succès");
    return publicUrl;
  } catch (error) {
    console.error(
      "❌ [generateImageV2] Erreur lors de la génération d'image:",
      error,
    );
    throw new Error(`Erreur lors de la génération d'image: ${error.message}`);
  }
};

// Combine deux images avec Gemini (background = cover abstraite, foreground = photo utilisateur détourée)
// Entrées possibles pour chaque image:
// - string data URL ("data:image/...;base64,....")
// - string URL http(s)
// - { base64: string, mimeType: string }
// - { url: string, mimeType?: string }
// Sortie: URL publique de l'image composite (PNG 1024x1024)
exports.CombineCoverAndPicture = async (
  backgroundImage,
  foregroundImage,
  options = {},
) => {
  const size = Number(options.size || 1024);
  console.log("🧩 [CombineCoverAndPicture] Start", { size });

  // Convertit diverses entrées (URL http(s), data URL, {base64, mimeType}) en data URL
  const toDataUrl = async (input, fallbackMime = "image/png") => {
    if (!input) return null;
    try {
      if (typeof input === "string") {
        if (input.startsWith("data:")) return input;
        if (/^https?:\/\//i.test(input)) {
          const axios = require("axios");
          const resp = await axios.get(input, { responseType: "arraybuffer" });
          const mime = resp.headers["content-type"] || fallbackMime;
          const b64 = Buffer.from(resp.data).toString("base64");
          return `data:${mime};base64,${b64}`;
        }
        // Base64 brut -> fallback
        if (/^[A-Za-z0-9+/=]+$/.test(input) && input.length > 256) {
          return `data:${fallbackMime};base64,${input}`;
        }
        return input;
      }
      if (typeof input === "object") {
        if (input.url) {
          if (input.url.startsWith("data:")) return input.url;
          if (/^https?:\/\//i.test(input.url)) {
            const axios = require("axios");
            const resp = await axios.get(input.url, {
              responseType: "arraybuffer",
            });
            const mime =
              input.mimeType || resp.headers["content-type"] || fallbackMime;
            const b64 = Buffer.from(resp.data).toString("base64");
            return `data:${mime};base64,${b64}`;
          }
          if (input.base64)
            return `data:${input.mimeType || fallbackMime};base64,${input.base64}`;
          return input.url;
        }
        if (input.base64) {
          return `data:${input.mimeType || fallbackMime};base64,${input.base64}`;
        }
      }
    } catch (e) {
      console.warn("toDataUrl: échec de conversion en data URL", e?.message);
    }
    return null;
  };

  const bgUrl = await toDataUrl(backgroundImage, "image/png");
  const fgUrl = await toDataUrl(foregroundImage, "image/png");
  if (!bgUrl || !fgUrl) {
    throw new Error(
      "CombineCoverAndPicture: images d'entrée invalides (background/foreground requis)",
    );
  }

  const ai = genkit({ plugins: [googleAI({ apiKey: GEMINI_API_KEY })] });
  const model = googleAI.model("gemini-2.5-flash-image-preview", {
    responseModalities: ["IMAGE"],
  });

  // Instruction en FR pour un compositing propre
  const instruction = `
Tu reçois 2 images: la première est l'arrière-plan, la seconde est une photo à incruster.
1) Crée une toile carrée ${size}x${size} et place l'arrière-plan en plein cadre, sans bords vides (recadrer si nécessaire).
2) Détoure le sujet principal de la seconde image.
3) Place le sujet détouré par-dessus l'arrière-plan, et essaie de faire une jolie pochette.
4) Aucun texte, aucun logo, aucun filigrane. Export en PNG.
`.trim();

  // Construire le prompt avec parties media
  // Utiliser des data URLs pour garantir la prise en compte des 2 images
  const parts = [
    { text: instruction },
    { media: { url: bgUrl } },
    { media: { url: fgUrl } },
  ];

  console.log("🧩 [CombineCoverAndPicture] Entrées prêtes", {
    bgIsDataUrl: typeof bgUrl === "string" && bgUrl.startsWith("data:"),
    fgIsDataUrl: typeof fgUrl === "string" && fgUrl.startsWith("data:"),
    bgLen: (bgUrl || "").length,
    fgLen: (fgUrl || "").length,
  });

  let res;
  try {
    res = await ai.generate({ model, prompt: parts });
  } catch (err) {
    console.error(
      "❌ [CombineCoverAndPicture] ai.generate failed",
      err?.message || err,
    );
    throw err;
  }
  const media = res?.media;
  if (!media || !media.url) {
    throw new Error("La génération n'a pas retourné d'image composite.");
  }

  const dataUrl = String(media.url);
  const commaIdx = dataUrl.indexOf(",");
  if (commaIdx === -1) throw new Error("FORMAT_DATA_URL_INVALIDE");
  const header = dataUrl.substring(0, commaIdx);
  const b64 = dataUrl.substring(commaIdx + 1);

  let mimeType = media.contentType || "image/png";
  const headerMatch = header.match(/^data:([^;]+);base64$/i);
  if (headerMatch && headerMatch[1]) mimeType = headerMatch[1];

  const buffer = Buffer.from(b64, "base64");
  const bucket = admin.storage().bucket();
  const ext =
    mimeType === "image/jpeg"
      ? "jpg"
      : mimeType === "image/webp"
        ? "webp"
        : "png";
  const token = require("crypto").randomUUID();
  const path = `generated/combined/${Date.now()}-${token}.${ext}`;

  await bucket.file(path).save(buffer, {
    resumable: false,
    metadata: {
      contentType: mimeType,
      cacheControl: "public, max-age=31536000",
      metadata: { firebaseStorageDownloadTokens: token },
    },
  });

  const publicUrl = `https://firebasestorage.googleapis.com/v0/b/${bucket.name}/o/${encodeURIComponent(
    path,
  )}?alt=media&token=${token}`;
  console.log("✅ [CombineCoverAndPicture] Uploaded", {
    path,
    mimeType,
    urlLen: publicUrl.length,
  });
  return publicUrl;
};
