import assert from "node:assert/strict";
import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, test } from "vitest";
import {
	collectUokStatus,
	formatUokStatus,
	handleUok,
} from "../commands-uok.js";
import {
	closeDatabase,
	openDatabase,
	recordUokRunExit,
	recordUokRunStart,
} from "../sf-db.js";
import { appendTraceEvent } from "../uok/trace-writer.js";

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

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

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

test("collectUokStatus_reads_ledger_and_reports_clear_startup_gate", async () => {
	const projectRoot = makeProject();
	openDatabase(join(projectRoot, ".sf", "sf.db"));
	recordUokRunStart({
		runId: "uok-status-ok",
		sessionId: "session-uok",
		path: "uok-kernel",
		flags: { enabled: true },
		startedAt: new Date(NOW - 10_000).toISOString(),
	});
	recordUokRunExit({
		runId: "uok-status-ok",
		sessionId: "session-uok",
		path: "uok-kernel",
		flags: { enabled: true },
		status: "ok",
		endedAt: new Date(NOW - 5_000).toISOString(),
	});

	const status = await collectUokStatus(projectRoot, NOW);

	assert.equal(status.dbAvailable, true);
	assert.equal(status.startupBlocked, false);
	assert.equal(status.healthStatus, "ok");
	assert.equal(status.ledgerRunCount, 1);
	assert.equal(status.lastRun.runId, "uok-status-ok");
	assert.equal(status.diagnostics.verdict, "clear");
	assert.equal(status.current.criticalMismatches, 0);
	assert.equal(status.current.missingExitEvents, 0);
	assert.equal(status.current.errorEvents, 0);
});

test("collectUokStatus_reports_last_error_and_blocked_startup_gate", async () => {
	const projectRoot = makeProject();
	openDatabase(join(projectRoot, ".sf", "sf.db"));
	recordUokRunStart({
		runId: "uok-status-error",
		path: "uok-kernel",
		startedAt: new Date(NOW - 10_000).toISOString(),
	});
	recordUokRunExit({
		runId: "uok-status-error",
		path: "uok-kernel",
		status: "error",
		error: "ledger boom",
		endedAt: new Date(NOW - 5_000).toISOString(),
	});

	const status = await collectUokStatus(projectRoot, NOW);

	assert.equal(status.startupBlocked, true);
	assert.equal(status.healthStatus, "degraded");
	assert.equal(status.lastErrorRun.runId, "uok-status-error");
	assert.equal(status.current.errorEvents, 1);
	assert.deepEqual(status.current.criticalMismatches, 1);
});

test("collectUokStatus_when_process_switches_projects_reads_requested_project_db", async () => {
	const first = makeProject();
	const second = makeProject();
	openDatabase(join(first, ".sf", "sf.db"));
	recordUokRunStart({
		runId: "first-project-run",
		path: "uok-kernel",
		startedAt: new Date(NOW - 20_000).toISOString(),
	});
	recordUokRunExit({
		runId: "first-project-run",
		path: "uok-kernel",
		status: "ok",
		endedAt: new Date(NOW - 10_000).toISOString(),
	});

	const status = await collectUokStatus(second, NOW);

	assert.equal(status.dbAvailable, true);
	assert.equal(status.lastRun, null);
	assert.equal(status.ledgerRunCount, 0);
});

test("formatUokStatus_shows_operator_fields_without_raw_json", () => {
	const rendered = formatUokStatus(
		{
			dbAvailable: true,
			healthStatus: "degraded",
			startupBlocked: true,
			ledgerRunCount: 2,
			current: {
				criticalMismatches: 1,
				missingExitEvents: 0,
				errorEvents: 1,
			},
			historical: {
				criticalMismatches: 0,
				legacyMissingExitEvents: 3,
				errorEvents: 0,
				unmatchedRuns: 0,
			},
			lastRun: {
				runId: "uok-last",
				status: "error",
				path: "uok-kernel",
				startedAt: new Date(NOW - 10_000).toISOString(),
				endedAt: new Date(NOW - 5_000).toISOString(),
			},
			lastErrorRun: {
				runId: "uok-last",
				error: "ledger boom",
				endedAt: new Date(NOW - 5_000).toISOString(),
			},
			diagnostics: {
				verdict: "degraded",
				classification: "needs-repair",
				issues: [{ code: "open-ledger-without-live-lock" }],
			},
			reportPath: "/repo/.sf/runtime/uok-parity-report.json",
		},
		NOW,
	);

	assert.match(rendered, /UOK status/);
	assert.match(rendered, /Startup gate: blocked/);
	assert.match(rendered, /Ledger runs: 2/);
	assert.match(rendered, /Diagnostics: degraded \(needs-repair\)/);
	assert.match(rendered, /Diagnostic issue: open-ledger-without-live-lock/);
	assert.match(rendered, /Last run:/);
	assert.match(rendered, /Last error:/);
	assert.match(rendered, /ledger boom/);
	assert.match(rendered, /legacy missing exits: 3/);
});

test("handleUok_gates_when_empty_shows_no_recorded_gate_runs", async () => {
	const projectRoot = makeProject();
	process.chdir(projectRoot);
	const notifications = [];
	const ctx = {
		ui: {
			notify: (msg, level) => notifications.push({ msg, level }),
		},
	};
	await handleUok("gates", ctx);
	assert.ok(notifications.length >= 1);
	assert.match(notifications[0].msg, /No UOK gate runs recorded yet/);
});

test("handleUok_gates_lists_observed_gate_runs", async () => {
	const projectRoot = makeProject();
	process.chdir(projectRoot);
	openDatabase(join(projectRoot, ".sf", "sf.db"));
	appendTraceEvent(projectRoot, "trace-gates", {
		type: "gate_run",
		traceId: "trace-gates",
		turnId: "turn-gates",
		gateId: "observed-gate",
		gateType: "verification",
		unitType: "execute-task",
		unitId: "M001/S01/T01",
		milestoneId: "M001",
		sliceId: "S01",
		taskId: "T01",
		outcome: "pass",
		failureClass: "none",
		rationale: "ok",
		findings: "",
		attempt: 1,
		maxAttempts: 1,
		retryable: false,
		evaluatedAt: new Date(NOW).toISOString(),
		durationMs: 10,
	});
	const notifications = [];
	const ctx = {
		ui: {
			notify: (msg, level) => notifications.push({ msg, level }),
		},
	};

	await handleUok("gates", ctx);

	assert.ok(notifications.length >= 1);
	assert.match(notifications[0].msg, /Observed gates/);
	assert.match(notifications[0].msg, /observed-gate/);
});

test("handleUok_messages_shows_bus_metrics", async () => {
	const projectRoot = makeProject();
	process.chdir(projectRoot);
	openDatabase(join(projectRoot, ".sf", "sf.db"));
	const notifications = [];
	const ctx = {
		ui: {
			notify: (msg, level) => notifications.push({ msg, level }),
		},
	};
	await handleUok("messages", ctx);
	assert.ok(notifications.length >= 1);
	assert.match(notifications[0].msg, /Message bus/);
	assert.match(notifications[0].msg, /Total messages: 0/);
});
