import fs from "fs";
import path from "path";
import { execa } from "execa";
import { bundleK6File } from "../bundler/index.mjs";
import { resolveK6Binary } from "../config/index.mjs";
import { determineDefaultRuntimeFailure } from "./default-runtime-errors.mjs";
import { formatBatchDescriptor } from "./formatting.mjs";
import { buildExecutionEnv } from "./template.mjs";
import { readDatabaseUrl } from "./state-io.mjs";

export async function runHttpK6Batch(targetConfig, batch, lifecycle) {
  const baseUrl = targetConfig.testkit.local?.baseUrl;
  if (!baseUrl) {
    throw new Error(`Service "${targetConfig.name}" requires local.baseUrl for HTTP suites`);
  }

  console.log(
    `\n── ${targetConfig.workerLabel} ${batch.type}:${batch.tasks[0].suiteName}${formatBatchDescriptor(batch)} ──`
  );

  return Promise.all(
    batch.tasks.map((task) => runHttpK6Task(targetConfig, task, baseUrl, lifecycle))
  );
}

export async function runDalBatch(targetConfig, batch, lifecycle) {
  const databaseUrl = readDatabaseUrl(targetConfig.stateDir);
  if (!databaseUrl) {
    throw new Error(`Service "${targetConfig.name}" requires a database for DAL suites`);
  }

  console.log(
    `\n── ${targetConfig.workerLabel} ${batch.type}:${batch.tasks[0].suiteName}${formatBatchDescriptor(batch)} ──`
  );

  return Promise.all(
    batch.tasks.map((task) => runDalTask(targetConfig, task, databaseUrl, lifecycle))
  );
}

async function runHttpK6Task(targetConfig, task, baseUrl, lifecycle) {
  const bundledFile = await bundleK6File({
    productDir: targetConfig.productDir,
    serviceName: targetConfig.name,
    sourceFile: path.join(targetConfig.productDir, task.file),
  });
  console.log(`·· ${targetConfig.workerLabel}:${task.suiteName} → ${task.file}`);
  return runDefaultRuntimeTask(
    targetConfig,
    task,
    ["run", "--address", "127.0.0.1:0", "-e", `BASE_URL=${baseUrl}`, bundledFile],
    lifecycle
  );
}

async function runDalTask(targetConfig, task, databaseUrl, lifecycle) {
  const bundledFile = await bundleK6File({
    productDir: targetConfig.productDir,
    serviceName: targetConfig.name,
    sourceFile: path.join(targetConfig.productDir, task.file),
  });
  console.log(`·· ${targetConfig.workerLabel}:${task.suiteName} → ${task.file}`);
  return runDefaultRuntimeTask(
    targetConfig,
    task,
    ["run", "--address", "127.0.0.1:0", "-e", `DATABASE_URL=${databaseUrl}`, bundledFile],
    lifecycle
  );
}

export async function runDefaultRuntimeTask(targetConfig, task, args, lifecycle, firstLine) {
  const k6Binary = resolveK6Binary();
  const summaryFile = buildDefaultRuntimeSummaryPath(targetConfig, task);
  fs.mkdirSync(path.dirname(summaryFile), { recursive: true });
  const startedAt = Date.now();
  const result = await execa(
    k6Binary,
    [...args.slice(0, 1), "--summary-export", summaryFile, ...args.slice(1)],
    {
      cwd: targetConfig.productDir,
      env: buildExecutionEnv(targetConfig, {}, process.env),
      reject: false,
      cancelSignal: lifecycle.signal,
      forceKillAfterDelay: 5_000,
    }
  );

  if (result.stdout) process.stdout.write(result.stdout);
  if (result.stderr) process.stderr.write(result.stderr);

  const summary = readDefaultRuntimeSummary(summaryFile);
  const runtimeError = determineDefaultRuntimeFailure(result, summary, firstLine);
  const finishedAt = Date.now();

  return {
    task,
    failed: runtimeError !== null,
    error: runtimeError,
    durationMs: finishedAt - startedAt,
    startedAt,
    finishedAt,
  };
}

export function buildDefaultRuntimeSummaryPath(targetConfig, task) {
  return path.join(
    targetConfig.stateDir || path.join(targetConfig.productDir, ".testkit"),
    "_runtime",
    `task-${task.id}.summary.json`
  );
}

export function readDefaultRuntimeSummary(filePath) {
  try {
    return JSON.parse(fs.readFileSync(filePath, "utf8"));
  } catch {
    return null;
  }
}
