const { onCall, onRequest } = require("firebase-functions/v2/https");
const axios = require("axios");
const admin = require("firebase-admin");
const { logger } = require("firebase-functions/logger");
const { SUNO_API_KEY } = require("../config/keys");

// Initialiser Firestore
const db = admin.firestore();

/**
 * ===== CONFIG GLOBALE =====
 */
const SUNO_API_BASE = "https://api.sunoapi.org";
const SUNO_API_PATH = "/api/v1/generate";
const SUNO_STATUS_PATH = "/api/v1/generate/record-info";
const SUNO_TIMESTAMPED_LYRICS_PATH = "/api/v1/generate/get-timestamped-lyrics";
const SUNO_MODEL = "V4_5";
// const SUNO_MODEL = "V3_5";
const SUNO_CALLBACK_URL =
  "https://us-central1-musicland-d33f9.cloudfunctions.net/music-sunoCallback";

/**
 * Limite la longueur d'une chaîne
 * @param {string} str - La chaîne à limiter
 * @param {number} max - La longueur maximale
 * @return {string} La chaîne limitée
 */
function clampLen(str = "", max) {
  if (!max) return str || "";
  if (!str) return "";
  return str.length <= max ? str : str.slice(0, max);
}

/**
 * Construit le style musical à partir des paramètres
 * @param {Object} params - Les paramètres de style
 * @return {string} Le style formaté
 */
function buildStyle({ genres = [], voice = "", instruments = [], tempo = "" }) {
  const flatGenres = (genres || []).map((g) => ("" + g).trim()).filter(Boolean);
  const flatInstruments = (instruments || [])
    .map((i) => ("" + i).trim())
    .filter(Boolean);

  const styleParts = [
    flatGenres.length ? `Genres: ${flatGenres.join(", ")}` : "",
    tempo ? `Tempo: ${tempo}` : "",
    flatInstruments.length ? `Instruments: ${flatInstruments.join(", ")}` : "",
    voice ? `Vocal: ${voice}` : "",
    "Mix: propre, punchy, large stéréo, radio-ready",
  ].filter(Boolean);

  const style = styleParts.join(" ; ");
  return clampLen(style, 600);
}

/**
 * Convertit les paroles en prompt avec tags
 * @param {Array} lyrics - Tableau des paroles
 * @return {string} Prompt formaté avec tags
 */
function lyricsToTaggedPrompt(lyrics = []) {
  if (!Array.isArray(lyrics) || lyrics.length === 0) {
    return "";
  }

  const sections = lyrics
    .map((section) => {
      if (!section || typeof section !== "object") return "";
      const { type = "verse", lyrics: content = "" } = section;
      if (!content.trim()) return "";

      const tag = type.toLowerCase();
      return `[${tag}]\n${content.trim()}`;
    })
    .filter(Boolean);

  return sections.join("\n\n");
}

/**
 * Construit le prompt pour Suno
 * @param {Object} params - Paramètres du prompt
 * @return {string} Prompt formaté
 */
function buildSunoPrompt({
  title,
  audience = "",
  projectContext = "",
  emotionGuide = "",
  styleGuide = "",
}) {
  const parts = [
    title ? `Titre: ${title}` : "",
    audience ? `Public cible: ${audience}` : "",
    projectContext ? `Contexte: ${projectContext}` : "",
    emotionGuide ? `Émotion: ${emotionGuide}` : "",
    styleGuide ? `Style: ${styleGuide}` : "",
  ].filter(Boolean);

  return parts.join("\n\n");
}

/**
 * Construit les guides de style et d'émotion
 * @param {Object} params - Paramètres des guides
 * @return {Object} Guides formatés
 */
function buildGuides({
  genres = [],
  tempo = "",
  voice = "",
  instruments = [],
}) {
  const styleElements = [];

  if (genres && genres.length > 0) {
    styleElements.push(`Genres: ${genres.join(", ")}`);
  }

  if (tempo) {
    const tempoGuide = tempo.toLowerCase().includes("lent")
      ? "Rythme lent et contemplatif"
      : tempo.toLowerCase().includes("rapide")
        ? "Rythme énergique et dynamique"
        : `Tempo ${tempo.toLowerCase()}.`;
    styleElements.push(tempoGuide);
  }

  if (instruments && instruments.length > 0) {
    styleElements.push(`Instruments principaux: ${instruments.join(", ")}`);
  }

  if (voice) {
    styleElements.push(`Style vocal: ${voice}`);
  }

  return {
    styleGuide: styleElements.join(" "),
    emotionGuide: "",
  };
}

/**
 * Fonction principale de génération de musique
 */
exports.generateMusic = onCall(async ({ data = {} }) => {
  try {
    const {
      title = "",
      lyrics = [],
      genres = [],
      voice = "",
      instruments = [],
      tempo = "",
      audience = "",
      projectContext = "",
    } = data;

    const { styleGuide, emotionGuide } = buildGuides({
      genres,
      voice,
      instruments,
      tempo,
    });
    const taggedLyrics = lyricsToTaggedPrompt(lyrics);

    const safeTitle = clampLen(title, 80);

    // Construire les informations de contexte pour le style
    const contextInfo = buildSunoPrompt({
      title: safeTitle,
      audience,
      projectContext,
      emotionGuide,
      styleGuide,
    });

    // Le style combine les informations de genre ET le contexte
    const baseStyle = buildStyle({ genres, voice, instruments, tempo });
    const enhancedStyle = `${baseStyle}. ${contextInfo}`;

    const payload = {
      customMode: true,
      instrumental: false,
      model: SUNO_MODEL,
      prompt: clampLen(taggedLyrics, 3000),
      title: safeTitle,
      style: clampLen(enhancedStyle, 600),
      callBackUrl: SUNO_CALLBACK_URL || "",
    };

    console.log("PAYLOAD", payload);

    const response = await axios.post(
      `${SUNO_API_BASE}${SUNO_API_PATH}`,
      payload,
      {
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${SUNO_API_KEY}`,
        },
      }
    );

    const parsed = response.data;

    return {
      success: !!parsed?.data?.taskId,
      request: {
        model: SUNO_MODEL,
        instrumental: false,
        title: safeTitle,
        style: enhancedStyle,
        prompt: taggedLyrics,
      },
      response: parsed || {},
    };
  } catch (error) {
    console.error("❌ Erreur lors de la génération de musique:", error);
    throw new Error(
      `Erreur lors de la génération de musique: ${error.message}`
    );
  }
});

/**
 * Fonction pour suivre le statut d'une génération de musique Suno
 */
exports.getSunoStatus = onCall(async ({ data = {} }) => {
  try {
    const { taskId } = data;

    if (!taskId) {
      throw new Error("TaskId manquant");
    }

    console.log("🔍 Vérification du statut Suno", { taskId });

    let response;
    let parsed;

    try {
      response = await axios.get(
        `${SUNO_API_BASE}${SUNO_STATUS_PATH}?taskId=${taskId}`,
        {
          headers: {
            Authorization: `Bearer ${SUNO_API_KEY}`,
          },
          timeout: 30000, // 30 secondes de timeout
        }
      );
      parsed = response.data;

      console.log("📊 Réponse Suno API:", parsed);
    } catch (error) {
      console.error("❌ Erreur détaillée API Suno Status:", {
        message: error.message,
        status: error.response?.status,
        statusText: error.response?.statusText,
        data: error.response?.data,
        config: {
          url: error.config?.url,
          headers: error.config?.headers,
        },
      });

      // Gestion spéciale pour les taskId de test
      if (
        error.response &&
        error.response.status === 404 &&
        taskId.includes("test")
      ) {
        return {
          success: true,
          taskId,
          data: {
            status: "not_found",
            message: "TaskId de test - tâche non trouvée dans l'API Suno",
            isTestId: true,
          },
        };
      }

      // Retourner une erreur plus descriptive
      const errorStatus = error.response?.status || "UNKNOWN";
      const errorMessage =
        error.response?.data?.msg ||
        error.response?.data?.message ||
        error.response?.statusText ||
        error.message;

      return {
        success: false,
        taskId,
        error: {
          status: errorStatus,
          message: errorMessage,
          type: "API_ERROR",
        },
      };
    }

    console.log("✅ Statut Suno récupéré", {
      taskId,
      status: parsed?.data?.status || parsed?.status,
    });

    return {
      success: true,
      taskId,
      data: parsed?.data || parsed,
    };
  } catch (error) {
    console.error("❌ Erreur lors de la vérification du statut:", error);

    return {
      success: false,
      taskId: data?.taskId,
      error: {
        message: error.message,
        type: "INTERNAL_ERROR",
      },
    };
  }
});

/**
 * Récupère les timestamps (aligned words) pour une génération Suno
 * Attend: { taskId: string, musicIndex: number, projectId: string }
 */
exports.getSunoTimestamps = onCall(async ({ data = {} }) => {
  try {
    const { taskId, musicIndex, projectId } = data || {};

    if (!taskId) {
      throw new Error("TaskId manquant");
    }

    const index = Number(musicIndex);
    if (!Number.isFinite(index) || index < 0) {
      throw new Error("musicIndex invalide");
    }

    if (!projectId || typeof projectId !== "string" || !projectId.trim()) {
      throw new Error("projectId manquant ou invalide");
    }

    console.log("🔎 Récupération des timestamps Suno", {
      taskId,
      musicIndex: index,
    });

    let response;
    let parsed;

    try {
      response = await axios.post(
        `${SUNO_API_BASE}${SUNO_TIMESTAMPED_LYRICS_PATH}`,
        { taskId, musicIndex: index },
        {
          headers: {
            "Content-Type": "application/json",
            Authorization: `Bearer ${SUNO_API_KEY}`,
          },
          timeout: 30000,
        }
      );
      parsed = response.data;
      console.log("📊 Réponse timestamps Suno API:", parsed?.code, parsed?.msg);
    } catch (error) {
      console.error("❌ Erreur API Suno Timestamps:", {
        message: error.message,
        status: error.response?.status,
        statusText: error.response?.statusText,
        data: error.response?.data,
        config: {
          url: error.config?.url,
          headers: error.config?.headers
            ? { ...error.config.headers, Authorization: "[redacted]" }
            : undefined,
        },
      });

      const errorStatus = error.response?.status || "UNKNOWN";
      const errorMessage =
        error.response?.data?.msg ||
        error.response?.data?.message ||
        error.response?.statusText ||
        error.message;

      return {
        success: false,
        taskId,
        musicIndex: index,
        error: {
          status: errorStatus,
          message: errorMessage,
          type: "API_ERROR",
        },
      };
    }

    const dataToReturn = parsed?.data || parsed || {};

    // Sauvegarde obligatoire dans le document projet
    let saved = false;
    try {
      await db
        .collection("projects")
        .doc(projectId)
        .set(
          {
            musicTimestamps: {
              [musicIndex]: dataToReturn,
            },
            updatedAt: admin.firestore.FieldValue.serverTimestamp(),
          },
          { merge: true }
        );
      saved = true;
      console.log("💾 Timestamps sauvegardés dans Firestore", {
        projectId,
        index,
      });
    } catch (e) {
      console.error(
        "❌ Échec sauvegarde timestamps Firestore:",
        e?.message || e
      );
    }

    return {
      success: true,
      taskId,
      musicIndex: index,
      projectId,
      saved,
      musicTimestamps: dataToReturn,
    };
  } catch (error) {
    console.error("❌ Erreur interne getSunoTimestamps:", error);
    return {
      success: false,
      taskId: data?.taskId,
      musicIndex: data?.musicIndex,
      error: {
        message: error.message,
        type: "INTERNAL_ERROR",
      },
    };
  }
});

/**
 * Cloud Function pour recevoir les callbacks de l'API Suno
 * Cette fonction est appelée par l'API Suno lorsque la génération
 * de musique est terminée
 */
exports.sunoCallback = onRequest({ methods: ["POST"] }, async (req, res) => {
  try {
    console.log("🎵 [SunoCallback] Reçu:", JSON.stringify(req.body));

    if (req.method !== "POST") {
      console.warn("⚠️ [SunoCallback] Méthode non autorisée:", req.method);
      return res.status(405).json({ error: "Méthode non autorisée" });
    }

    const body = req.body || {};
    const code = body.code ?? body.statusCode ?? null;
    const callbackType = (body?.data?.callbackType || "")
      .toString()
      .toLowerCase();
    const status = (body.status || body.state || callbackType)
      .toString()
      .toLowerCase();
    const taskId =
      body.taskId ||
      body.task_id ||
      body?.data?.taskId ||
      body?.data?.task_id ||
      null;
    const tracks = Array.isArray(body?.data?.data)
      ? body.data.data
      : Array.isArray(body.data)
        ? body.data
        : [];

    console.log("🎯 [SunoCallback] Détails:", {
      code,
      status,
      callbackType,
      taskId,
      count: tracks.length,
    });

    // On n'agit que si code === 200 et status === "complete"
    if (code !== 200 || status !== "complete") {
      console.log("ℹ️ [SunoCallback] Callback ignoré (code/status)", {
        code,
        status,
      });
      return res.status(200).json({ success: true, ignored: true });
    }

    if (!taskId) {
      console.warn("⚠️ [SunoCallback] taskId manquant dans le callback");
      return res.status(200).json({ success: true, ignored: true });
    }

    // 1) Récupérer le projectId associé au taskId
    let projectId = null;
    try {
      const projSnap = await db
        .collection("projects")
        .where("sunoTaskId", "==", taskId)
        .limit(1)
        .get();
      if (!projSnap.empty) {
        projectId = projSnap.docs[0].id;
      }
    } catch (e) {
      console.error("❌ [SunoCallback] Erreur lookup project par taskId:", e);
    }

    if (!projectId) {
      console.warn("⚠️ [SunoCallback] Aucun projet trouvé pour", { taskId });
      return res.status(200).json({ success: true, ignored: true });
    }

    // 2) Extraire jusqu'à 2 URLs audio
    const audioUrls = tracks
      .map(
        (t) =>
          t.audio_url || t.audioUrl || t.stream_audio_url || t.streamAudioUrl
      )
      .filter(Boolean)
      .slice(0, 2);

    if (audioUrls.length < 2) {
      console.warn(
        "⚠️ [SunoCallback] Moins de 2 pistes audio dans le callback",
        {
          found: audioUrls.length,
          tracksSample: tracks.map((t) => ({
            id: t.id,
            has_audio_url: !!t.audio_url,
            has_stream_audio_url: !!t.stream_audio_url,
          })),
        }
      );
    }

    // 3) Télécharger et sauvegarder dans Cloud Storage + récupérer download URLs
    const bucket = admin.storage().bucket();
    console.log("🪣 [SunoCallback] Bucket:", bucket.name);
    const saveOne = async (url, index) => {
      if (!url) return null;
      try {
        console.log(`⬇️ [SunoCallback] Téléchargement piste ${index + 1}`);
        const resp = await axios.get(url, { responseType: "arraybuffer" });
        const buffer = Buffer.from(resp.data);
        const path = `musics/${projectId}/sound${index + 1}.mp3`;
        const token = require("crypto").randomUUID();
        const file = bucket.file(path);
        await file.save(buffer, {
          resumable: false,
          metadata: {
            contentType: "audio/mpeg",
            cacheControl: "public, max-age=31536000",
            metadata: { firebaseStorageDownloadTokens: token },
          },
        });
        const downloadUrl = `https://firebasestorage.googleapis.com/v0/b/${bucket.name}/o/${encodeURIComponent(
          path
        )}?alt=media&token=${token}`;
        console.log("✅ [SunoCallback] Sauvegardé:", path, "URL:", downloadUrl);
        return { path, url: downloadUrl };
      } catch (e) {
        console.error(
          `❌ [SunoCallback] Échec save piste ${index + 1}:`,
          e.message
        );
        return null;
      }
    };

    const [p1, p2] = await Promise.all([
      saveOne(audioUrls[0], 0),
      saveOne(audioUrls[1], 1),
    ]);

    // 4) Mettre à jour le statut du projet
    try {
      const musicUrls = [p1?.url, p2?.url].filter(Boolean);
      await db.collection("projects").doc(projectId).set(
        {
          musicStatus: "GENERATED",
          musicUrls,
          updatedAt: admin.firestore.FieldValue.serverTimestamp(),
        },
        { merge: true }
      );
      console.log("🏷️ [SunoCallback] Projet marqué GENERATED", {
        projectId,
        musicUrlsCount: musicUrls.length,
      });
    } catch (e) {
      console.error("❌ [SunoCallback] Erreur maj projet:", e.message);
    }

    return res.status(200).json({
      success: true,
      projectId,
      saved: [p1, p2].filter(Boolean),
    });
  } catch (error) {
    logger.error("❌ [SunoCallback] Erreur interne:", error);
    res
      .status(500)
      .json({ error: "Erreur interne du serveur", message: error.message });
  }
});
