/**
 * db-driven-recovery-dispatch.test.mjs — DB authority in recovery/dispatch.
 *
 * Purpose: prove DB-backed recovery and manual dispatch do not promote stale
 * roadmap/plan projections into executable runtime state.
 */
import assert from "node:assert/strict";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, test } from "vitest";
import { dispatchDirectPhase } from "../auto-direct-dispatch.js";
import { verifyExpectedArtifact } from "../auto-recovery.js";
import {
	closeDatabase,
	insertMilestone,
	insertSlice,
	openDatabase,
} from "../sf-db.js";
import { invalidateStateCache } from "../state.js";

const tmpDirs = [];

afterEach(() => {
	closeDatabase();
	invalidateStateCache();
	while (tmpDirs.length > 0) {
		const dir = tmpDirs.pop();
		if (dir) rmSync(dir, { recursive: true, force: true });
	}
});

function makeProject() {
	const dir = mkdtempSync(join(tmpdir(), "sf-db-recovery-dispatch-"));
	tmpDirs.push(dir);
	mkdirSync(join(dir, ".sf", "milestones", "M990", "slices", "S01"), {
		recursive: true,
	});
	openDatabase(join(dir, ".sf", "sf.db"));
	insertMilestone({
		id: "M990",
		title: "DB recovery authority",
		status: "active",
	});
	return dir;
}

test("verifyExpectedArtifact_when_db_has_no_tasks_refuses_plan_file_task_ids", () => {
	const project = makeProject();
	insertSlice({
		milestoneId: "M990",
		id: "S01",
		title: "Planned on disk only",
		status: "pending",
		sequence: 1,
	});
	const sliceDir = join(project, ".sf", "milestones", "M990", "slices", "S01");
	writeFileSync(
		join(sliceDir, "S01-PLAN.md"),
		[
			"# S01: stale generated plan",
			"",
			"## Tasks",
			"",
			"- [ ] **T01:** stale task that is not in DB",
			"",
		].join("\n"),
	);
	mkdirSync(join(sliceDir, "tasks"), { recursive: true });
	writeFileSync(join(sliceDir, "tasks", "T01-PLAN.md"), "# T01\n");

	assert.equal(
		verifyExpectedArtifact("plan-slice", "M990/S01", project),
		false,
	);
});

test("verifyExpectedArtifact_when_db_slice_missing_refuses_complete_slice_files", () => {
	const project = makeProject();
	const sliceDir = join(project, ".sf", "milestones", "M990", "slices", "S01");
	writeFileSync(join(sliceDir, "S01-SUMMARY.md"), "# S01 summary\n");
	writeFileSync(join(sliceDir, "S01-UAT.md"), "# S01 UAT\n");

	assert.equal(
		verifyExpectedArtifact("complete-slice", "M990/S01", project),
		false,
	);
});

test("dispatchDirectPhase_when_db_has_no_completed_slices_ignores_stale_roadmap_done_checkbox", async () => {
	const project = makeProject();
	insertSlice({
		milestoneId: "M990",
		id: "S01",
		title: "Pending in DB",
		status: "pending",
		sequence: 1,
	});
	writeFileSync(
		join(project, ".sf", "milestones", "M990", "M990-ROADMAP.md"),
		[
			"# M990: stale roadmap",
			"",
			"## Slice Overview",
			"| ID | Slice | Risk | Depends | Done | After this |",
			"|----|-------|------|---------|------|------------|",
			"| S01 | Pending in DB | low | - | ✅ | stale done |",
			"",
		].join("\n"),
	);
	const notifications = [];
	const ctx = {
		ui: {
			notify(message, level) {
				notifications.push({ message, level });
			},
		},
		async newSession() {
			throw new Error("newSession should not be called");
		},
	};
	const pi = {};

	await dispatchDirectPhase(ctx, pi, "reassess-roadmap", project);

	assert.deepEqual(notifications, [
		{
			message: "Cannot dispatch reassess-roadmap: no completed slices.",
			level: "warning",
		},
	]);
});
