import { flags } from "@oclif/command"
import BaseCommand from "../utils/BaseCommand"
import ora from "ora"

// eslint-disable-next-line
import * as fs from "fs-extra"
import * as prompts from "prompts"
import cli from "cli-ux"
import * as eta from "eta"

import api from "../utils/api"
import Console from "../utils/console"
import { ValidationError } from "../utils/errors"

import * as path from "path"
import {
  // hasCreatorPermission,
  generateImage,
  downloadImage,
  interactiveCreation,
  createCodeFile,
  readmeCreator,
  createPreviewReadme,
  makeReadmeReadable,
  isValidRigoToken,
} from "../utils/rigoActions"
import { getConsumable } from "../utils/api"
import {
  checkReadability,
  PackageInfo,
  getExInfo,
  extractImagesFromMarkdown,
  getFilenameFromUrl,
  makePackageInfo,
  estimateDuration,
  appendContentIndex,
  appendAIRules,
  slugify,
} from "../utils/creatorUtilities"
import SessionManager from "../managers/session"

const durationByKind: Record<string, number> = {
  code: 5,
  quiz: 3,
  read: 2,
}

function estimateActivities(estimatedDuration: number) {
  const result: Record<string, number> = {}

  for (const kind of Object.keys(durationByKind)) {
    result[kind] = Math.floor(estimatedDuration / durationByKind[kind])
  }

  return result
}

const PARAMS = {
  expected_grade_level: "6",
  max_fkgl: 8,
  max_words: 200,
  max_rewrite_attempts: 3,
  max_title_length: 50,
}

const whichTargetAudienceAndEstimatedDuration = async () => {
  const res = await prompts([
    {
      type: "text",
      name: "targetAudience",
      message: "What is the target audience for this tutorial?",
    },
    {
      type: "number",
      name: "estimatedDuration",
      message: "What is the estimated duration for this tutorial in minutes?",
    },
  ])
  return {
    targetAudience: res.targetAudience,
    estimatedDuration: res.estimatedDuration,
  }
}

async function processExercise(
  rigoToken: string,
  steps: string[],
  packageContext: string,
  exercise: any,
  exercisesDir: string
): Promise<string> {
  const { exNumber, exTitle, kind, description } = getExInfo(exercise)
  const exerciseDir = path.join(exercisesDir, `${exNumber}-${exTitle}`)

  const readme = await readmeCreator(
    rigoToken,
    {
      title: `${exNumber} - ${exTitle}`,
      output_lang: "en",
      list_of_exercises: steps.join(","),
      tutorial_description: packageContext,
      lesson_description: description,
      kind: kind.toLowerCase(),
      last_lesson: "",
    },
    "learnpack-lesson-writer"
  )

  const duration = durationByKind[kind.toLowerCase()]
  let attempts = 0
  let readability = checkReadability(readme.parsed.content, 200, duration || 1)

  while (
    readability.fkglResult.fkgl > PARAMS.max_fkgl &&
    attempts < PARAMS.max_rewrite_attempts
  ) {
    Console.warning(
      `The lesson ${exTitle} has as readability score of  ${
        readability.fkglResult.fkgl
      } . It exceeds the maximum of words per minute. Rewriting it... (Attempt ${
        attempts + 1
      })`
    )

    // 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,
      },
      "learnpack-lesson-writer"
    )

    // console.log("REDUCED README START", reducedReadme, "REDUCED README END")

    if (!reducedReadme) break

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

    attempts++
  }

  Console.success(
    `After ${attempts} attempts, the lesson ${exTitle} has a readability score of ${
      readability.fkglResult.fkgl
    } using FKGL. And it has ${readability.minutes.toFixed(
      2
    )} minutes of reading time.`
  )

  const readmeFilename = "README.md"
  fs.writeFileSync(
    path.join(exerciseDir, readmeFilename),
    readability.newMarkdown
  )

  if (kind.toLowerCase() === "code") {
    const codeFile = await createCodeFile(rigoToken, {
      readme: readability.newMarkdown,
      tutorial_info: packageContext,
    })

    fs.writeFileSync(
      path.join(
        exerciseDir,
        `app.${codeFile.parsed.extension.replace(".", "")}`
      ),
      codeFile.parsed.content
    )
  }

  return readability.newMarkdown
}

const initializeInteractiveCreation = async (
  rigoToken: string,
  courseInfo: string
): Promise<{
  steps: string[]
  title: string
  description: string
  interactions: string
  difficulty: string
  duration: number
}> => {
  let prevInteractions = ""
  let isReady = false
  let currentSteps = []
  let currentTitle = ""
  let currentDescription = ""
  let currentDifficulty = ""

  while (!isReady) {
    const spinner = ora("Thinking...").start()
    let wholeInfo = courseInfo
    wholeInfo += `
    Current title: ${currentTitle}
    Current description: ${currentDescription}
    `
    // eslint-disable-next-line
    const res = await interactiveCreation(rigoToken, {
      courseInfo: wholeInfo,
      prevInteractions: prevInteractions,
    })
    spinner.succeed("Done!")
    currentSteps = res.parsed.listOfSteps
    isReady = res.parsed.ready
    if (res.parsed.title && currentTitle !== res.parsed.title) {
      currentTitle = res.parsed.title
    }

    if (
      res.parsed.description &&
      currentDescription !== res.parsed.description
    ) {
      currentDescription = res.parsed.description
    }

    if (res.parsed.difficulty && currentDifficulty !== res.parsed.difficulty) {
      currentDifficulty = res.parsed.difficulty
    }

    if (!isReady) {
      console.log(currentSteps)
      Console.info(`AI: ${res.parsed.aiMessage}`)
      prevInteractions += `\nAI: ${res.parsed.aiMessage}`
      // eslint-disable-next-line
      const userMessage = await prompts([
        {
          type: "text",
          name: "userMessage",
          message: "Your message: ",
        },
      ])
      prevInteractions += `\nUser: ${userMessage.userMessage}`
    }
  }

  const duration = estimateDuration(currentSteps)
  return {
    steps: currentSteps,
    title: currentTitle,
    description: currentDescription,
    interactions: prevInteractions,
    difficulty: currentDifficulty,
    duration,
  }
}

process.emitWarning = (warning: string) => {
  Console.debug("A Warning was emitted by Node.js: ", warning)
}

const handleAILogic = async (tutorialDir: string, packageInfo: PackageInfo) => {
  fs.removeSync(path.join(tutorialDir, "exercises", "01-hello-world"))

  let sessionPayload = await SessionManager.getPayload()

  const sessionExists = sessionPayload && sessionPayload.rigobot

  const isValidToken =
    sessionExists && sessionPayload.rigobot.key ?
      await isValidRigoToken(sessionPayload.rigobot.key) :
      false

  const isValidBreathecodeToken =
    sessionExists && sessionPayload.token ?
      await api.validateToken(sessionPayload.token) :
      false

  if (!sessionExists || !isValidBreathecodeToken || !isValidToken) {
    Console.info(
      "Almost there! First you need to login with 4Geeks.com to use AI Generation tool for creators. You can create a new account here: https://4geeks.com/checkout?plan=4geeks-creator"
    )
    try {
      sessionPayload = await SessionManager.login()
    } catch (error) {
      Console.error("Error trying to authenticate")
      Console.error((error as TypeError).message || (error as string))
    }
  }

  const rigoToken = sessionPayload.rigobot.key

  const consumable = await getConsumable(sessionPayload.token, "ai-generation")

  if (consumable.count === 0) {
    Console.error(
      "It seems you cannot generate tutorials with AI. Make sure you creator subscription is up to date here: https://4geeks.com/profile/subscriptions. If you believe there is an issue you can always contact support@4geeks.com"
    )
    process.exit(1)
  }

  // const isCreator = await hasCreatorPermission(rigoToken)
  // if (!isCreator) {
  //   Console.error(
  //     "👀 Oops! You need to be a creator to use our specialized AI. Please contact support"
  //   )
  //   process.exit(1)
  // }

  Console.success("🎉 Let's begin this learning journey!")

  const { targetAudience, estimatedDuration } =
    await whichTargetAudienceAndEstimatedDuration()
  const contentIndex = await appendContentIndex()
  const airules = await appendAIRules()

  if (airules) {
    const rulesDir = path.join(tutorialDir, ".learn", "rules")

    // Crear el directorio si no existe
    fs.mkdirSync(rulesDir, { recursive: true })

    fs.writeFileSync(path.join(rulesDir, "airules.txt"), airules)
  }

  let packageContext = `
  \n
  Title: "${packageInfo.title.us}"
  Description: "${packageInfo.description.us}"
  Target Audience: "${targetAudience}"
  Estimated Duration: "${estimatedDuration} minutes"

  ${
    contentIndex ?
      `Content Index submitted by the user, use this to guide your creation. Keep in mind that your tutorial should contain these topics: 
      ---
      ${contentIndex}
      ---
      ` :
      ""
  }

  This is the duration for each type of step, use it to estimate the number of steps to create:
  ${Object.entries(durationByKind)
    .map(([key, value]) => `${key}: ${value} minutes`)
    .join("\n")}


  Within the estimated duration, is possible to have the following activities:
  Format=
  Activity: Maximum number of steps for duration

  Estimated activities:
  ${JSON.stringify(estimateActivities(estimatedDuration))}

  You should create a tutorial that is engaging and fun to follow.


  ${
    airules ?
      `
      This is a list of rules you need to follow when creating the tutorial:
      ${airules}
      ` :
      ""
  }
  `

  const { steps, title, description, duration, difficulty } =
    await initializeInteractiveCreation(rigoToken, packageContext)
  packageInfo.title.us = title
  packageInfo.description.us = description
  packageInfo.duration = duration
  packageInfo.difficulty = difficulty

  packageContext = `
  Title: "${title}"
  Description: "${description}"
  Target Audience: "${targetAudience}"
  List of exercises: ${steps.join(", ")}

  AI Rules: ${airules}
  `
  const exercisesDir = path.join(tutorialDir, "exercises")
  fs.ensureDirSync(exercisesDir)

  Console.info("Creating lessons...")
  for (const [index, exercise] of steps.entries()) {
    const { exNumber, exTitle } = getExInfo(exercise)
    const exerciseDir = path.join(exercisesDir, `${exNumber}-${exTitle}`)
    fs.ensureDirSync(exerciseDir)
  }

  const exercisePromises = steps.map(exercise =>
    processExercise(rigoToken, steps, packageContext, exercise, exercisesDir)
  )

  const readmeContents = await Promise.all(exercisePromises)
  Console.success("Lessons created! 🎉")

  Console.info("Generating images for the lessons...")
  let imagesArray: any[] = []

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

  const imagePromises = imagesArray.map(async (image: any) => {
    try {
      const filename = getFilenameFromUrl(image.url)
      const webhookUrl = `${process.env.HOST}/webhooks/${slugify(
        packageInfo.title.us
      )}/images/${filename}`

      const imagePath = path.join(tutorialDir, ".learn", "assets", filename)

      const res = await generateImage(rigoToken, {
        prompt: image.alt,
        callbackUrl: webhookUrl,
      })
      await downloadImage(res.image_url, imagePath)
      return true
    } catch {
      Console.error(`Error downloading image ${image.url}`)

      return false
    }
  })
  await Promise.all(imagePromises)
  Console.info(
    "Images generated successfully! 🎉 Your tutorial will be ready soon!"
  )

  Console.info("Creating preview readme...")

  await createPreviewReadme(tutorialDir, packageInfo, rigoToken, readmeContents)
  const imagePath = path.join(tutorialDir, "preview.png")

  const res = await generateImage(rigoToken, {
    prompt:
      "Generate a preview image for the tutorial. This is all the tutorial information: " +
      packageContext +
      "\n Generate only a basic preview image, add the tutorial Title as a text add the top middle, avoid adding any other text elements. Try to generate an image that related with the tutorial content.",
    callbackUrl: `${process.env.HOST}/webhooks/${slugify(
      packageInfo.title.us
    )}/images/preview.png`,
  })

  // await downloadImage(res.image_url, imagePath)

  return true
}

const getChoices = async (empty: boolean) => {
  const defaultChoices = {
    title: "My Interactive Tutorial",
    description: "",
    difficulty: "beginner",
    duration: 5,
    useAI: "no",
    grading: "isolated",
  }

  if (empty) {
    return defaultChoices
  }

  const choices = await prompts([
    {
      type: "select",
      name: "grading",
      message: "How are you going to grade students or yourself?",
      choices: [
        {
          title:
            "No grading: No feedback or testing whatsoever, similar to a an interactive book.",
          value: null,
        },
        {
          title: "Isolated: Each step is a new separate exercise",
          value: "isolated",
        },
        {
          title:
            "Step by step: Each step builds on top of each other like an incremental tutorial",
          value: "incremental",
        },
      ],
    },
    {
      type: "text",

      name: "title",
      initial: "My Interactive Tutorial",
      message: "Title for your tutorial? Press enter to leave as it is",
      validate: (value: string) => {
        if (value.length > PARAMS.max_title_length) {
          return `Title must be less than ${PARAMS.max_title_length} characters`
        }

        return true
      },
    },
    {
      type: "text",
      name: "description",
      initial: "",
      message: "Description for your tutorial? Press enter to leave blank",
    },

    {
      type: "select",
      name: "useAI",
      message:
        "Want a little bit of AI magic to help you? Our AI can craft the tutorial for you",
      choices: [
        {
          title: "Yes, please help me",
          value: "yes",
        },
        { title: "No, thanks, I prefer to do it manually", value: "no" },
      ],
    },
  ])

  const completeChoices = {
    ...choices,
    difficulty: "beginner",
    duration: 30,
  }
  return completeChoices
}

class InitComand extends BaseCommand {
  static description =
    "Create a new learning package: Book, Tutorial or Exercise"

  static flags = {
    ...BaseCommand.flags,
    grading: flags.help({ char: "h" }),
  }

  async run() {
    const { flags } = this.parse(InitComand)

    await alreadyInitialized()

    const choices = await getChoices(flags.yes)

    const packageInfo = makePackageInfo(choices)

    const tutorialDir = `./${packageInfo.slug}`
    fs.ensureDirSync(tutorialDir)

    const templatesDir = path.resolve(
      __dirname,
      "../../src/utils/templates/" + (choices.grading || "no-grading")
    )
    if (!fs.existsSync(templatesDir))
      throw ValidationError(`Template ${templatesDir} does not exists`)
    await fs.copySync(templatesDir, tutorialDir)

    if (choices.useAI === "yes") {
      await handleAILogic(tutorialDir, packageInfo)
    }

    const languages = ["en", "es"]

    // Creating README files
    for (const language of languages) {
      const readmeFilename = `README${language !== "en" ? `.${language}` : ""}`
      const readmeTemplatePath = path.resolve(
        templatesDir,
        `${readmeFilename}.ejs`
      )

      if (choices.useAI !== "yes") {
        const readmeObject = {
          title: packageInfo.title.us,
          description: packageInfo.description.us,
          grading: packageInfo.grading,
          difficulty: packageInfo.difficulty,
          duration: packageInfo.duration,
        }
        const readmeContent = eta.render(
          fs.readFileSync(readmeTemplatePath, "utf-8"),
          readmeObject
        )
        fs.writeFileSync(
          path.join(tutorialDir, `${readmeFilename}.md`),
          readmeContent
        )
      }

      if (fs.existsSync(path.join(tutorialDir, `${readmeFilename}.ejs`)))
        fs.removeSync(path.join(tutorialDir, `${readmeFilename}.ejs`))
    }

    cli.action.start("Initializing package")

    if (!fs.existsSync(path.join(tutorialDir, ".gitignore")))
      fs.copyFile(
        path.resolve(__dirname, "../../src/utils/templates/gitignore.txt"),
        path.join(tutorialDir, ".gitignore")
      )

    fs.writeFileSync(
      path.join(tutorialDir, "learn.json"),
      JSON.stringify(packageInfo, null, 2)
    )

    cli.action.stop()
    Console.success(`😋 Package initialized successfully in ${tutorialDir}`)
    Console.help(
      `Get inside the tutorial with the command: $ cd ${tutorialDir}`
    )
    Console.help(
      `Start the exercises by running the following command on your terminal: $ learnpack start`
    )
    process.exit(0)
  }
}

const alreadyInitialized = () =>
  new Promise((resolve, reject) => {
    fs.readdir("./", function (err: any, files: any) {
      files = files.filter((f: any) =>
        [".learn", "learn.json", "bc.json", ".breathecode"].includes(f)
      )
      if (err) {
        reject(ValidationError(err.message))
        throw ValidationError(err.message)
      } else if (files.length > 0) {
        reject(
          ValidationError(
            "It seems the package is already initialized because we've found the following files: " +
              files.join(",")
          )
        )
        throw ValidationError(
          "It seems the package is already initialized because we've found the following files: " +
            files.join(",")
        )
      }

      resolve(false)
    })
  })

export default InitComand
