import { randomUUID } from "node:crypto";
import {
	appendFileSync,
	closeSync,
	existsSync,
	mkdirSync,
	openSync,
} from "node:fs";
import { join } from "node:path";
import { isStaleWrite } from "../auto/turn-epoch.js";
import { withFileLockSync } from "../file-lock.js";
import { sfRuntimeRoot } from "../paths.js";

const UOK_AUDIT_SCHEMA_VERSION = 1;

function auditLogPath(basePath) {
	return join(sfRuntimeRoot(basePath), "audit", "events.jsonl");
}
function ensureAuditDir(basePath) {
	mkdirSync(join(sfRuntimeRoot(basePath), "audit"), { recursive: true });
}
export function buildAuditEnvelope(args) {
	return {
		schemaVersion: UOK_AUDIT_SCHEMA_VERSION,
		eventId: randomUUID(),
		traceId: args.traceId,
		turnId: args.turnId,
		causedBy: args.causedBy,
		category: args.category,
		type: args.type,
		ts: new Date().toISOString(),
		payload: args.payload ?? {},
	};
}
export function emitUokAuditEvent(basePath, event) {
	// Drop writes from a turn superseded by timeout recovery / cancellation.
	if (isStaleWrite("uok-audit")) return;
	try {
		ensureAuditDir(basePath);
		const path = auditLogPath(basePath);
		// proper-lockfile requires the target file to exist before locking.
		// Touch it via open(O_APPEND|O_CREAT) so the first writer wins the race
		// atomically at the kernel level.
		if (!existsSync(path)) closeSync(openSync(path, "a"));
		// onLocked: "skip" — audit writes are best-effort; under heavy contention
		// POSIX O_APPEND atomicity still protects small line writes, so skipping
		// the lock rather than stalling orchestration is the correct tradeoff.
		withFileLockSync(
			path,
			() => {
				appendFileSync(path, `${JSON.stringify(event)}\n`, "utf-8");
			},
			{ onLocked: "skip" },
		);
	} catch {
		// Best-effort: audit writes must never break orchestration.
	}
}
