import { execFile } from "node:child_process";
import { existsSync } from "node:fs";
import { join } from "node:path";

/**
 * UOK Security Gate
 *
 * Runs the workspace secret-scan.sh against uncommitted changes.
 * Prevents the kernel from finalizing a turn if potential secrets are detected.
 */
export class SecurityGate {
	constructor() {
		this.id = "security-guard";
		this.type = "security";
	}

	/**
	 * @param {import("./contracts.js").UokContext} ctx
	 * @param {number} _attempt
	 */
	async execute(ctx, _attempt) {
		const scriptPath = join(ctx.basePath, "scripts/secret-scan.sh");

		if (!existsSync(scriptPath)) {
			return {
				outcome: "pass",
				rationale: "Security scan skipped: scripts/secret-scan.sh not found.",
			};
		}

		try {
			await execFilePromise("bash", [scriptPath, "--diff", "HEAD"], {
				cwd: ctx.basePath,
				timeout: 30_000,
			});

			return {
				outcome: "pass",
				rationale: "No secrets detected in changed files.",
			};
		} catch (err) {
			const stdout = err.stdout ? String(err.stdout) : "";
			const stderr = err.stderr ? String(err.stderr) : "";
			const output = (stdout + stderr).trim();

			return {
				outcome: "fail",
				failureClass: "policy",
				rationale: "Security scan detected potential secrets or credentials.",
				findings: output || String(err),
			};
		}
	}
}

function execFilePromise(file, args, options) {
	return new Promise((resolve, reject) => {
		const child = execFile(file, args, options, (error, stdout, stderr) => {
			if (error) {
				reject(Object.assign(error, { stdout, stderr }));
			} else {
				resolve({ stdout, stderr });
			}
		});
		if (options?.timeout && options.timeout > 0) {
			const t = setTimeout(() => {
				child.kill();
				reject(
					Object.assign(new Error("security scan timed out"), {
						stdout: "",
						stderr: "",
					}),
				);
			}, options.timeout);
			child.on("exit", () => clearTimeout(t));
			child.on("error", () => clearTimeout(t));
		}
	});
}
