import assert from "node:assert/strict";
import {
	existsSync,
	mkdirSync,
	mkdtempSync,
	readFileSync,
	rmSync,
	writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, test } from "vitest";
import {
	beginAutonomousSolverIteration,
	readAutonomousSolverState,
} from "../autonomous-solver.js";
import {
	closeDatabase,
	openDatabase,
	recordUokRunExit,
	recordUokRunStart,
} from "../sf-db.js";
import {
	readUokDiagnostics,
	synthesizeUokDiagnostics,
	writeUokDiagnostics,
} from "../uok/diagnostic-synthesis.js";
import { MessageBus } from "../uok/message-bus.js";
import {
	listUnitRuntimeRecords,
	writeUnitRuntimeRecord,
} from "../uok/unit-runtime.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-diagnostics-"));
	tmpRoots.push(root);
	mkdirSync(join(root, ".sf", "runtime", "units"), { recursive: true });
	return root;
}

function writeAutoLock(root, lock) {
	writeFileSync(
		join(root, ".sf", "auto.lock"),
		`${JSON.stringify(lock, null, 2)}\n`,
		"utf-8",
	);
}

function issueCodes(diagnostics) {
	return diagnostics.issues.map((issue) => issue.code);
}

test("synthesizeUokDiagnostics_when_lock_pid_dead_reports_stale_lock", () => {
	const root = makeProject();
	writeAutoLock(root, {
		pid: 999_999_999,
		startedAt: new Date(NOW - 60_000).toISOString(),
		unitType: "execute-task",
		unitId: "M010/S08/T08.1",
		unitStartedAt: new Date(NOW - 60_000).toISOString(),
		sessionFile: "/tmp/session.jsonl",
	});

	const diagnostics = synthesizeUokDiagnostics(root, {
		nowMs: NOW,
		processRows: [],
	});

	assert.equal(diagnostics.verdict, "degraded");
	assert.equal(diagnostics.signals.lock, "stale");
	assert.ok(issueCodes(diagnostics).includes("stale-lock"));
	assert.equal(diagnostics.currentUnit.unitId, "M010/S08/T08.1");
});

test("synthesizeUokDiagnostics_when_live_lock_exists_reports_running_and_children", () => {
	const root = makeProject();
	writeAutoLock(root, {
		pid: process.pid,
		startedAt: new Date(NOW - 10_000).toISOString(),
		unitType: "execute-task",
		unitId: "M010/S08/T08.2",
		unitStartedAt: new Date(NOW - 10_000).toISOString(),
		sessionFile: "/tmp/session.jsonl",
	});

	const diagnostics = synthesizeUokDiagnostics(root, {
		nowMs: NOW,
		processRows: [
			{
				pid: process.pid,
				ppid: 1,
				stat: "S",
				command: "node dist/loader.js autonomous",
			},
			{ pid: 12345, ppid: process.pid, stat: "S", command: "bash" },
		],
	});

	assert.equal(diagnostics.verdict, "clear");
	assert.equal(diagnostics.classification, "running");
	assert.equal(diagnostics.signals.lock, "active");
	assert.deepEqual(diagnostics.currentUnit.childPids, [12345]);
});

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

	const diagnostics = synthesizeUokDiagnostics(root, {
		nowMs: NOW,
		processRows: [],
	});

	assert.equal(diagnostics.verdict, "clear");
	assert.equal(diagnostics.signals.ledger, "consistent");
	assert.ok(!issueCodes(diagnostics).includes("open-ledger-without-live-lock"));
});

test("synthesizeUokDiagnostics_when_parity_missing_exit_reports_current_warning", () => {
	const root = makeProject();
	writeFileSync(
		join(root, ".sf", "runtime", "uok-parity.jsonl"),
		`${JSON.stringify({
			ts: new Date(NOW - 5_000).toISOString(),
			runId: "uok-missing-exit",
			path: "uok-kernel",
			phase: "enter",
		})}\n`,
		"utf-8",
	);

	const diagnostics = synthesizeUokDiagnostics(root, {
		nowMs: NOW,
		processRows: [],
	});

	assert.equal(diagnostics.verdict, "attention");
	assert.equal(diagnostics.signals.parity, "degraded");
	assert.ok(issueCodes(diagnostics).includes("uok-parity-degraded"));
});

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

	const diagnostics = synthesizeUokDiagnostics(root, {
		nowMs: NOW,
		processRows: [
			{
				pid: 4242,
				ppid: 1,
				stat: "S",
				command: "sf autonomous",
			},
		],
	});

	assert.equal(diagnostics.verdict, "attention");
	assert.equal(diagnostics.signals.wrapper, "maybe-live-after-kernel-exit");
	assert.ok(
		issueCodes(diagnostics).includes("kernel-exited-wrapper-maybe-live"),
	);
});

test("synthesizeUokDiagnostics_when_db_next_differs_from_projection_reports_mismatch", () => {
	const root = makeProject();
	writeUnitRuntimeRecord(root, "execute-task", "M010/S08/T08.1", NOW - 10_000, {
		status: "running",
		lastHeartbeatAt: NOW - 5_000,
		lastProgressAt: NOW - 5_000,
	});

	const diagnostics = synthesizeUokDiagnostics(root, {
		nowMs: NOW,
		processRows: [],
		expectedNext: {
			action: "dispatch",
			unitType: "execute-task",
			unitId: "M010/S08/T08.2",
		},
	});

	assert.equal(diagnostics.signals.runtimeProjection, "mismatch");
	assert.ok(issueCodes(diagnostics).includes("db-projection-unit-mismatch"));
});

test("writeUokDiagnostics_when_repair_enabled_clears_mismatched_stale_projection", () => {
	const root = makeProject();
	writeUnitRuntimeRecord(
		root,
		"rewrite-docs",
		"M001/validation-attention",
		NOW - 10_000,
		{
			status: "running",
			lastHeartbeatAt: NOW - 5_000,
			lastProgressAt: NOW - 5_000,
		},
	);

	const diagnostics = writeUokDiagnostics(root, {
		nowMs: NOW,
		processRows: [],
		expectedNext: {
			action: "dispatch",
			unitType: "validate-milestone",
			unitId: "M001",
		},
		repairStaleRuntimeProjection: true,
	});

	assert.equal(diagnostics.signals.runtimeProjection, "ok");
	assert.ok(!issueCodes(diagnostics).includes("db-projection-unit-mismatch"));
	assert.deepEqual(listUnitRuntimeRecords(root), []);
});

test("writeUokDiagnostics_when_live_lock_projection_mismatches_db_next_repairs_projection", () => {
	const root = makeProject();
	writeAutoLock(root, {
		pid: process.pid,
		startedAt: new Date(NOW - 60_000).toISOString(),
		unitType: "research-slice",
		unitId: "M004/parallel-research",
		unitStartedAt: new Date(NOW - 60_000).toISOString(),
	});
	writeUnitRuntimeRecord(
		root,
		"research-slice",
		"M004/parallel-research",
		NOW - 60_000,
		{
			status: "running",
			lastHeartbeatAt: NOW - 55_000,
			lastProgressAt: NOW - 55_000,
		},
	);

	const diagnostics = writeUokDiagnostics(root, {
		nowMs: NOW,
		processRows: [
			{
				pid: process.pid,
				ppid: 1,
				stat: "S",
				command: "node dist/loader.js autonomous",
			},
		],
		expectedNext: {
			action: "dispatch",
			unitType: "plan-slice",
			unitId: "M004/S02",
		},
		repairStaleRuntimeProjection: true,
	});

	assert.equal(diagnostics.signals.runtimeProjection, "ok");
	assert.ok(!issueCodes(diagnostics).includes("db-projection-unit-mismatch"));
	assert.deepEqual(listUnitRuntimeRecords(root), []);
});

test("writeUokDiagnostics_when_repair_enabled_clears_stale_projection_without_expected_unit", () => {
	const root = makeProject();
	writeUnitRuntimeRecord(root, "research-slice", "M003/S01", NOW - 10_000, {
		status: "running",
		lastHeartbeatAt: NOW - 5_000,
		lastProgressAt: NOW - 5_000,
	});

	const diagnostics = writeUokDiagnostics(root, {
		nowMs: NOW,
		processRows: [],
		expectedNext: { action: "skip" },
		repairStaleRuntimeProjection: true,
	});

	assert.equal(diagnostics.signals.runtimeProjection, "ok");
	assert.ok(!issueCodes(diagnostics).includes("stale-runtime-projection"));
	assert.deepEqual(listUnitRuntimeRecords(root), []);
});

test("writeUokDiagnostics_when_repair_enabled_clears_stale_autonomous_solver_state", () => {
	const root = makeProject();
	beginAutonomousSolverIteration(root, "execute-task", "M003/S02/T02");

	const diagnostics = writeUokDiagnostics(root, {
		nowMs: NOW,
		processRows: [],
		expectedNext: {
			action: "dispatch",
			unitType: "execute-task",
			unitId: "M003/S02/T02",
		},
		repairStaleRuntimeProjection: true,
	});

	assert.equal(diagnostics.signals.runtimeProjection, "ok");
	assert.equal(readAutonomousSolverState(root), null);
});

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

	const diagnostics = writeUokDiagnostics(root, {
		nowMs: NOW,
		processRows: [],
	});
	const reportPath = join(root, ".sf", "runtime", "uok-diagnostics.json");

	assert.equal(existsSync(reportPath), true);
	assert.deepEqual(
		readUokDiagnostics(root),
		JSON.parse(readFileSync(reportPath, "utf-8")),
	);
	assert.equal(diagnostics.reportPath, reportPath);
});

test("writeUokDiagnostics_when_attention_publishes_deduped_observer_message", () => {
	const root = makeProject();
	openDatabase(":memory:");
	writeAutoLock(root, {
		pid: 999_999_999,
		startedAt: new Date(NOW - 60_000).toISOString(),
		unitType: "execute-task",
		unitId: "M010/S08/T08.1",
		unitStartedAt: new Date(NOW - 60_000).toISOString(),
		sessionFile: "/tmp/session.jsonl",
	});

	writeUokDiagnostics(root, { nowMs: NOW, processRows: [] });
	writeUokDiagnostics(root, { nowMs: NOW, processRows: [] });

	const inbox = new MessageBus(root).getInbox("uok-observer");
	const messages = inbox.list();
	assert.equal(messages.length, 1);
	assert.equal(messages[0].from, "uok-diagnostics");
	assert.match(messages[0].body, /stale-lock/);
	assert.deepEqual(messages[0].metadata.issueCodes, ["stale-lock"]);
});
