import axios, { AxiosResponse } from "axios";
import chalk from "chalk";
import ora from "ora";
import path from "path";
import { bundleSchema } from "../../bundler/index.js";
import { version } from "../../index.js";
import { Context } from "./context.js";
import { poll, fatalServerErr, deprecationCheckWarning } from "./utils.js";

type IndexMetadata = {
  table: string;
  name: string;
  fields: string[];
  backfill: {
    state: "in_progress" | "done";
  };
};

function stringifyIndex(index: IndexMetadata) {
  return `${index.table}.${index.name} ${JSON.stringify(index.fields)}`;
}

function diffIndexes(indexes: {
  added: IndexMetadata[];
  dropped: IndexMetadata[];
}) {
  let indexDiff = "";
  if (indexes.dropped.length > 0) {
    indexDiff += "Delete the following indexes:\n";
    for (const index of indexes.dropped) {
      indexDiff += `[-] ${stringifyIndex(index)}\n`;
    }
  }
  if (indexes.added.length > 0) {
    indexDiff += "Add the following indexes:\n";
    for (const index of indexes.added) {
      indexDiff += `[+] ${stringifyIndex(index)}\n`;
    }
  }
  return indexDiff;
}

export async function buildIndexes(
  ctx: Context,
  origin: string,
  adminKey: string,
  schemaDir: string,
  dryRun: boolean
): Promise<void> {
  if (!ctx.fs.exists(path.resolve(schemaDir, "schema.ts"))) {
    // Don't do anything.
    return;
  }
  const bundles = await bundleSchema(ctx.fs, schemaDir);
  const spinner = (ctx.spinner = ora({
    text: "Checking for changed table indexes...",
    stream: process.stdout,
  }));

  if (!dryRun) {
    spinner.start();
  }

  try {
    const res = await axios.post<{
      added: IndexMetadata[];
      dropped: IndexMetadata[];
    }>(`${origin}/api/${version}/build_indexes`, {
      bundle: bundles[0],
      adminKey,
      dryRun,
    });
    deprecationCheckWarning(ctx, res);

    const indexDiff = diffIndexes(res.data);
    if (indexDiff !== "") {
      console.log(
        chalk.bold(
          `\nIndexes ${
            dryRun ? "would" : "will"
          } be overwritten with the following changes:`
        )
      );
      console.log(indexDiff);
    }

    if (dryRun) {
      return;
    }
    spinner.text = "Waiting for all table indexes to be backfilled...";
    await waitForIndexesToBuild(origin, adminKey);
    res.data.added.length > 0
      ? spinner.succeed(chalk.green("Successfully backfilled table indexes."))
      : res.data.dropped.length > 0
      ? spinner.succeed(
          chalk.green("Successfully dropped deleted table indexes.")
        )
      : spinner.stop();
  } catch (err) {
    spinner.fail(chalk.red("Error: Unable to build indexes on", origin));
    return await fatalServerErr(ctx, err);
  }
}

async function waitForIndexesToBuild(origin: string, adminKey: string) {
  const fetch = () =>
    axios.get<{ indexes: IndexMetadata[] }>(
      `${origin}/api/${version}/get_indexes`,
      {
        headers: { Authorization: `Convex ${adminKey}` },
      }
    );
  const validate = (result: AxiosResponse<{ indexes: IndexMetadata[] }, any>) =>
    result.data.indexes.every(index => index.backfill.state === "done");
  await poll(fetch, validate);
}
