#!/usr/bin/env node
import {
  TEST_TIMEOUT
} from "./chunk-KRU4DFRH.js";

// src/tools/builtin/run-tests.ts
import { execSync, spawnSync } from "child_process";
import { existsSync, readFileSync, readdirSync } from "fs";
import { join } from "path";
import { platform } from "os";
import chalk from "chalk";
var FILTER_WHITELIST = /^[\w.\-/:#\s*?()\[\]@+]{1,200}$/;
var IS_WINDOWS = platform() === "win32";
function detectNodeTestFramework(cwd, pkg) {
  const devDeps = pkg.devDependencies ?? {};
  const deps = pkg.dependencies ?? {};
  const allDeps = { ...deps, ...devDeps };
  if ("vitest" in allDeps || existsSync(join(cwd, "vitest.config.ts")) || existsSync(join(cwd, "vitest.config.js")) || existsSync(join(cwd, "vitest.config.mts"))) {
    return { type: "node", framework: "vitest", command: "npx vitest run" };
  }
  if ("jest" in allDeps || existsSync(join(cwd, "jest.config.js")) || existsSync(join(cwd, "jest.config.ts")) || existsSync(join(cwd, "jest.config.mjs"))) {
    return { type: "node", framework: "jest", command: "npx jest" };
  }
  if ("mocha" in allDeps || existsSync(join(cwd, ".mocharc.yml")) || existsSync(join(cwd, ".mocharc.yaml")) || existsSync(join(cwd, ".mocharc.json")) || existsSync(join(cwd, ".mocharc.js"))) {
    return { type: "node", framework: "mocha", command: "npx mocha" };
  }
  if ("ava" in allDeps) {
    return { type: "node", framework: "ava", command: "npx ava" };
  }
  if ("@playwright/test" in allDeps) {
    return { type: "node", framework: "playwright", command: "npx playwright test" };
  }
  return null;
}
function safeReadPackageJson(cwd) {
  const filePath = join(cwd, "package.json");
  let raw;
  try {
    raw = readFileSync(filePath, "utf-8");
  } catch (err) {
    const code = err.code;
    if (code === "EACCES" || code === "EPERM") {
      process.stderr.write(`[Warning] Permission denied reading package.json (${code})
`);
    } else {
      process.stderr.write(
        `[Warning] Cannot read package.json: ${err instanceof Error ? err.message : String(err)}
`
      );
    }
    return null;
  }
  if (raw.charCodeAt(0) === 65279) {
    raw = raw.slice(1);
  }
  if (!raw.trim()) {
    process.stderr.write("[Warning] package.json is empty\n");
    return null;
  }
  try {
    const parsed = JSON.parse(raw);
    if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
      process.stderr.write("[Warning] package.json root is not a JSON object\n");
      return null;
    }
    return parsed;
  } catch (err) {
    const msg = err instanceof Error ? err.message : String(err);
    if (msg.includes("Unexpected token")) {
      process.stderr.write(`[Warning] package.json has syntax error: ${msg}
`);
    } else if (msg.includes("Unexpected end")) {
      process.stderr.write("[Warning] package.json is truncated or incomplete\n");
    } else {
      process.stderr.write(`[Warning] package.json parse failed: ${msg}
`);
    }
    return null;
  }
}
function detectProject(cwd) {
  if (existsSync(join(cwd, "pom.xml"))) {
    return { type: "java", framework: "Maven (JUnit)", command: IS_WINDOWS ? "mvn.cmd test" : "mvn test" };
  }
  if (existsSync(join(cwd, "build.gradle")) || existsSync(join(cwd, "build.gradle.kts"))) {
    const wrapper = IS_WINDOWS ? "gradlew.bat" : "./gradlew";
    const cmd = existsSync(join(cwd, IS_WINDOWS ? "gradlew.bat" : "gradlew")) ? `${wrapper} test` : "gradle test";
    return { type: "java", framework: "Gradle (JUnit)", command: cmd };
  }
  if (existsSync(join(cwd, "package.json"))) {
    const pkg = safeReadPackageJson(cwd);
    if (pkg) {
      const testScript = pkg.scripts?.test;
      if (testScript && testScript !== 'echo "Error: no test specified" && exit 1') {
        return { type: "node", framework: "npm", command: "npm test" };
      }
      const frameworkDetected = detectNodeTestFramework(cwd, pkg);
      if (frameworkDetected) return frameworkDetected;
    } else {
      return { type: "node", framework: "npm (package.json error)", command: "npm test" };
    }
  }
  if (existsSync(join(cwd, "pyproject.toml")) || existsSync(join(cwd, "setup.py")) || existsSync(join(cwd, "pytest.ini"))) {
    return { type: "python", framework: "pytest", command: "pytest -v" };
  }
  if (existsSync(join(cwd, "Cargo.toml"))) {
    return { type: "rust", framework: "cargo", command: "cargo test" };
  }
  if (existsSync(join(cwd, "go.mod"))) {
    return { type: "go", framework: "go test", command: "go test ./..." };
  }
  return null;
}
function parseJUnitXml(xmlContent) {
  const summary = { tests: 0, passed: 0, failures: 0, errors: 0, skipped: 0, duration: 0, failedTests: [] };
  const suiteMatch = xmlContent.match(/<testsuite[^>]*\btests="(\d+)"[^>]*/);
  if (suiteMatch) {
    summary.tests = parseInt(suiteMatch[1], 10);
    const failMatch = xmlContent.match(/<testsuite[^>]*\bfailures="(\d+)"/);
    const errMatch = xmlContent.match(/<testsuite[^>]*\berrors="(\d+)"/);
    const skipMatch = xmlContent.match(/<testsuite[^>]*\bskipped="(\d+)"/);
    const timeMatch = xmlContent.match(/<testsuite[^>]*\btime="([^"]*)"/);
    summary.failures = failMatch ? parseInt(failMatch[1], 10) : 0;
    summary.errors = errMatch ? parseInt(errMatch[1], 10) : 0;
    summary.skipped = skipMatch ? parseInt(skipMatch[1], 10) : 0;
    summary.duration = timeMatch ? parseFloat(timeMatch[1]) : 0;
    summary.passed = summary.tests - summary.failures - summary.errors - summary.skipped;
  }
  const tcPattern = /<testcase[^>]*\bname="([^"]*)"[^>]*classname="([^"]*)"[^>]*>[\s\S]*?<(failure|error)[^>]*>([^<]*)/g;
  let m;
  while ((m = tcPattern.exec(xmlContent)) !== null) {
    summary.failedTests.push({
      name: `${m[2]}#${m[1]}`,
      message: m[4].trim().slice(0, 200)
    });
  }
  return summary;
}
function findJUnitReports(cwd) {
  const dirs = [
    join(cwd, "target", "surefire-reports"),
    // Maven
    join(cwd, "build", "test-results", "test"),
    // Gradle
    join(cwd, "build", "test-results")
    // Gradle (older)
  ];
  const xmlFiles = [];
  for (const dir of dirs) {
    if (!existsSync(dir)) continue;
    try {
      const files = readdirSync(dir);
      for (const f of files) {
        if (f.endsWith(".xml")) xmlFiles.push(join(dir, f));
      }
    } catch {
    }
  }
  return xmlFiles;
}
function parseGenericOutput(output) {
  const mvn = output.match(/Tests run:\s*(\d+),\s*Failures:\s*(\d+),\s*Errors:\s*(\d+),\s*Skipped:\s*(\d+)/);
  if (mvn) {
    const [, tests, failures, errors, skipped] = mvn.map(Number);
    return { tests, failures, errors, skipped, passed: tests - failures - errors - skipped };
  }
  const jest = output.match(/Tests:\s+(?:(\d+)\s+passed,?\s*)?(?:(\d+)\s+failed,?\s*)?(?:(\d+)\s+skipped,?\s*)?(\d+)\s+total/);
  if (jest) {
    const passed = parseInt(jest[1] ?? "0", 10);
    const failures = parseInt(jest[2] ?? "0", 10);
    const skipped = parseInt(jest[3] ?? "0", 10);
    const tests = parseInt(jest[4], 10);
    return { tests, passed, failures, skipped, errors: 0 };
  }
  const pytest = output.match(/(\d+)\s+passed(?:.*?(\d+)\s+failed)?(?:.*?(\d+)\s+error)?/);
  if (pytest) {
    const passed = parseInt(pytest[1], 10);
    const failures = parseInt(pytest[2] ?? "0", 10);
    const errors = parseInt(pytest[3] ?? "0", 10);
    return { tests: passed + failures + errors, passed, failures, errors, skipped: 0 };
  }
  const cargo = output.match(/test result:.*?(\d+)\s+passed;\s*(\d+)\s+failed;\s*(\d+)\s+ignored/);
  if (cargo) {
    const passed = parseInt(cargo[1], 10);
    const failures = parseInt(cargo[2], 10);
    const skipped = parseInt(cargo[3], 10);
    return { tests: passed + failures + skipped, passed, failures, skipped, errors: 0 };
  }
  const goPass = (output.match(/^ok\s/gm) ?? []).length;
  const goFail = (output.match(/^FAIL\s/gm) ?? []).length;
  if (goPass + goFail > 0) {
    return { tests: goPass + goFail, passed: goPass, failures: goFail, errors: 0, skipped: 0 };
  }
  return null;
}
function formatReport(summary, framework, output, _exitCode) {
  const status = summary.failures + summary.errors > 0 ? "FAILED \u2717" : "PASSED \u2713";
  const lines = [];
  lines.push(`## Test Results \u2014 ${status}`);
  lines.push(`**Total: ${summary.tests} | Passed: ${summary.passed} | Failed: ${summary.failures}${summary.errors > 0 ? ` | Errors: ${summary.errors}` : ""} | Skipped: ${summary.skipped}**`);
  if (summary.duration > 0) {
    lines.push(`Duration: ${summary.duration.toFixed(1)}s | Framework: ${framework}`);
  } else {
    lines.push(`Framework: ${framework}`);
  }
  if (summary.failedTests.length > 0) {
    lines.push("");
    lines.push("### Failed Tests");
    for (const ft of summary.failedTests) {
      lines.push(`- ${ft.name} \u2014 ${ft.message}`);
    }
  }
  const outputLines = output.split("\n");
  const tail = outputLines.length > 150 ? outputLines.slice(-150) : outputLines;
  lines.push("");
  lines.push(`### Output (last ${tail.length} lines)`);
  lines.push("```");
  lines.push(tail.join("\n"));
  lines.push("```");
  return lines.join("\n");
}
function renderColorReport(summary, framework) {
  const isPass = summary.failures + summary.errors === 0;
  const status = isPass ? chalk.green.bold("PASSED \u2713") : chalk.red.bold("FAILED \u2717");
  console.log();
  console.log(`  ${chalk.bold("Test Results")} \u2014 ${status}`);
  console.log(
    `  Total: ${chalk.bold(String(summary.tests))} | ${chalk.green(`Passed: ${summary.passed}`)} | ${chalk.red(`Failed: ${summary.failures}`)}` + (summary.errors > 0 ? ` | ${chalk.red(`Errors: ${summary.errors}`)}` : "") + ` | ${chalk.yellow(`Skipped: ${summary.skipped}`)}`
  );
  if (summary.duration > 0) {
    console.log(`  Duration: ${summary.duration.toFixed(1)}s | Framework: ${framework}`);
  }
  if (summary.failedTests.length > 0) {
    console.log(chalk.red("\n  Failed Tests:"));
    for (const ft of summary.failedTests) {
      console.log(chalk.red(`    - ${ft.name}`));
      if (ft.message) console.log(chalk.dim(`      ${ft.message}`));
    }
  }
  console.log();
}
function needsWindowsShell(file) {
  if (!IS_WINDOWS) return false;
  const lower = file.toLowerCase();
  return lower.endsWith(".cmd") || lower.endsWith(".bat") || lower === "mvn" || lower === "gradle" || lower === "npm" || lower === "npx";
}
function buildArgv(detected, filter) {
  switch (detected.type) {
    case "java": {
      const parts2 = detected.command.split(/\s+/);
      const file = parts2[0];
      const rest = parts2.slice(1);
      const args = [...rest];
      if (filter) {
        args.push(detected.framework.startsWith("Maven") ? `-Dtest=${filter}` : `--tests=${filter}`);
      }
      return { file, args };
    }
    case "python":
      return { file: "pytest", args: filter ? ["-v", "-k", filter] : ["-v"] };
    case "rust":
      return { file: "cargo", args: filter ? ["test", filter] : ["test"] };
    case "go":
      return filter ? { file: "go", args: ["test", "./...", "-run", filter] } : { file: "go", args: ["test", "./..."] };
    case "node": {
      if (detected.framework === "vitest") {
        return { file: "npx", args: filter ? ["vitest", "run", "-t", filter] : ["vitest", "run"] };
      }
      if (detected.framework === "jest") {
        return { file: "npx", args: filter ? ["jest", "-t", filter] : ["jest"] };
      }
      if (detected.framework === "mocha") {
        return { file: "npx", args: filter ? ["mocha", "--grep", filter] : ["mocha"] };
      }
      if (detected.framework === "playwright") {
        return { file: "npx", args: filter ? ["playwright", "test", "--grep", filter] : ["playwright", "test"] };
      }
      if (detected.framework === "ava") {
        return { file: "npx", args: filter ? ["ava", "--match", filter] : ["ava"] };
      }
      return filter ? { file: "npm", args: ["test", "--", "--grep", filter] } : { file: "npm", args: ["test"] };
    }
  }
  const parts = detected.command.split(/\s+/);
  return { file: parts[0], args: parts.slice(1) };
}
async function executeTests(args) {
  const cwd = process.cwd();
  const customCmd = args["command"] ? String(args["command"]).trim() : "";
  const filter = args["filter"] ? String(args["filter"]).trim() : "";
  if (filter && !FILTER_WHITELIST.test(filter)) {
    return `Error: filter contains characters that are not allowed in test names. Allowed: word chars, dots, dashes, slashes, colons, parens, spaces, and glob (*?). Rejected: quotes, semicolons, pipes, backticks, redirects, and shell metacharacters.
Got: ${filter.slice(0, 60)}${filter.length > 60 ? "..." : ""}`;
  }
  let command;
  let framework;
  let argv = null;
  if (customCmd) {
    command = customCmd;
    framework = "custom";
  } else {
    const detected = detectProject(cwd);
    if (!detected) {
      return "Error: Could not detect project type. No pom.xml, build.gradle, package.json, pyproject.toml, Cargo.toml, or go.mod found.\nPlease specify a test command using the `command` parameter.";
    }
    command = detected.command;
    framework = detected.framework;
    argv = buildArgv(detected, filter);
  }
  let output;
  let exitCode = 0;
  if (argv) {
    const r = spawnSync(argv.file, argv.args, {
      cwd,
      timeout: TEST_TIMEOUT,
      stdio: ["pipe", "pipe", "pipe"],
      // shell:false is the default for spawnSync; on Windows .cmd/.bat
      // requires shell:true, so we opt-in only for known wrapper names.
      shell: needsWindowsShell(argv.file),
      env: {
        ...process.env,
        ...IS_WINDOWS ? {} : { FORCE_COLOR: "0" }
      },
      encoding: "utf-8",
      maxBuffer: 64 * 1024 * 1024
    });
    if (r.error) {
      output = `Failed to launch test runner: ${r.error.message}`;
      exitCode = 1;
    } else {
      exitCode = r.status ?? 1;
      output = (r.stdout ?? "") + (r.stderr ? "\n" + r.stderr : "");
    }
  } else {
    try {
      const buf = execSync(command, {
        cwd,
        timeout: TEST_TIMEOUT,
        encoding: "buffer",
        stdio: ["pipe", "pipe", "pipe"],
        env: {
          ...process.env,
          ...IS_WINDOWS ? {} : { FORCE_COLOR: "0" }
        }
      });
      output = buf.toString("utf-8");
    } catch (err) {
      const e = err;
      exitCode = e.status ?? 1;
      const stdout = e.stdout?.toString("utf-8") ?? "";
      const stderr = e.stderr?.toString("utf-8") ?? "";
      output = stdout + (stderr ? "\n" + stderr : "");
    }
  }
  let summary = {
    tests: 0,
    passed: 0,
    failures: 0,
    errors: 0,
    skipped: 0,
    duration: 0,
    failedTests: []
  };
  const xmlFiles = findJUnitReports(cwd);
  if (xmlFiles.length > 0) {
    for (const xmlFile of xmlFiles) {
      try {
        const xml = readFileSync(xmlFile, "utf-8");
        const parsed = parseJUnitXml(xml);
        summary.tests += parsed.tests;
        summary.passed += parsed.passed;
        summary.failures += parsed.failures;
        summary.errors += parsed.errors;
        summary.skipped += parsed.skipped;
        summary.duration += parsed.duration;
        summary.failedTests.push(...parsed.failedTests);
      } catch {
      }
    }
  } else {
    const parsed = parseGenericOutput(output);
    if (parsed) {
      summary = { ...summary, ...parsed };
    } else {
      summary.tests = 1;
      if (exitCode === 0) {
        summary.passed = 1;
      } else {
        summary.failures = 1;
      }
    }
  }
  renderColorReport(summary, framework);
  return formatReport(summary, framework, output, exitCode);
}
var runTestsTool = {
  definition: {
    name: "run_tests",
    description: "Run project tests and return a structured report. Auto-detects project type (Maven/Gradle/npm/pytest/cargo/go). Returns test counts (passed/failed/skipped) and failed test details.",
    parameters: {
      command: {
        type: "string",
        description: "Optional: custom test command to run (overrides auto-detection)",
        required: false
      },
      filter: {
        type: "string",
        description: 'Optional: test name filter/pattern (e.g., "ExamService" to run only matching tests)',
        required: false
      }
    }
  },
  async execute(args) {
    return executeTests(args);
  }
};

export {
  executeTests,
  runTestsTool
};
