import { IExercise } from "../models/exercise-obj"
import { promisify } from "util"
import { exec as execCallback } from "child_process"

const exec = promisify(execCallback)

import { cli } from "cli-ux"
import Console from "./console"

type TNeededPlugins = {
  needed: string[];
  notInstalled: string[];
};
type TPackageManager = "npm" | "pip";

export const checkNotInstalledPlugins = async (
  exercises: IExercise[],
  installedPlugins: string[],
  command: any
): Promise<TNeededPlugins> => {
  Console.info("Checking needed plugins...")
  const usefulExtensions = new Set(["py", "js", "jsx", "html"])
  const foundExtensions: string[] = []
  const testingExtensions: string[] = []
  const neededPlugins: string[] = []
  let someExerciseWithOnlyTestFile = false // I will suppose that there are not exercises with only readme and test files

  // See the extensions of the files that are not tests files
  for (const e of exercises) {
    const notReadmeFiles = e.files.filter(
      f => !f.name.toLowerCase().includes("readme")
    )

    if (!someExerciseWithOnlyTestFile && notReadmeFiles.length > 0) {
      someExerciseWithOnlyTestFile = notReadmeFiles.every(f => {
        return f.name.includes("test")
      })

      if (someExerciseWithOnlyTestFile) {
        Console.debug(
          "Some exercises have only test files, I will install the @learnpack/node plugin to run the tests."
        )
      }
    }

    for (const f of notReadmeFiles) {
      const ext = f.name.split(".").pop()
      if (!ext) continue

      if (f.name.includes("test")) {
        !testingExtensions.includes(ext) && testingExtensions.push(ext)
        continue
      }

      if (usefulExtensions.has(ext) && !foundExtensions.includes(ext)) {
        foundExtensions.push(ext)
      }
    }
  }

  Console.debug("foundExtensions", foundExtensions)
  Console.debug("testingExtensions", testingExtensions)

  if (foundExtensions.length === 0 && testingExtensions.length === 0)
    return {
      needed: [],
      notInstalled: [],
    }

  if (foundExtensions.includes("html") && foundExtensions.includes("js")) {
    neededPlugins.push("@learnpack/dom")
  }

  if (foundExtensions.includes("html") && !foundExtensions.includes("js")) {
    neededPlugins.push("@learnpack/html")
  }

  if (foundExtensions.includes("jsx")) {
    neededPlugins.push("@learnpack/react")
  }

  if (foundExtensions.includes("py") || testingExtensions.includes("py")) {
    neededPlugins.push("@learnpack/python")
  }

  if (
    (foundExtensions.includes("js") && !foundExtensions.includes("html")) ||
    (testingExtensions.includes("js") && someExerciseWithOnlyTestFile)
  ) {
    neededPlugins.push("@learnpack/node")
  }

  const notInstalled = neededPlugins.filter(
    item => !installedPlugins.includes(item)
  )

  if (notInstalled.length > 0) {
    Console.error(
      "These plugins are not installed but required: ",
      notInstalled
    )
    const confirmInstall = await cli.confirm(
      "Do you want to install the needed plugins? (y/n)"
    )
    if (confirmInstall) {
      Console.info("Installing the needed plugins...")
      for await (const p of notInstalled) {
        await command.config.runCommand(`plugins:install`, [p])
        Console.log(`Plugin ${p} installed`)
      }

      Console.log(
        "All needed plugins installed, please restart LearnPack to load them."
      )
      Console.info("Run: $ learnpack start")
      command.exit(0)
    } else {
      Console.error(
        "You need to install the plugins to complete this exercise"
      )
      Console.info(
        "To install the plugins run each of the following commands: "
      )
      for (const p of notInstalled) {
        Console.log(`learnpack plugins:install ${p}`)
      }

      command.exit(1)
    }
  }

  return {
    needed: neededPlugins,
    notInstalled,
  }
}

function includesAll(baseString: string, elementsArray: string[]) {
  return elementsArray.every(element => baseString.includes(element))
}

const installDependencies = async (
  deps: string[],
  packageManager: TPackageManager
) => {
  let command = ""
  if (packageManager === "npm") {
    command = `npm install -g ${deps.join(" ")}`
  } else if (packageManager === "pip") {
    command = `pip install ${deps.join(" ")}`
  }

  try {
    const { stdout, stderr } = await exec(command)
    if (
      stderr &&
      (stderr.includes("npm ERR!") || stderr.includes("Traceback"))
    ) {
      Console.error(`Error executing ${command}.`)
      Console.error(stderr)
      return
    }

    Console.info(`Dependencies ${deps.join(" ")} installed...`)
    return true
  } catch (error) {
    // Si npm/pip retorna código > 0, exec lanzará una excepción
    const execError = error as any
    const stdout = execError.stdout || ""
    const stderr = execError.stderr || ""
    const errorMessage = stderr || execError.message || String(error)

    // Verificar si es un error real
    if (
      errorMessage.includes("npm ERR!") ||
      errorMessage.includes("Traceback")
    ) {
      Console.error(`Error executing ${command}.`)
      Console.error(errorMessage)
      return
    }

    // Si no es un error real, podría ser solo un warning
    // Verificar si la instalación fue exitosa a pesar del warning
    if (
      stdout &&
      (stdout.includes("added") || stdout.includes("Successfully installed"))
    ) {
      Console.info(`Dependencies ${deps.join(" ")} installed...`)
      return true
    }

    // Si no hay indicios de éxito, se asume fallo (retorna undefined implícitamente)
    Console.debug(
      `${command} returned non-zero exit code, but may be just a warning:`,
      errorMessage
    )
  }
}

export const checkNotInstalledDependencies = async (
  neededPlugins: string[]
) => {
  Console.info("Checking needed dependencies...")

  const jsPluginsDependencies = [
    "jest@29.7.0",
    "jest-environment-jsdom@29.7.0",
  ]
  // pytest up to 6.2.5
  const pyPluginsDependencies = ["pytest", "pytest-testdox", "mock"]

  const npmLsCommand = "npm ls jest jest-environment-jsdom -g"

  let pytestNeeded = false
  let jestNeeded = false

  if (
    neededPlugins.includes("@learnpack/dom") ||
    neededPlugins.includes("@learnpack/html") ||
    neededPlugins.includes("@learnpack/react") ||
    neededPlugins.includes("@learnpack/node")
  ) {
    jestNeeded = true
  }

  if (neededPlugins.includes("@learnpack/python")) {
    pytestNeeded = true
  }

  if (jestNeeded) {
    try {
      const { stdout, stderr } = await exec("npm ls -g")
      // Solo considerar errores reales, no warnings
      if (stderr && stderr.includes("npm ERR!")) {
        Console.error(`Error executing npm ls -g. Use debug for more info`)
        Console.debug(stderr)
        return false
      }

      if (includesAll(stdout, jsPluginsDependencies)) return true
    } catch (error) {
      // Si npm retorna código > 0, exec lanzará una excepción
      // Verificar si es un error real o solo un warning
      const errorMessage =
        (error as any)?.stderr || (error as Error)?.message || String(error)
      if (errorMessage.includes("npm ERR!")) {
        Console.error(`Error executing npm ls -g. Use debug for more info`)
        Console.debug(errorMessage)
        return false
      }

      // Si no es un error real, continuar (podría ser solo un warning)
      Console.debug(
        "npm ls -g returned non-zero exit code, but may be just a warning:",
        errorMessage
      )
    }

    Console.error("The jest dependencies are not installed")
    const confirmInstall = await cli.confirm(
      "Do you want to install the needed dependencies? (y/n)"
    )

    if (!confirmInstall) {
      Console.error(
        `The exercises can't be tested without the following dependencies: ${jsPluginsDependencies.join(
          ", "
        )}`
      )
      Console.info(
        `Please install them and try again. Run the following command to install them: \nnpm install -g ${jsPluginsDependencies.join(
          " "
        )}`
      )
      return false
    }

    Console.log("Installing jest dependencies...")
    await installDependencies(jsPluginsDependencies, "npm")
  }

  if (pytestNeeded) {
    try {
      await exec("python -m pip install --upgrade pip")
    } catch (error) {
      Console.error(
        "Error upgrading pip. Please install pip manually, run: pip install --upgrade pip"
      )
      Console.debug(error)
    }

    try {
      const { stdout, stderr } = await exec("pip list")
      // Solo considerar errores reales, no warnings
      if (stderr && stderr.includes("Traceback")) {
        Console.error(`Error executing pip list. Use debug for more info`)
        Console.debug(stderr)
        return false
      }

      if (includesAll(stdout, pyPluginsDependencies)) return true
    } catch (error) {
      // Si pip retorna código > 0, verificar si es un error real
      const errorMessage =
        (error as any)?.stderr || (error as Error)?.message || String(error)
      if (errorMessage.includes("Traceback")) {
        Console.error(`Error executing pip list. Use debug for more info`)
        Console.debug(errorMessage)
        return false
      }

      // Si no es un error real, continuar
      Console.debug(
        "pip list returned non-zero exit code, but may be just a warning:",
        errorMessage
      )
    }

    Console.error("The pytest dependencies are not installed")
    const confirmInstall = await cli.confirm(
      "Do you want to install the needed dependencies? (y/n)"
    )
    if (!confirmInstall) {
      Console.error(
        `The exercises can't be tested without the following dependencies: ${pyPluginsDependencies.join(
          ", "
        )}`
      )
      Console.info(
        `Please install them and try again. Run the following command to install them: \npip install ${pyPluginsDependencies.join(
          " "
        )}`
      )
      return false
    }

    Console.log("Installing pytest dependencies...")
    await installDependencies(pyPluginsDependencies, "pip")
  }

  return true
}
