import path from "node:path";
import { createHostRunner } from "@poe-code/process-runner";
import { loadEval } from "../source/registry.js";
import { assertCanonicalContainedPath } from "../path-boundary.js";
import { terminateRunHandle } from "./subprocess-termination.js";
export async function verifyOracle(source, id) {
    const evalDef = await loadEval(source, id);
    if (evalDef.verify === undefined) {
        return {
            passed: true,
            output: "no verify command configured"
        };
    }
    const evalDir = path.resolve(source.rootDir, id);
    const oracleDir = path.join(evalDir, "oracle");
    await assertCanonicalContainedPath(evalDir, oracleDir, "oracle.path");
    const result = await runVerifyCommand(createHostRunner(), {
        command: evalDef.verify.command,
        cwd: oracleDir,
        env: createOracleEnv(oracleDir),
        timeoutMs: evalDef.verify.timeoutMs
    });
    return {
        passed: result.exitCode === 0 && !result.timedOut,
        output: result.output
    };
}
async function runVerifyCommand(runner, input) {
    const handle = runner.exec(createShellRunSpec(input));
    const stdout = captureStream(handle.stdout);
    const stderr = captureStream(handle.stderr);
    const timedResult = await waitForResult(handle, input.timeoutMs);
    if (timedResult.timedOut) {
        await terminateRunHandle(handle);
        return {
            exitCode: timedResult.exitCode,
            output: appendTimeoutNote(`${stdout.output()}${stderr.output()}`, `verification timed out after ${input.timeoutMs}ms`),
            timedOut: true
        };
    }
    await Promise.all([stdout.finished, stderr.finished]);
    return {
        exitCode: timedResult.exitCode,
        output: `${stdout.output()}${stderr.output()}`,
        timedOut: timedResult.timedOut
    };
}
function createShellRunSpec(input) {
    if (process.platform === "win32") {
        return {
            command: "cmd.exe",
            args: ["/d", "/s", "/c", input.command],
            cwd: input.cwd,
            env: input.env,
            stdout: "pipe",
            stderr: "pipe",
            killProcessGroup: true
        };
    }
    return {
        command: "sh",
        args: ["-c", input.command],
        cwd: input.cwd,
        env: input.env,
        stdout: "pipe",
        stderr: "pipe",
        killProcessGroup: true
    };
}
function createOracleEnv(oracleDir) {
    const env = Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] !== undefined));
    env.ORACLE_DIR = oracleDir;
    return env;
}
async function waitForResult(handle, timeoutMs) {
    let timeout = null;
    const timeoutPromise = new Promise((resolve) => {
        timeout = setTimeout(() => {
            resolve({ exitCode: 1, timedOut: true });
        }, timeoutMs);
    });
    const result = await Promise.race([
        handle.result.then((runResult) => ({
            exitCode: runResult.exitCode,
            timedOut: false
        })),
        timeoutPromise
    ]);
    if (timeout !== null) {
        clearTimeout(timeout);
    }
    return result;
}
function captureStream(stream) {
    if (stream === null) {
        return {
            output: () => "",
            finished: Promise.resolve()
        };
    }
    let output = "";
    stream.setEncoding("utf8");
    const finished = new Promise((resolve, reject) => {
        stream.on("data", (chunk) => {
            output += chunk;
        });
        stream.once("end", resolve);
        stream.once("error", reject);
    });
    return {
        output: () => output,
        finished
    };
}
function appendTimeoutNote(output, note) {
    if (note === undefined) {
        return output;
    }
    if (output.length === 0) {
        return note;
    }
    return output.endsWith("\n") ? `${output}${note}` : `${output}\n${note}`;
}
