import { resolveServiceCwd } from "../config/index.mjs";
import {
  announceResolvedToolchain,
  applyToolchainEnv,
  resolveConfiguredToolchain,
} from "../toolchains/index.mjs";
import { buildExecutionEnv, numericPortFromUrl } from "./template.mjs";
import { DEFAULT_READY_TIMEOUT_MS, assertLocalServicePortsAvailable, isPortInUse, waitForReady } from "./readiness.mjs";
import { killChildProcess, pipeOutput, startDetachedCommand, stopChildProcess, sleep } from "./processes.mjs";
import { readDatabaseUrl } from "./state-io.mjs";

export async function startLocalServices(runtimeConfigs, lifecycle) {
  const started = [];

  try {
    for (const config of runtimeConfigs) {
      if (!config.testkit.local) continue;
      const proc = await startLocalService(config, lifecycle);
      started.push(proc);
    }
  } catch (error) {
    await stopLocalServices(started, lifecycle);
    throw error;
  }

  return started;
}

export async function startLocalService(config, lifecycle) {
  const cwd = resolveServiceCwd(config.productDir, config.testkit.local.cwd);
  const resolvedToolchain = await resolveConfiguredToolchain(config);
  await announceResolvedToolchain(config, resolvedToolchain);
  const env = applyToolchainEnv(
    buildExecutionEnv(config, config.testkit.local.env, process.env),
    resolvedToolchain
  );
  const port = config.testkit.local.port || numericPortFromUrl(config.testkit.local.baseUrl);
  if (port) {
    env.PORT = String(port);
  }

  const dbUrl = readDatabaseUrl(config.stateDir);
  if (dbUrl) {
    env.DATABASE_URL = dbUrl;
  }

  await assertLocalServicePortsAvailable(config, isPortInUse);

  console.log(`Starting ${config.runtimeLabel}:${config.name}: ${config.testkit.local.start}`);
  const child = startDetachedCommand(config.testkit.local.start, cwd, env);

  const outputDrains = [
    pipeOutput(child.stdout, `[${config.runtimeLabel}:${config.name}]`),
    pipeOutput(child.stderr, `[${config.runtimeLabel}:${config.name}]`),
  ];
  lifecycle.registerService(config, child, cwd, () => {
    killChildProcess(child, "SIGTERM");
  });

  const readyTimeoutMs = config.testkit.local.readyTimeoutMs || DEFAULT_READY_TIMEOUT_MS;

  try {
    await waitForReady({
      name: `${config.runtimeLabel}:${config.name}`,
      url: config.testkit.local.readyUrl,
      timeoutMs: readyTimeoutMs,
      process: child,
      signal: lifecycle.signal,
      sleep,
    });
  } catch (error) {
    await stopChildProcess(child, outputDrains);
    lifecycle.unregisterService(child.pid);
    throw error;
  }

  return { name: config.name, child, outputDrains };
}

export async function stopLocalServices(started, lifecycle) {
  for (const service of [...started].reverse()) {
    await stopChildProcess(service.child, service.outputDrains);
    lifecycle?.unregisterService(service.child.pid);
  }
}
