/**
 * uok-kernel.ts — Unit-of-Work kernel lifecycle management.
 *
 * Purpose: wraps the autonomous loop with UOK entry/exit instrumentation —
 * DB run ledger, parity heartbeat, audit envelope, and diagnostic snapshot —
 * so every run is durable, observable, and crash-recoverable.
 *
 * Consumer: auto/dispatch.ts and signal handlers (SIGINT/SIGTERM) in the
 * autonomous mode boot path.
 */

import { randomUUID } from "node:crypto";
import { debugLog } from "../debug-logger.js";
import {
	defaultPermissionProfileForRunControl,
	resolvePermissionProfile,
	resolveRunControlMode,
	runControlModeForSession,
} from "../operating-model.js";
import {
	isDbAvailable,
	recordUokRunExit,
	recordUokRunStart,
} from "../sf-db.js";
import { buildAuditEnvelope, emitUokAuditEvent } from "./audit.js";
import { setAuditEnvelopeEnabled } from "./audit-toggle.js";
import { writeUokDiagnostics } from "./diagnostic-synthesis.js";
import { resolveUokFlags } from "./flags.js";
import { createTurnObserver } from "./loop-adapter.js";
import {
	checkAndDrainMissingExit,
	resetParityCommitBlock,
	signalKernelEnter,
} from "./parity-diff-capture.js";
import {
	hasCurrentParityWarning,
	writeParityHeartbeat,
	writeParityReport,
} from "./parity-report.js";

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

/** Flags resolved from user preferences for this UOK run. */
interface UokFlags {
	auditEnvelope?: boolean;
	gitops?: boolean;
	gitopsTurnAction?: string;
	gitopsTurnPush?: boolean;
	chaosMonkey?: boolean;
	enabled?: boolean;
	[key: string]: unknown;
}

/** Lifecycle flags written to the DB run ledger and parity heartbeat. */
interface LifecycleFlags extends UokFlags {
	runControl?: string;
	permissionProfile?: string;
	workMode?: string;
	modelMode?: string;
}

export interface UokKernelTerminationArgs {
	basePath: string;
	runId?: string;
	sessionId?: string | null;
	flags?: UokFlags;
	runControl?: string;
	permissionProfile?: string;
	/** Exit status — "ok" | "error" | "signal". Defaults to "signal". */
	status?: string;
	error?: string;
}

/** Arguments for {@link runAutoLoopWithUok}. */
export interface RunAutoLoopWithUokArgs {
	/** Coding-agent context (opaque; provides sessionManager). */
	ctx: {
		sessionManager?: {
			getSessionId?: () => string | undefined;
		};
		[key: string]: unknown;
	};
	/** Pi/provider interface (opaque). */
	pi: unknown;
	/** Mutable session state. */
	s: {
		basePath: string;
		workMode?: string;
		modelMode?: string;
		autoStartTime?: number;
		currentUokRunId?: string;
		[key: string]: unknown;
	};
	/** Injected dependency bag. */
	deps: {
		loadEffectiveSFPreferences?: () => {
			preferences?: Record<string, unknown>;
		} | null;
		[key: string]: unknown;
	};
	runControl?: string;
	permissionProfile?: string;
	/** The inner autonomous loop to execute inside the UOK wrapper. */
	runKernelLoop: (
		ctx: RunAutoLoopWithUokArgs["ctx"],
		pi: unknown,
		s: RunAutoLoopWithUokArgs["s"],
		deps: Record<string, unknown>,
	) => Promise<void>;
}

// ---------------------------------------------------------------------------
// Internals
// ---------------------------------------------------------------------------

function refreshParityReport(
	basePath: string,
): ReturnType<typeof writeParityReport> | null {
	try {
		return writeParityReport(basePath);
	} catch (err) {
		debugLog("uok-parity-report-write-failed", {
			error: err instanceof Error ? err.message : String(err),
		});
		return null;
	}
}

function resolveKernelPathLabel(): string {
	return "uok-kernel";
}

// ---------------------------------------------------------------------------
// Exports
// ---------------------------------------------------------------------------

/**
 * Records an abnormal UOK kernel termination in both durable stores.
 *
 * Purpose: keep the DB-backed UOK run ledger and JSONL parity heartbeat
 * symmetrical when autonomous mode exits via signal and bypasses the async kernel
 * finally block.
 *
 * Consumer: auto signal cleanup and UOK parity tests.
 */
export function recordUokKernelTermination({
	basePath,
	runId,
	sessionId,
	flags,
	runControl,
	permissionProfile,
	status = "signal",
	error,
}: UokKernelTerminationArgs): ReturnType<typeof writeParityReport> | null {
	const endedAt = new Date().toISOString();
	const lifecycleFlags: LifecycleFlags = {
		...(flags ?? {}),
		...(runControl ? { runControl } : {}),
		...(permissionProfile ? { permissionProfile } : {}),
	};
	if (runId && isDbAvailable()) {
		recordUokRunExit({
			runId,
			sessionId,
			path: resolveKernelPathLabel(),
			flags: lifecycleFlags,
			status,
			endedAt,
			...(error ? { error } : {}),
		});
	}
	writeParityHeartbeat(basePath, {
		ts: endedAt,
		...(runId ? { runId } : {}),
		sessionId,
		path: resolveKernelPathLabel(),
		flags: lifecycleFlags,
		...(runControl ? { runControl } : {}),
		...(permissionProfile ? { permissionProfile } : {}),
		phase: "exit",
		status,
		...(error ? { error } : {}),
	});
	const report = refreshParityReport(basePath);
	try {
		writeUokDiagnostics(basePath);
	} catch (err) {
		debugLog("uok-diagnostics-write-failed", {
			error: err instanceof Error ? err.message : String(err),
		});
	}
	return report;
}

/**
 * Runs the autonomous loop inside the UOK lifecycle wrapper.
 *
 * Purpose: instrument every autonomous run with entry/exit DB records, parity
 * heartbeats, audit events, and a crash-recovery diagnostic snapshot so that
 * incomplete runs can be detected and drained on the next startup.
 *
 * Consumer: auto/dispatch.ts — called once per autonomous session activation.
 */
export async function runAutoLoopWithUok(
	args: RunAutoLoopWithUokArgs,
): Promise<void> {
	const { ctx, pi, s, deps, runKernelLoop } = args;
	const prefs = deps.loadEffectiveSFPreferences?.()?.preferences as
		| Record<string, unknown>
		| undefined;
	const flags: UokFlags = { ...resolveUokFlags(prefs), enabled: true };
	const runControl: string = resolveRunControlMode(
		args.runControl ?? runControlModeForSession(s),
	);
	const permissionProfile: string = resolvePermissionProfile(
		args.permissionProfile ??
			(prefs?.uok as Record<string, unknown> | undefined)?.permission_profile ??
			defaultPermissionProfileForRunControl(runControl),
	);
	// Include workMode and modelMode from session in lifecycle flags
	const workMode: string = (s.workMode as string | undefined) ?? "chat";
	const modelMode: string = (s.modelMode as string | undefined) ?? "smart";
	const lifecycleFlags: LifecycleFlags = {
		...flags,
		runControl,
		permissionProfile,
		workMode,
		modelMode,
	};

	const healthVerdict = writeUokDiagnostics(s.basePath);
	debugLog("uok-system-health-verdict", healthVerdict);

	const previousReport = refreshParityReport(s.basePath);
	const runId = `uok-${randomUUID()}`;
	s.currentUokRunId = runId;
	resetParityCommitBlock();
	if (
		previousReport &&
		(previousReport as { missingExitEvents?: number }).missingExitEvents !=
			null &&
		(previousReport as { missingExitEvents: number }).missingExitEvents > 0 &&
		hasCurrentParityWarning(previousReport)
	) {
		checkAndDrainMissingExit(
			(previousReport as { enterEvents: number }).enterEvents,
			(previousReport as { exitEvents: number }).exitEvents,
		);
	}
	setAuditEnvelopeEnabled(flags.auditEnvelope ?? false);
	signalKernelEnter();
	const startedAt = new Date().toISOString();
	const sessionId = ctx.sessionManager?.getSessionId?.();
	if (isDbAvailable()) {
		recordUokRunStart({
			runId,
			sessionId,
			path: resolveKernelPathLabel(),
			flags: lifecycleFlags,
			startedAt,
		});
	}
	writeParityHeartbeat(s.basePath, {
		ts: startedAt,
		runId,
		sessionId,
		path: resolveKernelPathLabel(),
		flags: lifecycleFlags,
		runControl,
		permissionProfile,
		phase: "enter",
	});
	if (flags.auditEnvelope) {
		emitUokAuditEvent(
			s.basePath,
			buildAuditEnvelope({
				traceId: `session:${String(s.autoStartTime || Date.now())}`,
				category: "orchestration",
				type: "uok-kernel-enter",
				payload: {
					flags: lifecycleFlags,
					runControl,
					permissionProfile,
					workMode,
					modelMode,
					sessionId,
				},
			}),
		);
	}
	const decoratedDeps = {
		...deps,
		uokObserver: createTurnObserver({
			basePath: s.basePath,
			gitAction: flags.gitopsTurnAction,
			gitPush: flags.gitopsTurnPush,
			enableAudit: flags.auditEnvelope,
			enableGitops: flags.gitops,
			enableChaosMonkey: flags.chaosMonkey,
			runControl,
			permissionProfile,
		}),
		uokRunControl: runControl,
		uokPermissionProfile: permissionProfile,
	};
	// ── Guaranteed-exit writer ────────────────────────────────────────────────
	// Ensures a parity heartbeat exit event is always written for every enter,
	// even when the process exits via SIGTERM/SIGKILL, OOM, or uncaught exception
	// before the async finally block can run.
	//
	// Design:
	//  - A once-flag prevents double-writes: the first caller (finally block,
	//    signal handler, or process 'exit' event) wins; subsequent calls are no-ops.
	//  - 'process.once' handlers are one-shot and do NOT prevent process exit.
	//  - The 'exit' event runs synchronously; writeParityHeartbeat uses appendFileSync
	//    so it is safe to call there.
	//  - All handlers are removed in the finally block to avoid accumulation across
	//    multiple runAutoLoopWithUok calls in the same process.
	let __uokExitWritten = false;
	const __writeUokExitOnce = (reason: string, exitStatus?: string): void => {
		if (__uokExitWritten) return;
		__uokExitWritten = true;
		try {
			writeParityHeartbeat(s.basePath, {
				ts: new Date().toISOString(),
				runId,
				sessionId,
				path: resolveKernelPathLabel(),
				flags: lifecycleFlags,
				runControl,
				permissionProfile,
				phase: "exit",
				status: exitStatus ?? status,
				reason,
			});
		} catch {
			// Best-effort: logging failure during shutdown must not propagate.
		}
	};
	// Build per-signal bound handlers so we can removeListener precisely without
	// disturbing other listeners registered on the same signal (e.g. auto-supervisor).
	const __uokSignals = ["SIGTERM", "SIGINT", "SIGHUP"] as const;
	type CleanupSignal = (typeof __uokSignals)[number];
	const __uokBoundSignalHandlers = new Map<CleanupSignal, () => void>();
	for (const sig of __uokSignals) {
		const bound = (): void => __writeUokExitOnce(`signal:${sig}`, "signal");
		__uokBoundSignalHandlers.set(sig, bound);
		process.once(sig, bound);
	}
	const __uokUncaughtHandler = (err: unknown): void =>
		__writeUokExitOnce(
			`uncaught:${err instanceof Error ? err.message : String(err)}`,
			"error",
		);
	const __uokRejectionHandler = (reason: unknown): void =>
		__writeUokExitOnce(
			`unhandledRejection:${typeof reason === "string" ? reason : reason instanceof Error ? reason.message : "unknown"}`,
			"error",
		);
	const __uokExitHandler = (): void => __writeUokExitOnce("process-exit");
	process.once("uncaughtException", __uokUncaughtHandler);
	process.once("unhandledRejection", __uokRejectionHandler);
	process.once("exit", __uokExitHandler);

	let status = "ok";
	let error: string | undefined;
	try {
		await runKernelLoop(ctx, pi, s, decoratedDeps);
	} catch (err) {
		status = "error";
		error = err instanceof Error ? err.message : String(err);
		throw err;
	} finally {
		// Remove our handlers precisely — do not disturb other listeners on the
		// same signals (e.g. auto-supervisor's SIGTERM lock-cleanup handler).
		for (const sig of __uokSignals) {
			const bound = __uokBoundSignalHandlers.get(sig);
			if (bound) process.removeListener(sig, bound);
		}
		process.removeListener("uncaughtException", __uokUncaughtHandler);
		process.removeListener("unhandledRejection", __uokRejectionHandler);
		process.removeListener("exit", __uokExitHandler);
		// Mark exit as written BEFORE recordUokKernelTermination so that if an
		// error inside recordUokKernelTermination causes a re-entry, the once-flag
		// prevents a second write.
		__uokExitWritten = true;
		recordUokKernelTermination({
			basePath: s.basePath,
			runId,
			sessionId,
			flags: lifecycleFlags,
			runControl,
			permissionProfile,
			status,
			...(error ? { error } : {}),
		});
		if (s.currentUokRunId === runId) s.currentUokRunId = undefined;
	}
}
