import { flags } from "@oclif/command"
// import multer from "multer"
// import * as ytdl from "ytdl-core"
import { YoutubeTranscript } from "youtube-transcript"
import * as express from "express"
import * as cors from "cors"
import * as path from "path"
import * as http from "http"

import { initSocketIO, emitToCourse } from "../utils/creatorSocket"

import * as os from "os"
import * as archiver from "archiver"
import * as mkdirp from "mkdirp"
import { convert } from "html-to-text"
import * as rimraf from "rimraf"
import SessionCommand from "../utils/SessionCommand"
import { Bucket, Storage } from "@google-cloud/storage"
import { downloadEditor, decompress } from "../managers/file"
import * as fs from "fs"
import {
  createCodeFile,
  translateExercise,
  isValidRigoToken,
  readmeCreator,
  makeReadmeReadable,
  generateImage,
  isPackageAuthor,
} from "../utils/rigoActions"
import * as dotenv from "dotenv"
import {
  extractImagesFromMarkdown,
  getFilenameFromUrl,
} from "../utils/creatorUtilities"
// import { handleAssetCreation } from "./publish"
import axios from "axios"
import * as FormData from "form-data"
import api, { RIGOBOT_HOST } from "../utils/api"
import { createUploadMiddleware, minutesToISO8601Duration } from "../utils/misc"
import { buildConfig, ConfigResponse } from "../utils/configBuilder"
import { checkReadability, slugify } from "../utils/creatorUtilities"
import { checkAndFixSidebarPure } from "../utils/sidebarGenerator"
import { handleAssetCreation } from "./publish"
const frontMatter = require("front-matter")

dotenv.config()

export const createLearnJson = (courseInfo: FormState) => {
  console.log("courseInfo to create learn json", courseInfo)

  const expectedPreviewUrl = `https://${slugify(
    courseInfo.title as string
  )}.learn-pack.com/preview.png`
  console.log("Preview url in generated learn.json", expectedPreviewUrl)

  const learnJson = {
    slug: slugify(courseInfo.title as string),
    title: {
      us: courseInfo.title,
    },
    technologies: courseInfo.technologies || [],
    difficulty: "beginner",
    description: {
      us: courseInfo.description,
    },
    grading: "isolated",
    telemetry: {
      batch: "https://breathecode.herokuapp.com/v1/assignment/me/telemetry",
    },
    preview: expectedPreviewUrl,
  }
  return learnJson
}

const uploadFileToBucket = async (
  bucket: Bucket,
  file: string,
  path: string
) => {
  const fileRef = bucket.file(path)
  await fileRef.save(file)
}

const uploadImageToBucket = async (
  bucket: Bucket,
  url: string,
  path: string
) => {
  const response = await fetch(url)
  if (!response.ok) {
    throw new Error(`Failed to download image: ${response.statusText}`)
  }

  const contentType =
    response.headers.get("content-type") || "application/octet-stream"
  const buffer = await response.arrayBuffer()

  const fileRef = bucket.file(path)
  await fileRef.save(Buffer.from(buffer), {
    resumable: false,
    contentType,
  })
}

const PARAMS = {
  expected_grade_level: "7",
  max_fkgl: 9,
  max_words: 200,
  max_rewrite_attempts: 2,
  max_title_length: 50,
}

export const processImage = async (
  bucket: Bucket,
  tutorialDir: string,
  url: string,
  description: string,
  rigoToken: string
) => {
  try {
    const filename = getFilenameFromUrl(url)

    const imagePath = `${tutorialDir}/.learn/assets/${filename}`

    console.log("🖼️ Generating image", imagePath)

    const res = await generateImage(rigoToken, { prompt: description })
    await uploadImageToBucket(bucket, res.image_url, imagePath)

    console.log("✅ Image", imagePath, "generated successfully!")
    return true
  } catch {
    return false
  }
}

const createInitialSidebar = async (slugs: string[]) => {
  const sidebar: Record<string, Record<string, string>> = {}
  for (const slug of slugs) {
    sidebar[slug] = {
      us: slug,
    }
  }

  return sidebar
}

export async function processExercise(
  bucket: Bucket,
  rigoToken: string,
  steps: Lesson[],
  packageContext: string,
  exercise: Lesson,
  exercisesDir: string,
  courseSlug: string
): Promise<string> {
  //   const tid = toast.loading("Generating lesson...")
  const exSlug = slugify(exercise.id + "-" + exercise.title)
  console.log("exSlug", exSlug)

  const readmeFilename = "README.md"
  const targetDir = `${exercisesDir}/${exSlug}`

  console.log("✍🏻 Generating lesson", exercise.id, exercise.title)

  const isGeneratingText = `
  \`\`\`loader slug="${exSlug}"
  :rigo
  \`\`\`
      `

  await uploadFileToBucket(
    bucket,
    isGeneratingText,
    `${targetDir}/${readmeFilename}`
  )

  const readme = await readmeCreator(rigoToken, {
    title: `${exercise.id} - ${exercise.title}`,
    output_lang: "en",
    list_of_exercises: JSON.stringify(steps),
    tutorial_description: packageContext,
    lesson_description: exercise.description,
    kind: exercise.type.toLowerCase(),
  })

  const duration = exercise.duration
  let attempts = 0
  let readability = checkReadability(readme.parsed.content, 200, duration || 1)

  while (
    readability.fkglResult.fkgl > PARAMS.max_fkgl &&
    attempts < PARAMS.max_rewrite_attempts
  ) {
    emitToCourse(courseSlug, "course-creation", {
      lesson: exSlug,
      status: "generating",
      log: `🔄 The lesson ${exercise.id} - ${exercise.title} has a readability score of ${readability.fkglResult.fkgl}`,
    })

    // eslint-disable-next-line
    const reducedReadme = await makeReadmeReadable(rigoToken, {
      lesson: readability.body,
      number_of_words: readability.minutes.toString(),
      expected_number_words: PARAMS.max_words.toString(),
      fkgl_results: JSON.stringify(readability.fkglResult),
      expected_grade_level: PARAMS.expected_grade_level,
    })

    if (!reducedReadme) 
break

    readability = checkReadability(
      reducedReadme.parsed.content,
      PARAMS.max_words,
      duration || 1
    )

    attempts++
  }

  await uploadFileToBucket(
    bucket,
    readability.newMarkdown,
    `${targetDir}/${readmeFilename}`
  )
  emitToCourse(courseSlug, "course-creation", {
    lesson: exSlug,
    status: "done",
    log: `✅ The lesson ${exercise.id} - ${exercise.title} has been generated successfully!`,
  })

  if (exercise.type.toLowerCase() === "code") {
    console.log("🔍 Creating code file for", exercise.title)

    const codeFile = await createCodeFile(rigoToken, {
      readme: readability.newMarkdown,
      tutorial_info: packageContext,
    })
    await uploadFileToBucket(
      bucket,
      codeFile.parsed.content,
      `${targetDir}/index.${codeFile.parsed.extension.replace(".", "")}`
    )
    emitToCourse(courseSlug, "course-creation", {
      lesson: exSlug,
      status: "done",
      log: `✅ The code file for ${exercise.title} has been generated successfully!`,
    })
  }

  return readability.newMarkdown
}

interface Lesson {
  id: string
  uid: string
  title: string
  type: "READ" | "CODE" | "QUIZ"
  description: string
  duration?: number
}

interface ParsedLink {
  name: string
  text: string
}
type FormState = {
  description: string
  duration: number
  targetAudience: string
  hasContentIndex: boolean
  contentIndex: string
  sources: ParsedLink[]
  isCompleted: boolean
  variables: string[]
  currentStep: string
  title: string
  technologies?: string[]
}

type Syllabus = {
  lessons: Lesson[]
  courseInfo: FormState
  sources: ParsedLink[]
}

const fixPreviewUrl = (slug: string, previewUrl: string) => {
  if (!previewUrl) {
    return null
  }

  if (previewUrl.startsWith("http")) {
    return previewUrl
  }

  const expectedUrl = `https://${slug}.learn-pack.com/preview.png`
  console.log("Preview url fixed!", expectedUrl)
  return expectedUrl
}

const getTitleFromHTML = (html: string) => {
  const titleRegex = /<title>(.*?)<\/title>/
  const titleMatch = html.match(titleRegex)
  return titleMatch ? titleMatch[1] : null
}

export default class ServeCommand extends SessionCommand {
  static description = "Runs a small server to build tutorials"

  static flags = {
    ...SessionCommand.flags,
    port: flags.string({ char: "p", description: "server port" }),
    host: flags.string({ char: "h", description: "server host" }),
    debug: flags.boolean({
      char: "d",
      description: "debugger mode for more verbage",
      default: false,
    }),
  }

  async init() {
    const { flags } = this.parse(ServeCommand)
    console.log("Initializing serve command")
  }

  async run() {
    const crendsEnv = process.env.GCP_CREDENTIALS_JSON
    if (!crendsEnv) {
      console.log("GCP_CREDENTIALS_JSON is not set")
      process.exit(1)
    }

    const credentials = JSON.parse(crendsEnv)
    const bucketStorage = new Storage({
      credentials,
    })

    const bucket = bucketStorage.bucket(
      process.env.GCP_BUCKET_NAME || "learnpack-packages"
    )

    // async function listFilesWithPrefix(prefix: string) {
    //   const [files] = await bucket.getFiles({ prefix })
    //   return files
    // }

    const app = express()
    const server = http.createServer(app)
    initSocketIO(server)

    const PORT = process.env.PORT || 3000

    const distPath = path.resolve(__dirname, "../creatorDist")

    // Servir archivos estáticos
    // app.use(express.static(distPath))
    app.use(express.json())
    app.use(cors())

    const appPath = path.resolve(__dirname, "../ui/_app")
    const tarPath = path.resolve(__dirname, "../ui/app.tar.gz")
    if (fs.existsSync(appPath)) {
      fs.rmSync(appPath, { recursive: true })
    }

    if (fs.existsSync(tarPath)) {
      fs.rmSync(tarPath)
    }

    await downloadEditor("5.0.0", `${__dirname}/../ui/app.tar.gz`)

    await decompress(
      `${__dirname}/../ui/app.tar.gz`,
      `${__dirname}/../ui/_app/`
    )

    const localAppPath = path.resolve(__dirname, "../ui/_app")
    // app.use(express.static(localAppPath))

    app.post("/upload", async (req, res) => {
      const { content, destination } = req.body
      //   console.log("UPLOAD", content, destination)

      if (!content || !destination) {
        return res.status(400).send("Missing content or destination")
      }

      if (!bucket) {
        return res.status(500).send("Upload failed")
      }

      const buffer = Buffer.from(content, "utf-8")
      const file = bucket.file(destination)

      const stream = file.createWriteStream({
        resumable: false,
        contentType: "text/plain",
      })

      stream.on("error", err => {
        console.error("❌ Error uploading:", err)
        res.status(500).send("Upload failed")
      })

      stream.on("finish", () => {
        console.log(`✅ Uploaded to: ${file.name}`)
        res.send("File uploaded successfully")
      })

      stream.end(buffer)
    })

    app.post(
      "/upload-image",
      express.json({ limit: "10mb" }),
      async (req, res) => {
        const { image_url, destination } = req.body
        if (!image_url || !destination) {
          return res
            .status(400)
            .json({ error: "image_url and destination are required" })
        }

        try {
          const response = await fetch(image_url)
          if (!response.ok) {
            return res.status(400).json({
              error: `Failed to download image: ${response.statusText}`,
            })
          }

          const contentType =
            response.headers.get("content-type") || "application/octet-stream"
          const buffer = await response.arrayBuffer()

          const file = bucket.file(destination)
          await file.save(Buffer.from(buffer), {
            resumable: false,
            contentType,
          })

          console.log(`✅ Image uploaded to ${file.name}`)
          res.json({ message: "Image uploaded successfully", path: file.name })
        } catch (error) {
          console.error("❌ upload-image error:", error)
          res.status(500).json({ error: (error as Error).message })
        }
      }
    )

    const upload = createUploadMiddleware()

    app.post("/upload-image-file", upload.single("file"), async (req, res) => {
      console.log("UPLOADING IMAGE FILE")

      const destination = (req.body as { destination?: string }).destination

      // eslint-disable-next-line
      // @ts-ignore
      if (!req.file || !destination) {
        return res
          .status(400)
          .json({ error: "File and destination are required" })
      }

      try {
        // eslint-disable-next-line
        // @ts-ignore
        const fileData = req.file.buffer

        const file = bucket.file(destination)
        await file.save(fileData, {
          resumable: false,
          // eslint-disable-next-line
          // @ts-ignore
          contentType: req.file.mimetype,
        })

        console.log(`✅ Image uploaded to ${file.name}`)
        res.json({ message: "Image uploaded successfully", path: file.name })
      } catch (error) {
        console.error("❌ upload-image-file error:", error)
        res.status(500).json({ error: (error as Error).message })
      }
    })

    app.put(
      "/upload/preview-image/:slug",
      createUploadMiddleware().single("file"),
      async (req, res) => {
        const { slug } = req.params
        const rigoToken = req.header("x-rigo-token")

        if (!rigoToken) {
          return res.status(400).json({
            error: "Rigo token is required. x-rigo-token header is missing",
          })
        }

        const { isAuthor } = await isPackageAuthor(rigoToken, slug)

        if (!isAuthor) {
          return res.status(403).json({
            error: "You are not authorized to upload a preview image",
          })
        }

        // eslint-disable-next-line
        // @ts-ignore
        if (!slug || !req.file) {
          return res.status(400).json({ error: "Slug and file are required" })
        }

        try {
          const filePath = `courses/${slug}/preview.png`
          const file = bucket.file(filePath)

          // eslint-disable-next-line
          // @ts-ignore
          await file.save(req.file.buffer, {
            resumable: false,

            // eslint-disable-next-line
            // @ts-ignore
            contentType: req.file.mimetype,
          })

          console.log(`✅ Preview image uploaded to ${filePath}`)
          return res.json({ message: "Preview uploaded", path: filePath })
        } catch (error) {
          console.error("❌ Error uploading preview image:", error)
          return res.status(500).json({ error: (error as Error).message })
        }
      }
    )

    app.get("/check-preview-image/:slug", async (req, res) => {
      const { slug } = req.params
      const file = bucket.file(`courses/${slug}/preview.png`)
      const [exists] = await file.exists()
      res.json({ exists, file: file.name })
    })

    app.get("/create", (req, res) => {
      console.log("GET /create")
      res.redirect("/creator/")
    })

    app.get("/", async (req, res) => {
      console.log("GET /")

      const slug = req.query.slug

      if (slug) {
        const file = path.resolve(__dirname, "../ui/_app/index.html")
        res.sendFile(file)
      } else {
        res.redirect("https://learnpack.co")
      }
    })

    app.get("/preview/:slug", async (req, res) => {
      console.log("GET COURSE PREVIEW /")

      const file = path.resolve(__dirname, "../ui/_app/index.html")
      res.sendFile(file)
    })

    app.get("/config", async (req, res) => {
      const courseSlug = req.query.slug as string
      // GEt the x-rigo-token
      // const rigoToken = req.header("x-rigo-token")

      if (!courseSlug) {
        return res
          .status(400)
          .json({ error: "Course slug and rigo token required" })
      }

      try {
        console.log("GET CONFIG, COURSE SLUG", courseSlug)
        const { config, exercises } = await buildConfig(bucket, courseSlug)
        res.set("X-Creator-Web", "true")
        res.set("Access-Control-Expose-Headers", "X-Creator-Web")
        res.json({ config, exercises })
      } catch (error) {
        console.error(error)
        res.status(500).json({ error: (error as Error).message })
      }
    })

    app.get("/exercise/:slug/readme", async (req, res) => {
      console.log("GET /exercise/:slug/readme")

      const { slug } = req.params
      const courseSlug = req.query.slug
      const lang = req.query.lang || "us"

      const basePath = `courses/${courseSlug}/exercises/${slug}/`
      const filename = lang === "us" ? "README.md" : `README.${lang}.md`

      const file = bucket.file(basePath + filename)

      let contentBuffer

      try {
        contentBuffer = await file.download()
      } catch {
        if (lang !== "us") {
          console.warn(`No README for lang '${lang}', falling back to 'us'`)
          const fallbackFile = bucket.file(basePath + "README.md")
          contentBuffer = await fallbackFile.download()
        } else {
          return res.status(404).json({ error: "README not found" })
        }
      }

      const { attributes, body } = frontMatter(contentBuffer[0].toString())

      res.send({ attributes, body })
    })

    app.get("/.learn/assets/:file", async (req, res) => {
      console.log("GET /.learn/assets/:file", req.params.file)
      const { file } = req.params
      const courseSlug = req.query.slug

      if (!courseSlug) {
        return res.status(400).send("Course slug is required")
      }

      const filePath = `courses/${courseSlug}/.learn/assets/${file}`
      const fileRef = bucket.file(filePath)
      const [exists] = await fileRef.exists()
      if (!exists) {
        return res.status(404).send("File not found")
      }

      const fileStream = fileRef.createReadStream()
      res.set("Content-Type", "application/octet-stream")
      res.set("Content-Disposition", `attachment; filename="${file}"`)
      fileStream.pipe(res)
    })

    app.put(
      "/exercise/:slug/file/:fileName",
      express.text(),
      async (req, res) => {
        const { slug, fileName } = req.params
        const query = req.query
        console.log(`PUT /exercise/${slug}/file/${fileName}`)

        const courseSlug = query.slug
        console.log("COURSE SLUG", courseSlug)

        // Update the file in the bucket
        const file = await bucket.file(
          `courses/${courseSlug}/exercises/${slug}/${fileName}`
        )
        await file.save(req.body)
        const created = await file.exists()
        console.log("File updated", created)
        res.send({
          message: "File updated",
          created,
        })
      }
    )
    app.post("/exercise/:slug/create", async (req, res) => {
      console.log("POST /exercise/:slug/create")
      const query = req.query
      const { title, readme, language } = req.body

      if (!title || !readme) {
        return res
          .status(400)
          .json({ error: "Missing title or readme content" })
      }

      const courseSlug = query.slug

      const fileName = `courses/${courseSlug}/exercises/${title}/README${
        language === "us" || language === "en" ? "" : `.${language}`
      }.md`
      const file = bucket.file(fileName)
      await file.save(readme)
      const created = await file.exists()
      res.send({
        message: "File updated",
        created,
      })
    })

    app.put("/actions/rename", async (req, res) => {
      console.log("PUT /actions/rename")
      const { slug, newSlug } = req.body
      const query = req.query
      const courseSlug = query.slug
      const filePrefix = `courses/${courseSlug}/exercises/${slug}/`
      const [files] = await bucket.getFiles({ prefix: filePrefix })
      for (const file of files) {
        const newFileName = file.name.replace(slug, newSlug)
        // eslint-disable-next-line no-await-in-loop
        await file.rename(newFileName)
      }

      res.send({ message: "Files renamed" })
    })

    app.post("/actions/translate", express.json(), async (req, res) => {
      console.log("POST /actions/translate")
      const { exerciseSlugs, languages, rigoToken } = req.body
      const query = req.query
      const courseSlug = query.slug

      console.log("EXERCISE SLUGS", exerciseSlugs)
      console.log("LANGUAGES", languages)
      console.log("RIGO TOKEN", rigoToken)

      if (!rigoToken) {
        return res.status(400).json({ error: "RigoToken not found" })
      }

      const languagesToTranslate: string[] = languages.split(",")

      try {
        await Promise.all(
          exerciseSlugs.map(async (slug: string) => {
            const readmePath = `courses/${courseSlug}/exercises/${slug}/README.md`
            const readme = await bucket.file(readmePath).download()
            await Promise.all(
              languagesToTranslate.map(async (language: string) => {
                const response = await translateExercise(rigoToken, {
                  text_to_translate: readme.toString(),
                  output_language: language,
                  exercise_slug: slug,
                })

                const translatedReadme = await bucket.file(
                  `courses/${courseSlug}/exercises/${slug}/README.${response.parsed.output_language_code}.md`
                )
                await translatedReadme.save(response.parsed.translation)
              })
            )
          })
        )

        return res.status(200).json({ message: "Translated exercises" })
      } catch (error) {
        console.log(error, "ERROR")
        return res.status(400).json({ error: (error as Error).message })
      }
    })

    app.delete("/exercise/:slug/delete", async (req, res) => {
      const { slug } = req.params
      const query = req.query
      const courseSlug = query.slug
      const filePrefix = `courses/${courseSlug}/exercises/${slug}/`
      const [files] = await bucket.getFiles({ prefix: filePrefix })
      for (const file of files) {
        // eslint-disable-next-line no-await-in-loop
        await file.delete()
      }

      res.send({ message: "Files deleted" })
    })

    app.get("/translations/sidebar", async (req, res) => {
      const { slug } = req.query

      const rigoToken = req.header("x-rigo-token")

      console.log("GETTING SIDEBAR FOR", slug)

      if (!slug) {
        return res.status(400).json({ error: "Slug is required" })
      }

      const courseSlug = slug as string

      try {
        const file = bucket.file(`courses/${courseSlug}/.learn/sidebar.json`)

        const [content] = await file.download()

        const sidebar = JSON.parse(content.toString())

        if (rigoToken) {
          const { exercises } = await buildConfig(bucket, courseSlug)
          const { fixedSidebar, valid } = await checkAndFixSidebarPure(
            sidebar,
            exercises,
            rigoToken
          )
          if (fixedSidebar) {
            await uploadFileToBucket(
              bucket,
              JSON.stringify(fixedSidebar),
              `courses/${courseSlug}/.learn/sidebar.json`
            )
            return res.json(fixedSidebar)
          }
        }

        res.json(sidebar)
      } catch (error: any) {
        if (error.code === 404) {
          console.log("SIDEBAR FILE NOT FOUND", courseSlug)

          const { exercises } = await buildConfig(bucket, courseSlug)

          const exerciseSlugsArray = exercises.map(exercise => exercise.slug)
          const sidebar = await createInitialSidebar(exerciseSlugsArray)

          if (rigoToken) {
            const { fixedSidebar } = await checkAndFixSidebarPure(
              sidebar,
              exercises,
              rigoToken
            )
            if (fixedSidebar) {
              await uploadFileToBucket(
                bucket,
                JSON.stringify(fixedSidebar),
                `courses/${courseSlug}/.learn/sidebar.json`
              )
            }

            await uploadFileToBucket(
              bucket,
              JSON.stringify(sidebar),
              `courses/${courseSlug}/.learn/sidebar.json`
            )
            return res.status(200).json(fixedSidebar)
          }

          await uploadFileToBucket(
            bucket,
            JSON.stringify(sidebar),
            `courses/${courseSlug}/.learn/sidebar.json`
          )

          return res.status(200).json(sidebar)
        }

        console.error("Unexpected error:", error)
        res.status(500).json({ error: error.message })
      }
    })

    app.get("/test/:slug", (req, res) => {
      emitToCourse(req.params.slug, "course-creation", {
        lesson: "000-welcome-to-bird-domestication",
        status: "generating",
        log: "Hello",
      })
      emitToCourse(req.params.slug, "course-creation", {
        lesson: "000-welcome-to-bird-domestication",
        status: "generating",
        log: "Hello",
      })
      emitToCourse(req.params.slug, "course-creation", {
        lesson: "000-welcome-to-bird-domestication",
        status: "generating",
        log: "Hello broder",
      })
      emitToCourse(req.params.slug, "course-creation", {
        lesson: "000-welcome-to-bird-domestication",
        status: "done",
        log: "Hello broder",
      })
      emitToCourse(req.params.slug, "course-creation", {
        lesson: "other",
        status: "generating",
        log: "Hello",
      })
      res.send({ message: "Course creation started" })
    })

    app.get("/assets/:file", (req, res) => {
      const file = path.join(localAppPath, req.params.file)
      res.sendFile(file)
    })
    app.get("/creator", (req, res) => {
      res.sendFile(path.join(distPath, "index.html"))
    })
    app.get("/creator/syllabus", (req, res) => {
      res.sendFile(path.join(distPath, "index.html"))
    })

    app.get("/creator/:file", (req, res) => {
      console.log("GET /creator/:file", req.params.file)
      const file = path.join(distPath, req.params.file)
      res.sendFile(file)
    })

    app.get("/creator/assets/:file", (req, res) => {
      const file = path.join(distPath, "assets", req.params.file)
      res.sendFile(file)
    })

    app.post("/actions/create-course", async (req, res) => {
      console.log("POST /actions/create-course")
      const { syllabus }: { syllabus: Syllabus } = req.body
      const rigoToken = req.header("x-rigo-token")
      const bcToken = req.header("x-breathecode-token")

      if (!rigoToken || !bcToken) {
        return res.status(400).json({ error: "Missing tokens" })
      }

      const slug = slugify(syllabus.courseInfo.title)

      const tutorialDir = `courses/${slug}`
      const learnJson = createLearnJson(syllabus.courseInfo)

      try {
        await api.createRigoPackage(rigoToken, slug, learnJson)
      } catch (error) {
        console.error("Failed to create Rigo package:", error)
        return res.status(400).json({ error: "Failed to create Rigo package" })
      }

      await uploadFileToBucket(
        bucket,
        JSON.stringify(learnJson),
        `${tutorialDir}/learn.json`
      )
      console.log("🔄 Learn.json uploaded to bucket to", tutorialDir)

      const lessonsPromises = syllabus.lessons.map(lesson =>
        processExercise(
          bucket,
          rigoToken,
          syllabus.lessons,
          `{
            "courseInfo": ${JSON.stringify(syllabus.courseInfo)},
            "sources": ${JSON.stringify(syllabus.sources)}
          }`,
          lesson,
          tutorialDir + "/exercises",
          slugify(syllabus.courseInfo.title)
        )
      )
      const readmeContents = await Promise.all(lessonsPromises)

      let imagesArray: any[] = []

      for (const content of readmeContents) {
        imagesArray = [...imagesArray, ...extractImagesFromMarkdown(content)]
      }

      console.log("📷 Generating images...")

      const imagePromises = imagesArray.map(
        async (image: { alt: string; url: string }) => {
          return processImage(
            bucket,
            tutorialDir,
            image.url,
            image.alt,
            rigoToken
          )
        }
      )
      await Promise.all(imagePromises)

      const sidebar = await createInitialSidebar(
        syllabus.lessons.map(lesson =>
          slugify(lesson.id + "-" + lesson.title)
        )
      )
      await uploadFileToBucket(
        bucket,
        JSON.stringify(sidebar),
        `${tutorialDir}/.learn/sidebar.json`
      )

      console.log("SIDEBAR UPLOADED", sidebar)

      return res.json({
        message: "Course created",
        slug: slugify(syllabus.courseInfo.title),
      })
    })

    app.get(
      "/courses/:courseSlug/exercises/:exerciseSlug/",
      async (req, res) => {
        console.log("GET /courses/:courseSlug/exercises/:exerciseSlug/")

        const { courseSlug, exerciseSlug } = req.params

        try {
          const prefix = `courses/${courseSlug}/exercises/${exerciseSlug}/`
          const [files] = await bucket.getFiles({ prefix })

          if (files.length === 0) {
            return res.status(404).json({ error: "Exercise not found" })
          }

          const fileList = files.map(file => ({
            name: path.basename(file.name),
            path: file.name,
          }))

          const entryFile = fileList.find(
            f => /^index\.\w+$/.test(f.name) || /^app\.\w+$/.test(f.name)
          )?.name

          const langMatch = entryFile?.match(/\.(js|ts|py|java|cpp|html|rb)/)
          const language = langMatch ? langMatch[1] : null

          const isTesteable = Boolean(entryFile)

          return res.json({
            slug: exerciseSlug,
            graded: isTesteable,
            entry: entryFile || null,
            language,
            files: fileList,
          })
        } catch (error) {
          console.error("❌ Error fetching exercise info:", error)
          return res.status(500).json({ error: (error as Error).message })
        }
      }
    )

    app.post("/actions/publish/:slug", async (req, res) => {
      try {
        const { slug } = req.params
        const rigoToken = req.header("x-rigo-token")
        const bcToken = req.header("x-breathecode-token")
        // const { academyId, categoryId } = req.body

        if (!rigoToken || !bcToken) {
          return res
            .status(400)
            .json({ error: "Faltan tokens o academy/category" })
        }

        const { config, exercises }: ConfigResponse = await buildConfig(
          bucket,
          slug
        )

        // 3) Preparar dirs temporales
        const prefix = `courses/${slug}/`
        const tmpRoot = path.join(os.tmpdir(), `learnpack-${slug}`)
        const buildRoot = path.join(tmpRoot, "build")
        rimraf.sync(tmpRoot)
        mkdirp.sync(buildRoot)

        // 4) Copiar UI descomprimida (_app)
        const uiSrc = path.resolve(__dirname, "../ui/_app")
        const copyDir = (src: string, dest: string) => {
          for (const ent of fs.readdirSync(src, { withFileTypes: true })) {
            const from = path.join(src, ent.name)
            const to = path.join(dest, ent.name)
            if (ent.isDirectory()) {
              mkdirp.sync(to)
              copyDir(from, to)
            } else {
              fs.copyFileSync(from, to)
            }
          }
        }

        copyDir(uiSrc, buildRoot)

        // 5) Inyectar placeholders en index.html
        const idxTpl = fs.readFileSync(path.join(uiSrc, "index.html"), "utf-8")
        const idxHtml = idxTpl
          .replace(/{{title}}/g, config.title.us)
          .replace(/<title>.*<\/title>/, `<title>${config.title.us}</title>`)
          .replace(/{{description}}/g, config.description.us)
          .replace(
            /{{preview}}/g,
            fixPreviewUrl(slug, config.preview) ||
              "https://raw.githubusercontent.com/learnpack/ide/master/public/learnpack.svg"
          )
          .replace(/{{slug}}/g, slug)
          .replace(/{{duration}}/g, minutesToISO8601Duration(config.duration))
        fs.writeFileSync(path.join(buildRoot, "index.html"), idxHtml)

        const mfTpl = fs.readFileSync(
          path.join(uiSrc, "manifest.webmanifest"),
          "utf-8"
        )
        const mf = mfTpl
          .replace(/{{course_title}}/g, config.title.us)
          .replace(/{{course_app_name}}/g, config.slug)
        fs.writeFileSync(path.join(buildRoot, "manifest.webmanifest"), mf)

        // 7) Descargar todos los archivos del bucket al buildRoot
        const [files] = await bucket.getFiles({ prefix })
        await Promise.all(
          files.map(async file => {
            const rel = file.name.replace(prefix, "")
            const out = path.join(buildRoot, rel)
            mkdirp.sync(path.dirname(out))
            const [buf] = await file.download()
            fs.writeFileSync(out, buf)
          })
        )

        // 8) Crear el config.json en buildRoot con lo que retorna buildConfig
        const fullConfig = { config, exercises }
        fs.writeFileSync(
          path.join(buildRoot, "config.json"),
          JSON.stringify(fullConfig, null, 2),
          "utf-8"
        )

        // 9) Empaquetar en ZIP (solo contenido de buildRoot)
        const zipName = `${slug}.zip`
        const zipPath = path.join(tmpRoot, zipName)
        const output = fs.createWriteStream(zipPath)
        const archive = archiver("zip", { zlib: { level: 9 } })

        output.on("close", async () => {
          // 10) Subir ZIP a RigoBot
          const form = new FormData()
          form.append("file", fs.createReadStream(zipPath))
          form.append("config", JSON.stringify(config))

          const rigoRes = await axios.post(
            `${RIGOBOT_HOST}/v1/learnpack/upload`,
            form,
            {
              headers: {
                ...form.getHeaders(),
                Authorization: `Token ${rigoToken}`,
              },
            }
          )

          await handleAssetCreation(
            { token: bcToken, rigobotToken: rigoToken },
            config,
            rigoRes.data.url
          )

          rimraf.sync(tmpRoot)
          console.log("RIGO RES", rigoRes.data)

          return res.json({ url: rigoRes.data.url })
        })

        archive.on("error", err => {
          console.error("ZIP Error:", err)
          return res.status(500).send("Error building zip")
        })

        archive.pipe(output)
        archive.directory(buildRoot, false)
        await archive.finalize()
      } catch (error) {
        console.error(error)
        return res.status(500).json({ error: (error as Error).message })
      }
    })

    app.get(
      "/courses/:courseSlug/exercises/:exerciseSlug/file/:filename",
      async (req, res) => {
        console.log(
          "GET /courses/:courseSlug/exercises/:exerciseSlug/file/:filename",
          req.params
        )
        const { courseSlug, exerciseSlug, filename } = req.params

        try {
          const filePath = `courses/${courseSlug}/exercises/${exerciseSlug}/${filename}`
          const file = bucket.file(filePath)
          const [exists] = await file.exists()

          if (!exists) {
            return res.status(404).json({ error: "File not found" })
          }

          const [content] = await file.download()
          res.set("Content-Type", "text/plain")
          return res.send(content.toString("utf-8"))
        } catch (error) {
          console.error("❌ Error fetching file:", error)
          return res
            .status(500)
            .json({ error: (error as Error).message || "Unable to fetch file" })
        }
      }
    )

    const YT_REGEX = /(?:youtube\.com\/watch\?v=|youtu\.be\/)([\w-]{11})/

    app.get("/actions/fetch/:link", async (req, res) => {
      const { link } = req.params
      try {
        // 1) Decode the URL
        const decoded = Buffer.from(link, "base64url").toString("utf-8")
        const ytMatch = decoded.match(YT_REGEX)

        if (ytMatch) {
          const videoId = ytMatch[1]
          // fetch metadata
          const items = await YoutubeTranscript.fetchTranscript(videoId)
          const transcript = items.map(i => i.text).join(" ")

          const { data: meta } = await axios.get(
            "https://www.youtube.com/oembed",
            {
              params: { url: decoded, format: "json" },
            }
          )

          return res.json({
            url: decoded,
            title: meta.title,
            author: meta.author_name,
            thumbnail: meta.thumbnail_url,
            transcript,
          })
        }

        const response = await axios.get(decoded, { responseType: "text" })
        const html = response.data as string
        const title = getTitleFromHTML(html)
        console.log("TITLE", title)

        const text = convert(html)
        return res.json({
          url: decoded,
          text,
          title,
        })
      } catch (error: any) {
        console.error("❌ /actions/fetch error:", error.message || error)
        res.status(500).json({ error: error.message || "Failed to fetch link" })
      }
    })
    app.delete("/packages/:slug", async (req, res) => {
      console.log("DELETE /packages/:slug")

      const { slug } = req.params
      const rigoToken = req.header("x-rigo-token")

      if (!rigoToken) {
        return res.status(400).json({ error: "x-rigo-token is required" })
      }

      const isValid = await isValidRigoToken(rigoToken)

      if (!isValid) {
        return res.status(400).json({ error: "Invalid RigoToken" })
      }

      const filePrefix = `courses/${slug}/`

      try {
        const [files] = await bucket.getFiles({ prefix: filePrefix })

        if (files.length === 0) {
          return res.status(200).json({
            message: "No package found with the given slug, nothing to delete",
          })
        }

        await Promise.all(files.map(file => file.delete()))

        console.log(`✅ Successfully deleted package from GCP: ${slug}`)

        return res.json({ message: `Package ${slug} deleted successfully` })
      } catch (error) {
        console.error("❌ Error deleting package:", error)
        return res.status(500).json({ error: "Failed to delete the package" })
      }
    })

    app.get("/proxy", async (req, res) => {
      const { url } = req.query

      if (!url) {
        return res.status(400).json({ error: "URL is required" })
      }

      try {
        const decodedUrl = Buffer.from(url as string, "base64url").toString(
          "utf-8"
        )

        const response = await axios.get(decodedUrl, {
          responseType: "arraybuffer",
        })

        res.set(response.headers)
        res.status(response.status).send(response.data)
      } catch (error) {
        console.error("Error in /proxy:", error)
        res.status(500).json({ error: "Failed to fetch the resource" })
      }
    })

    server.listen(PORT, () => {
      console.log(
        `🚀 Creator UI server running at http://localhost:${PORT}/creator`
      )
    })
  }
}
