import chalk from "chalk";
import boxen from "boxen";
import {
  pullConfig,
  writeProjectConfig,
  configFilepath,
  readProjectConfig,
} from "./config.js";
import {
  fatalServerErr,
  functionsDir,
  validateOrSelectTeam,
  bigBrainAPI,
  loadPackageJson,
} from "./utils.js";
import inquirer from "inquirer";
import ora from "ora";
import path from "path";
import { doCodegen, doInitCodegen } from "./codegen";
import { Context } from "./context.js";

const cwd = path.basename(process.cwd());

export async function init(
  ctx: Context,
  project: string | null,
  team: string | null
) {
  {
    const configPath = await configFilepath(ctx);
    if (ctx.fs.exists(configPath)) {
      // Running init in a project with a convex.json file is a no-op.
      console.error(
        chalk.green(`Found existing project config "${configPath}"`)
      );
      return;
    }

    // Do opt in to TOS and Privacy Policy stuff first.
    const shouldContinue = await optins(ctx);
    if (!shouldContinue) {
      return await ctx.fatalError(1, undefined);
    }

    const selectedTeam = await validateOrSelectTeam(
      ctx,
      team,
      "Choose which team to create this project in:"
    );

    let projectName: string = project || cwd;
    if (process.stdin.isTTY && !project) {
      projectName = (
        await inquirer.prompt([
          {
            type: "input",
            name: "project",
            message: "Enter a name for your project:",
            default: cwd,
          },
        ])
      ).project;
    }

    const spinner = (ctx.spinner = ora({
      text: "Creating new Convex project...\n",
      stream: process.stdout,
    }).start());

    let projectSlug,
      teamSlug,
      prodUrl,
      adminKey,
      projectsRemaining,
      projectConfig,
      modules;
    try {
      ({ projectSlug, teamSlug, prodUrl, adminKey, projectsRemaining } =
        await create_project(ctx, selectedTeam, projectName));

      ({ projectConfig, modules } = await pullConfig(
        ctx,
        projectSlug,
        teamSlug,
        prodUrl,
        adminKey
      ));
    } catch (err) {
      spinner.fail("Unable to create project.");
      return await fatalServerErr(ctx, err);
    }

    spinner.succeed(`Successfully created project!`);

    console.log(
      chalk.green(
        `Your account now has ${projectsRemaining} projects remaining.`
      )
    );

    if (modules.length > 0) {
      console.error(chalk.red("Error: Unexpected modules in new project"));
      return await ctx.fatalError(1, undefined);
    }

    // create-react-app bans imports from outside of src, so we can just
    // put the functions directory inside of src/ to work around this issue.
    const packages = await loadPackageJson(ctx);
    const isCreateReactApp = !!packages.filter(
      ({ name }) => name === "react-scripts"
    ).length;
    if (isCreateReactApp) {
      projectConfig.functions = `src/${projectConfig.functions}`;
    }

    await writeProjectConfig(ctx, projectConfig);
    await doInitCodegen(
      ctx,
      functionsDir(configPath, projectConfig),
      true // quiet
    );

    {
      const { projectConfig, configPath } = await readProjectConfig(ctx);
      await doCodegen({
        ctx,
        projectConfig,
        configPath,
        deploymentType: "dev",
        // Don't typecheck because there isn't any code to check yet.
        typeCheckMode: "disable",
        quiet: true,
      });
    }

    const boxedText =
      chalk.white("Project ready:\n") +
      chalk.whiteBright.bold(projectName) +
      "\n" +
      chalk.whiteBright(projectConfig.project);
    const boxenOptions = {
      padding: 1,
      margin: 1,
      borderColor: "green",
      backgroundColor: "#555555",
    };
    console.log(boxen(boxedText, boxenOptions));

    console.log(
      chalk.green("Write your Convex functions in"),
      chalk.green.bold(functionsDir(configPath, projectConfig))
    );
    console.log("Configuration settings written to", chalk.bold(configPath));

    console.log(chalk.bold("\nWe would love feedback at either:"));
    console.log("- https://convex.dev/community");
    console.log("- support@convex.dev");

    console.log(
      "\nSee documentation at",
      chalk.bold("https://docs.convex.dev"),
      "for next steps."
    );
  }
}

interface CreateProjectArgs {
  projectName: string;
  team: string;
  backendVersionOverride?: string;
}

/** Provision a new empty project and return the origin. */
async function create_project(
  ctx: Context,
  team: string,
  projectName: string
): Promise<{
  projectSlug: string;
  teamSlug: string;
  prodUrl: string;
  adminKey: string;
  projectsRemaining: number;
}> {
  const provisioningArgs: CreateProjectArgs = {
    team,
    backendVersionOverride: process.env.CONVEX_BACKEND_VERSION_OVERRIDE,
    projectName,
  };
  const data = await bigBrainAPI(
    ctx,
    "POST",
    "create_project",
    provisioningArgs
  );

  const projectSlug = data.projectSlug;
  const teamSlug = data.teamSlug;
  const prodUrl = data.prodUrl;
  const adminKey = data.adminKey;
  const projectsRemaining = data.projectsRemaining;
  if (
    projectSlug === undefined ||
    teamSlug == undefined ||
    prodUrl === undefined ||
    adminKey === undefined ||
    projectsRemaining === undefined
  ) {
    throw new Error(
      "Unknown error during provisioning: " + JSON.stringify(data)
    );
  }
  return { projectSlug, teamSlug, prodUrl, adminKey, projectsRemaining };
}

/// There are fields like version, but we keep them opaque
type OptIn = Record<string, unknown>;

type OptInToAccept = {
  optIn: OptIn;
  message: string;
};

type AcceptOptInsArgs = {
  optInsAccepted: OptIn[];
};

// Returns whether we can proceed or not.
export async function optins(ctx: Context): Promise<boolean> {
  const data = await bigBrainAPI(ctx, "POST", "check_opt_ins", {});
  if (data.optInsToAccept.length == 0) {
    return true;
  }
  for (const optInToAccept of data.optInsToAccept) {
    const confirmed = (
      await inquirer.prompt([
        {
          type: "confirm",
          name: "confirmed",
          message: optInToAccept.message,
        },
      ])
    ).confirmed;

    if (!confirmed) {
      console.log("Please accept the Terms of Service to use Convex.");
      return Promise.resolve(false);
    }
  }

  const optInsAccepted = data.optInsToAccept.map((o: OptInToAccept) => o.optIn);
  const args: AcceptOptInsArgs = { optInsAccepted };
  await bigBrainAPI(ctx, "POST", "accept_opt_ins", args);
  return true;
}
