import fs from "fs";
import path from "path";
import { prepareDatabaseRuntime } from "../database/index.mjs";
import { taskNeedsLocalRuntime } from "./planning.mjs";
import { prepareRuntimeServices } from "./runtime-preparation.mjs";
import { writeGraphMetadata } from "./state.mjs";
import { startLocalServices, stopLocalServices } from "./services.mjs";
import { resolveRuntimeInstanceConfigs } from "./template.mjs";

export function createRuntimeInstanceContext(runtimeId, graph, productDir) {
  const graphDir = path.join(productDir, ".testkit", "_graphs", graph.dirName);
  const runtimeDir = path.join(graphDir, "runtimes", runtimeId);
  fs.mkdirSync(runtimeDir, { recursive: true });
  writeGraphMetadata(graphDir, graph);

  const runtimeConfigs = resolveRuntimeInstanceConfigs(graph.runtimeConfigs, runtimeId, runtimeDir, {
    graphDirName: graph.dirName,
    portNamespaceIndex: graph.portNamespaceIndex,
    portNamespaceStride: graph.portNamespaceStride,
  });

  return {
    graphKey: graph.key,
    targetNames: [...graph.targetNames],
    graphDir,
    runtimeDir,
    runtimeId,
    runtimeConfigs,
    configByName: new Map(runtimeConfigs.map((config) => [config.name, config])),
    prepared: false,
    preparationPromise: null,
    started: false,
    startupPromise: null,
    startedServices: [],
  };
}

export async function ensureRuntimeInstanceReady(context, task, lifecycle, options = {}) {
  if (!context.prepared) {
    if (!context.preparationPromise) {
      context.preparationPromise = (async () => {
        await prepareDatabases(context.runtimeConfigs);
        await prepareRuntimeServices(context.runtimeConfigs, options);
        context.prepared = true;
      })().finally(() => {
        context.preparationPromise = null;
      });
    }
    await context.preparationPromise;
  }

  if (taskNeedsLocalRuntime(task) && !context.started) {
    if (!context.startupPromise) {
      context.startupPromise = (async () => {
        context.startedServices = await startLocalServices(
          context.runtimeConfigs,
          lifecycle,
          options
        );
        context.started = true;
      })().finally(() => {
        context.startupPromise = null;
      });
    }
    await context.startupPromise;
  }

  return context;
}

export async function deactivateRuntimeInstanceContext(context, lifecycle) {
  if (!context?.started) return;
  await stopLocalServices(context.startedServices, lifecycle);
  context.started = false;
  context.startedServices = [];
}

export async function cleanupRuntimeInstanceContext(context, lifecycle) {
  if (!context) return;
  await deactivateRuntimeInstanceContext(context, lifecycle);
}

export async function prepareDatabases(runtimeConfigs) {
  for (const config of runtimeConfigs) {
    await prepareDatabaseRuntime(config);
  }
}
