import { callPackageManager } from "./install";

import path from "path";
import { cyan } from "picocolors";

import fsExtra from "fs-extra";
import { writeLoadersConfig } from "./datasources";
import { createBackendEnvFile, createFrontendEnvFile } from "./env-variables";
import { PackageManager } from "./get-pkg-manager";
import { installLlamapackProject } from "./llama-pack";
import { makeDir } from "./make-dir";
import { isHavingPoetryLockFile, tryPoetryRun } from "./poetry";
import { installPythonTemplate } from "./python";
import { downloadAndExtractRepo } from "./repo";
import { ConfigFileType, writeToolsConfig } from "./tools";
import {
  FileSourceConfig,
  InstallTemplateArgs,
  ModelConfig,
  TemplateDataSource,
  TemplateFramework,
  TemplateVectorDB,
} from "./types";
import { installTSTemplate } from "./typescript";

const checkForGenerateScript = (
  modelConfig: ModelConfig,
  vectorDb?: TemplateVectorDB,
  llamaCloudKey?: string,
  useLlamaParse?: boolean,
) => {
  const missingSettings = [];

  if (!modelConfig.isConfigured()) {
    missingSettings.push("your model provider API key");
  }

  const llamaCloudApiKey = llamaCloudKey ?? process.env["LLAMA_CLOUD_API_KEY"];
  const isRequiredLlamaCloudKey = useLlamaParse || vectorDb === "llamacloud";
  if (isRequiredLlamaCloudKey && !llamaCloudApiKey) {
    missingSettings.push("your LLAMA_CLOUD_API_KEY");
  }

  if (vectorDb !== "none" && vectorDb !== "llamacloud") {
    missingSettings.push("your Vector DB environment variables");
  }

  return missingSettings;
};

// eslint-disable-next-line max-params
async function generateContextData(
  framework: TemplateFramework,
  modelConfig: ModelConfig,
  packageManager?: PackageManager,
  vectorDb?: TemplateVectorDB,
  llamaCloudKey?: string,
  useLlamaParse?: boolean,
) {
  if (packageManager) {
    const runGenerate = `${cyan(
      framework === "fastapi"
        ? "poetry run generate"
        : `${packageManager} run generate`,
    )}`;

    const missingSettings = checkForGenerateScript(
      modelConfig,
      vectorDb,
      llamaCloudKey,
      useLlamaParse,
    );

    if (!missingSettings.length) {
      // If all the required environment variables are set, run the generate script
      if (framework === "fastapi") {
        if (isHavingPoetryLockFile()) {
          console.log(`Running ${runGenerate} to generate the context data.`);
          const result = tryPoetryRun("poetry run generate");
          if (!result) {
            console.log(`Failed to run ${runGenerate}.`);
            process.exit(1);
          }
          console.log(`Generated context data`);
          return;
        }
      } else {
        console.log(`Running ${runGenerate} to generate the context data.`);
        await callPackageManager(packageManager, true, ["run", "generate"]);
        return;
      }
    }

    const settingsMessage = `After setting ${missingSettings.join(" and ")}, run ${runGenerate} to generate the context data.`;
    console.log(`\n${settingsMessage}\n\n`);
  }
}

const copyContextData = async (
  root: string,
  dataSources: TemplateDataSource[],
) => {
  for (const dataSource of dataSources) {
    const dataSourceConfig = dataSource?.config as FileSourceConfig;
    // Copy local data
    const dataPath = dataSourceConfig.path;

    const destPath = path.join(root, "data", path.basename(dataPath));
    console.log("Copying data from path:", dataPath);
    await fsExtra.copy(dataPath, destPath);
  }
};

const installCommunityProject = async ({
  root,
  communityProjectConfig,
}: Pick<InstallTemplateArgs, "root" | "communityProjectConfig">) => {
  const { owner, repo, branch, filePath } = communityProjectConfig!;
  console.log("\nInstalling community project:", filePath || repo);
  await downloadAndExtractRepo(root, {
    username: owner,
    name: repo,
    branch,
    filePath: filePath || "",
  });
};

export const installTemplate = async (
  props: InstallTemplateArgs & { backend: boolean },
) => {
  process.chdir(props.root);

  if (props.template === "community" && props.communityProjectConfig) {
    await installCommunityProject(props);
    return;
  }

  if (props.template === "llamapack" && props.llamapack) {
    await installLlamapackProject(props);
    return;
  }

  if (props.framework === "fastapi") {
    await installPythonTemplate(props);
    // write loaders configuration (currently Python only)
    await writeLoadersConfig(
      props.root,
      props.dataSources,
      props.useLlamaParse,
    );
  } else {
    await installTSTemplate(props);
  }

  // write tools configuration
  await writeToolsConfig(
    props.root,
    props.tools,
    props.framework === "fastapi" ? ConfigFileType.YAML : ConfigFileType.JSON,
  );

  if (props.backend) {
    // This is a backend, so we need to copy the test data and create the env file.

    // Copy the environment file to the target directory.
    if (props.template === "streaming" || props.template === "multiagent") {
      await createBackendEnvFile(props.root, {
        modelConfig: props.modelConfig,
        llamaCloudKey: props.llamaCloudKey,
        vectorDb: props.vectorDb,
        framework: props.framework,
        dataSources: props.dataSources,
        port: props.externalPort,
        tools: props.tools,
        template: props.template,
      });
    }

    if (props.dataSources.length > 0) {
      console.log("\nGenerating context data...\n");
      await copyContextData(
        props.root,
        props.dataSources.filter((ds) => ds.type === "file"),
      );
      if (
        props.postInstallAction === "runApp" ||
        props.postInstallAction === "dependencies"
      ) {
        await generateContextData(
          props.framework,
          props.modelConfig,
          props.packageManager,
          props.vectorDb,
          props.llamaCloudKey,
          props.useLlamaParse,
        );
      }
    }

    // Create outputs directory
    await makeDir(path.join(props.root, "output/tools"));
    await makeDir(path.join(props.root, "output/uploaded"));
    await makeDir(path.join(props.root, "output/llamacloud"));
  } else {
    // this is a frontend for a full-stack app, create .env file with model information
    await createFrontendEnvFile(props.root, {
      customApiPath: props.customApiPath,
    });
  }
};

export * from "./types";