import path from "path";
import { execa } from "execa";
import { parsePlaywrightJsonResults } from "../reporters/playwright.mjs";
import { resolveServiceCwd } from "../config/index.mjs";
import { formatFileTimeoutBudgetError } from "../shared/file-timeout.mjs";
import { persistTaskOutputArtifacts } from "./artifacts.mjs";
import { settleSubprocess } from "./default-runtime-runner.mjs";
import { ensurePlaywrightTestConfig } from "./playwright-config.mjs";
import { normalizePathSeparators } from "./state.mjs";
import { buildPlaywrightEnv } from "./template.mjs";
import { killChildProcess } from "./processes.mjs";

export async function runPlaywrightTask(targetConfig, task, lifecycle, lease, reporter = null) {
  const local = targetConfig.testkit.local;
  if (!local?.baseUrl) {
    throw new Error(
      `Service "${targetConfig.name}" requires local.baseUrl for Playwright suites`
    );
  }

  const cwd = resolveServiceCwd(targetConfig.productDir, local.cwd);
  const requestedFile = path.relative(cwd, path.join(targetConfig.productDir, task.file));
  const playwrightConfigPath = ensurePlaywrightTestConfig(targetConfig, cwd, [requestedFile], lease);
  if (lifecycle.isStopRequested()) {
    throw new Error(`testkit run interrupted before starting ${task.file}`);
  }
  const startedAt = Date.now();
  const fileTimeoutSeconds = targetConfig.testkit.execution.fileTimeoutSeconds;
  const subprocess = execa(
    "npx",
    ["playwright", "test", "--config", playwrightConfigPath, "--reporter=json"],
    {
      cwd,
      env: buildPlaywrightEnv(targetConfig, local.baseUrl, lease, process.env),
      reject: false,
      forceKillAfterDelay: 5_000,
    }
  );
  lifecycle.registerProcess(subprocess, () => {
    killChildProcess(subprocess, "SIGINT");
  });
  if (lifecycle.isStopRequested()) {
    const interruptSubprocess = () => killChildProcess(subprocess, "SIGINT");
    if (subprocess.pid) interruptSubprocess();
    else subprocess.once?.("spawn", interruptSubprocess);
  }
  reporter?.taskStarted?.(task, targetConfig);
  let result;
  let timedOut;
  try {
    ({ result, timedOut } = await settleSubprocess(subprocess, fileTimeoutSeconds));
  } finally {
    lifecycle.unregisterProcess(subprocess.pid);
  }

  const parsed = parsePlaywrightJsonResults(result.stdout || "", cwd);
  const finishedAt = Date.now();
  const durationMs = finishedAt - startedAt;
  const relativeFile = normalizePathSeparators(requestedFile);
  const fileResult = parsed.fileResults.get(relativeFile);
  const outputArtifacts = persistTaskOutputArtifacts(targetConfig.productDir, task, [
    result.stderr
      ? {
          name: "playwright-stderr",
          kind: "runtime.output",
          summary: result.stderr.split(/\r?\n/).map((line) => line.trim()).find(Boolean) || "captured stderr",
          stream: "stderr",
          text: result.stderr,
        }
      : null,
  ]);
  const genericError = timedOut
    ? formatFileTimeoutBudgetError(fileTimeoutSeconds)
    : result.exitCode === 0
      ? parsed.errors[0] || null
      : parsed.errors[0] || result.stderr.trim() || `Playwright exited with code ${result.exitCode}`;

  return {
    task,
    failed: timedOut ? true : fileResult ? fileResult.status === "failed" : result.exitCode !== 0,
    status: timedOut ? "failed" : fileResult?.status || (result.exitCode === 0 ? "passed" : "failed"),
    error: timedOut ? genericError : fileResult?.error || genericError,
    durationMs: fileResult?.durationMs > 0 ? fileResult.durationMs : durationMs,
    startedAt,
    finishedAt,
    artifacts: outputArtifacts,
    failureDetails: timedOut ? [] : fileResult?.failureDetails || [],
  };
}
