import assert from "node:assert/strict";
import {
	existsSync,
	mkdirSync,
	mkdtempSync,
	readFileSync,
	rmSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, test } from "vitest";
import {
	closeDatabase,
	getUokRuns,
	openDatabase,
	recordUokRunExit,
	recordUokRunStart,
} from "../sf-db.js";
import {
	recordUokKernelTermination,
	runAutoLoopWithUok,
} from "../uok/kernel.js";
import {
	buildParityReport,
	hasCurrentParityWarning,
	parseParityEvents,
	UNMATCHED_RUN_STALE_MS,
	writeParityReport,
} from "../uok/parity-report.js";

const NOW = Date.parse("2026-05-06T00:00:00.000Z");
const tmpRoots = [];

afterEach(() => {
	closeDatabase();
	for (const dir of tmpRoots.splice(0)) {
		rmSync(dir, { recursive: true, force: true });
	}
});

function makeProject() {
	const root = mkdtempSync(join(tmpdir(), "sf-uok-parity-"));
	tmpRoots.push(root);
	mkdirSync(join(root, ".sf", "runtime"), { recursive: true });
	return root;
}

function testDeps(preferences = {}) {
	return {
		loadEffectiveSFPreferences() {
			return {
				preferences: {
					uok: {
						enabled: true,
						audit_envelope: { enabled: false },
						audit_unified: { enabled: false },
						...preferences.uok,
					},
				},
			};
		},
	};
}

function testCtx(sessionId = "session-test") {
	return {
		sessionManager: {
			getSessionId() {
				return sessionId;
			},
		},
	};
}

function readProjectParityEvents(projectRoot) {
	const path = join(projectRoot, ".sf", "runtime", "uok-parity.jsonl");
	assert.equal(existsSync(path), true);
	return parseParityEvents(readFileSync(path, "utf-8"));
}

test("buildParityReport_legacy_anonymous_missing_exit_is_historical_not_current", () => {
	const report = buildParityReport(
		[
			{ ts: "2026-05-05T17:21:52.209Z", path: "uok-kernel", phase: "enter" },
			{
				ts: "2026-05-05T17:25:52.439Z",
				path: "uok-kernel",
				phase: "exit",
				status: "ok",
			},
			{ ts: "2026-05-05T17:25:18.803Z", path: "uok-kernel", phase: "enter" },
		],
		"/tmp/uok-parity.jsonl",
		NOW,
	);

	assert.equal(report.legacyMissingExitEvents, 1);
	assert.equal(report.missingExitEvents, 0);
	assert.deepEqual(report.criticalMismatches, []);
	assert.equal(hasCurrentParityWarning(report), false);
});

test("buildParityReport_fresh_run_missing_exit_is_current_warning", () => {
	const report = buildParityReport(
		[
			{
				ts: new Date(NOW - 5_000).toISOString(),
				runId: "uok-run-1",
				path: "uok-kernel",
				phase: "enter",
			},
		],
		"/tmp/uok-parity.jsonl",
		NOW,
	);

	assert.equal(report.missingExitEvents, 1);
	assert.equal(report.freshUnmatchedRuns[0].runId, "uok-run-1");
	assert.match(report.criticalMismatches[0], /uok-run-1/);
	assert.equal(hasCurrentParityWarning(report), true);
});

test("buildParityReport_stale_run_missing_exit_is_historical_not_current", () => {
	const report = buildParityReport(
		[
			{
				ts: new Date(NOW - UNMATCHED_RUN_STALE_MS - 1_000).toISOString(),
				runId: "uok-old-run",
				path: "uok-kernel",
				phase: "enter",
			},
		],
		"/tmp/uok-parity.jsonl",
		NOW,
	);

	assert.equal(report.missingExitEvents, 0);
	assert.equal(report.historicalUnmatchedRuns[0].runId, "uok-old-run");
	assert.deepEqual(report.criticalMismatches, []);
	assert.equal(hasCurrentParityWarning(report), false);
});

test("buildParityReport_run_exit_balances_enter", () => {
	const report = buildParityReport(
		[
			{
				ts: new Date(NOW - 10_000).toISOString(),
				runId: "uok-run-2",
				path: "uok-kernel",
				phase: "enter",
			},
			{
				ts: new Date(NOW - 5_000).toISOString(),
				runId: "uok-run-2",
				path: "uok-kernel",
				phase: "exit",
				status: "ok",
			},
		],
		"/tmp/uok-parity.jsonl",
		NOW,
	);

	assert.equal(report.missingExitEvents, 0);
	assert.deepEqual(report.unmatchedRuns, []);
	assert.equal(hasCurrentParityWarning(report), false);
});

test("uok_run_ledger_records_lifecycle_as_primary_state", () => {
	openDatabase(":memory:");
	recordUokRunStart({
		runId: "uok-ledger-1",
		sessionId: "session-ledger",
		path: "uok-kernel",
		flags: { enabled: true },
		startedAt: new Date(NOW - 10_000).toISOString(),
	});
	recordUokRunExit({
		runId: "uok-ledger-1",
		sessionId: "session-ledger",
		path: "uok-kernel",
		flags: { enabled: true },
		status: "ok",
		endedAt: new Date(NOW - 5_000).toISOString(),
	});

	const runs = getUokRuns();
	assert.equal(runs.length, 1);
	assert.equal(runs[0].runId, "uok-ledger-1");
	assert.equal(runs[0].status, "ok");
	assert.equal(runs[0].sessionId, "session-ledger");
	assert.equal(runs[0].flags.enabled, true);

	const report = buildParityReport(
		[],
		"/tmp/uok-parity.jsonl",
		NOW,
		30_000,
		runs,
	);
	assert.equal(report.ledgerRunCount, 1);
	assert.equal(report.missingExitEvents, 0);
	assert.equal(hasCurrentParityWarning(report), false);
});

test("buildParityReport_ledger_overrides_jsonl_lifecycle_counts", () => {
	const report = buildParityReport(
		[
			{
				ts: new Date(NOW - 5_000).toISOString(),
				runId: "uok-ledger-clean",
				path: "uok-kernel",
				phase: "enter",
				status: "unknown",
			},
			{
				ts: new Date(NOW - 5_000).toISOString(),
				path: "uok-kernel",
				phase: "enter",
				status: "unknown",
			},
		],
		"/tmp/uok-parity.jsonl",
		NOW,
		30_000,
		[
			{
				runId: "uok-ledger-clean",
				sessionId: "session-ledger",
				path: "uok-kernel",
				status: "ok",
				startedAt: new Date(NOW - 10_000).toISOString(),
				endedAt: new Date(NOW - 5_000).toISOString(),
				flags: { enabled: true },
			},
		],
	);

	assert.equal(report.ledgerRunCount, 1);
	assert.equal(report.legacyMissingExitEvents, 1);
	assert.equal(report.missingExitEvents, 0);
	assert.deepEqual(report.criticalMismatches, []);
	assert.equal(hasCurrentParityWarning(report), false);
});

test("buildParityReport_ledger_error_is_current_warning", () => {
	const report = buildParityReport([], "/tmp/uok-parity.jsonl", NOW, 30_000, [
		{
			runId: "uok-ledger-error",
			sessionId: "session-ledger",
			path: "uok-kernel",
			status: "error",
			error: "ledger failure",
			startedAt: new Date(NOW - 10_000).toISOString(),
			endedAt: new Date(NOW - 5_000).toISOString(),
			flags: { enabled: true },
		},
	]);

	assert.equal(report.currentErrorEvents, 1);
	assert.deepEqual(report.criticalMismatches, ["ledger failure"]);
	assert.equal(hasCurrentParityWarning(report), true);
});

test("buildParityReport_fresh_error_is_current_but_stale_error_is_historical", () => {
	const report = buildParityReport(
		[
			{
				ts: new Date(NOW - 5_000).toISOString(),
				runId: "uok-fresh-error",
				path: "uok-kernel",
				phase: "exit",
				status: "error",
				error: "fresh failure",
			},
			{
				ts: new Date(NOW - UNMATCHED_RUN_STALE_MS - 1_000).toISOString(),
				runId: "uok-old-error",
				path: "uok-kernel",
				phase: "exit",
				status: "error",
				error: "old failure",
			},
		],
		"/tmp/uok-parity.jsonl",
		NOW,
	);

	assert.equal(report.currentErrorEvents, 1);
	assert.equal(report.historicalErrorEvents, 1);
	assert.deepEqual(report.criticalMismatches, ["fresh failure"]);
	assert.deepEqual(report.historicalCriticalMismatches, ["old failure"]);
	assert.equal(hasCurrentParityWarning(report), true);
});

test("buildParityReport_stale_parity_diff_is_historical_not_current", () => {
	const report = buildParityReport(
		[
			{
				kind: "parity-diff",
				ts: new Date(NOW - UNMATCHED_RUN_STALE_MS - 1_000).toISOString(),
				plane: "gitops",
				turnId: "T01",
				match: false,
				divergence: "legacy=commit uok=status-only",
			},
		],
		"/tmp/uok-parity.jsonl",
		NOW,
	);

	assert.equal(report.divergencesByPlane.gitops, 1);
	assert.deepEqual(report.criticalMismatches, []);
	assert.deepEqual(report.historicalCriticalMismatches, [
		"[gitops] legacy=commit uok=status-only",
	]);
	assert.equal(hasCurrentParityWarning(report), false);
});

test("runAutoLoopWithUok_success_writes_balanced_run_id_heartbeats", async () => {
	const projectRoot = makeProject();
	const state = { basePath: projectRoot, autoStartTime: NOW };
	let observerProvided = false;

	await runAutoLoopWithUok({
		ctx: testCtx("session-success"),
		pi: {},
		s: state,
		deps: testDeps(),
		async runKernelLoop(_ctx, _pi, _s, deps) {
			observerProvided = Boolean(deps.uokObserver);
		},
		async runStandardLoop() {
			throw new Error("standard loop should not run when uok is enabled");
		},
	});

	assert.equal(observerProvided, true);
	assert.equal(state.currentUokRunId, undefined);
	const events = readProjectParityEvents(projectRoot);
	assert.equal(events.length, 2);
	assert.equal(events[0].phase, "enter");
	assert.equal(events[1].phase, "exit");
	assert.equal(events[1].status, "ok");
	assert.equal(events[0].runId, events[1].runId);
	assert.equal(events[0].runControl, "autonomous");
	assert.equal(events[0].permissionProfile, "normal");
	assert.equal(events[1].runControl, "autonomous");
	assert.equal(events[1].permissionProfile, "normal");
	const report = JSON.parse(
		readFileSync(
			join(projectRoot, ".sf", "runtime", "uok-parity-report.json"),
			"utf-8",
		),
	);
	assert.equal(report.missingExitEvents, 0);
	assert.equal(hasCurrentParityWarning(report), false);
});

test("runAutoLoopWithUok_assisted_trusted_profile_is_recorded_at_kernel_boundary", async () => {
	const projectRoot = makeProject();
	const state = { basePath: projectRoot, autoStartTime: NOW, stepMode: true };

	await runAutoLoopWithUok({
		ctx: testCtx("session-assisted"),
		pi: {},
		s: state,
		deps: testDeps({ uok: { permission_profile: "trusted" } }),
		async runKernelLoop() {},
		async runStandardLoop() {
			throw new Error("standard loop should not run when uok is enabled");
		},
	});

	const events = readProjectParityEvents(projectRoot);
	assert.equal(events[0].runControl, "assisted");
	assert.equal(events[0].permissionProfile, "trusted");
	assert.equal(events[0].flags.runControl, "assisted");
	assert.equal(events[0].flags.permissionProfile, "trusted");
	assert.equal(events[1].runControl, "assisted");
	assert.equal(events[1].permissionProfile, "trusted");
});

test("runAutoLoopWithUok_ignores_legacy_disabled_flag_and_uses_kernel_path", async () => {
	const projectRoot = makeProject();
	const state = { basePath: projectRoot, autoStartTime: NOW };
	let kernelRan = false;

	await runAutoLoopWithUok({
		ctx: testCtx("session-force-uok"),
		pi: {},
		s: state,
		deps: testDeps({ uok: { enabled: false } }),
		async runKernelLoop(_ctx, _pi, _s, deps) {
			kernelRan = Boolean(deps.uokObserver);
		},
		async runStandardLoop() {
			throw new Error("legacy standard loop should not run");
		},
	});

	assert.equal(kernelRan, true);
	const events = readProjectParityEvents(projectRoot);
	assert.equal(events[0].path, "uok-kernel");
	assert.equal(events[1].path, "uok-kernel");
});

test("runAutoLoopWithUok_throw_still_writes_exit_and_current_error_report", async () => {
	const projectRoot = makeProject();
	const state = { basePath: projectRoot, autoStartTime: NOW };

	await assert.rejects(
		() =>
			runAutoLoopWithUok({
				ctx: testCtx("session-error"),
				pi: {},
				s: state,
				deps: testDeps(),
				async runKernelLoop() {
					throw new Error("boom");
				},
				async runStandardLoop() {
					throw new Error("standard loop should not run when uok is enabled");
				},
			}),
		/boom/,
	);

	assert.equal(state.currentUokRunId, undefined);
	const events = readProjectParityEvents(projectRoot);
	assert.equal(events.length, 2);
	assert.equal(events[0].phase, "enter");
	assert.equal(events[1].phase, "exit");
	assert.equal(events[1].status, "error");
	assert.equal(events[1].error, "boom");
	assert.equal(events[0].runId, events[1].runId);
	const report = JSON.parse(
		readFileSync(
			join(projectRoot, ".sf", "runtime", "uok-parity-report.json"),
			"utf-8",
		),
	);
	assert.equal(report.missingExitEvents, 0);
	assert.equal(report.currentErrorEvents, 1);
	assert.deepEqual(report.criticalMismatches, ["boom"]);
	assert.equal(hasCurrentParityWarning(report), true);
});

test("recordUokKernelTermination_marks_started_ledger_run_exited_on_signal", () => {
	const projectRoot = makeProject();
	openDatabase(":memory:");
	recordUokRunStart({
		runId: "uok-signal-run",
		sessionId: "session-signal",
		path: "uok-kernel",
		flags: { enabled: true },
		startedAt: new Date(NOW - 5_000).toISOString(),
	});

	const report = recordUokKernelTermination({
		basePath: projectRoot,
		runId: "uok-signal-run",
		sessionId: "session-signal",
		flags: { enabled: true },
		status: "signal",
	});
	const runs = getUokRuns();

	assert.equal(runs.length, 1);
	assert.equal(runs[0].status, "signal");
	assert.equal(typeof runs[0].endedAt, "string");
	assert.equal(report.missingExitEvents, 0);
	assert.equal(hasCurrentParityWarning(report), false);
	const events = readProjectParityEvents(projectRoot);
	assert.equal(events.length, 1);
	assert.equal(events[0].phase, "exit");
	assert.equal(events[0].status, "signal");
});

test("writeParityReport_recovers_started_ledger_runs_when_no_auto_lock_owner_exists", () => {
	const projectRoot = makeProject();
	openDatabase(":memory:");
	recordUokRunStart({
		runId: "uok-orphan-run",
		sessionId: "session-orphan",
		path: "uok-kernel",
		flags: { enabled: true },
		startedAt: new Date(NOW - 5_000).toISOString(),
	});

	const report = writeParityReport(projectRoot, NOW);
	const runs = getUokRuns();

	assert.equal(runs.length, 1);
	assert.equal(runs[0].status, "recovered");
	assert.equal(
		runs[0].error,
		"uok recovered missing exit: no live auto.lock owner",
	);
	assert.equal(report.missingExitEvents, 0);
	assert.equal(report.freshUnmatchedRuns.length, 0);
	assert.equal(hasCurrentParityWarning(report), false);
});
