/**
 * complete-task-purpose-trace.test.mjs — ADR-0000 purpose-trace gate.
 *
 * Purpose: prove the SF v70 doctrine restoration:
 *   1. complete_task without purpose_trace is refused with an actionable
 *      error that names the slice.goal.
 *   2. complete_task with purpose_trace persists the trace verbatim.
 *   3. complete_slice with one unmarked task is refused.
 *   4. complete_slice with all tasks marked passes.
 *
 * Consumer: execute-task agents and the complete-slice gate that closes a
 * slice only after every task served the slice goal (per ADR-0000).
 */
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 {
	closeDatabase,
	getTask,
	insertMilestone,
	insertSlice,
	openDatabase,
} from "../sf-db.js";
import { handleCompleteSlice } from "../tools/complete-slice.js";
import { handleCompleteTask } from "../tools/complete-task.js";

const tmpDirs = [];

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

const SLICE_GOAL =
	"Restore per-task purpose trace so complete-slice can mechanically verify intent.";

function makeProject() {
	const dir = mkdtempSync(join(tmpdir(), "sf-purpose-trace-"));
	tmpDirs.push(dir);
	mkdirSync(join(dir, ".sf", "milestones", "M001", "slices", "S01", "tasks"), {
		recursive: true,
	});
	openDatabase(join(dir, ".sf", "sf.db"));
	insertMilestone({
		id: "M001",
		title: "Doctrine restoration",
		status: "active",
	});
	insertSlice({
		milestoneId: "M001",
		id: "S01",
		title: "Purpose trace slice",
		status: "pending",
		planning: {
			goal: SLICE_GOAL,
		},
	});
	return dir;
}

function baseTaskParams(overrides = {}) {
	return {
		milestoneId: "M001",
		sliceId: "S01",
		taskId: "T01",
		oneLiner: "Add purpose_trace column and gate",
		narrative: "Wired ALTER TABLE + complete_task gate end to end.",
		verification: "Focused vitest covering both refusal paths passed.",
		verificationEvidence: [
			{
				command: "npx vitest run complete-task-purpose-trace.test.mjs",
				exitCode: 0,
				verdict: "passed",
				durationMs: 10,
			},
		],
		...overrides,
	};
}

test("complete_task refuses without purpose_trace and names the slice goal", async () => {
	const project = makeProject();

	const result = await handleCompleteTask(baseTaskParams(), project);

	assert.ok(result.error, "expected an error payload");
	assert.match(
		result.error,
		/purpose_trace is required/i,
		"error must call out purpose_trace requirement",
	);
	assert.match(result.error, /ADR-0000/, "error must reference the doctrine");
	assert.ok(
		result.error.includes(SLICE_GOAL),
		`error must quote slice.goal verbatim so the agent can anchor against it; got: ${result.error}`,
	);
	// DB must not have flipped status when the call was refused.
	const stored = getTask("M001", "S01", "T01");
	assert.equal(stored, null, "refused complete_task must not insert a row");
});

test("complete_task persists purpose_trace verbatim when supplied", async () => {
	const project = makeProject();
	const trace =
		"Served slice.goal clause: 'mechanically verify intent' by adding the per-row trace column.";

	const result = await handleCompleteTask(
		baseTaskParams({ purposeTrace: trace }),
		project,
	);

	assert.equal(result.error, undefined, `unexpected error: ${result.error}`);
	const stored = getTask("M001", "S01", "T01");
	assert.equal(stored.status, "complete");
	assert.equal(
		stored.purpose_trace,
		trace,
		"purpose_trace must be persisted verbatim",
	);
});

test("complete_slice refuses when any task is missing purpose_trace", async () => {
	const project = makeProject();
	// T01 done with trace; T02 done WITHOUT trace by bypassing the handler
	// (writes a row directly to simulate a legacy/migration-leftover state).
	const trace = "T01 served the trace-column clause of the slice goal.";
	const okResult = await handleCompleteTask(
		baseTaskParams({ purposeTrace: trace }),
		project,
	);
	assert.equal(
		okResult.error,
		undefined,
		`T01 setup failed: ${okResult.error}`,
	);

	// Use the low-level insertTask to forge a complete-but-untraced row,
	// matching the "legacy NULL row" condition the migration explicitly
	// allows. Importing here keeps the simulation isolated from the main
	// handler path under test.
	const { insertTask } = await import("../sf-db.js");
	insertTask({
		id: "T02",
		sliceId: "S01",
		milestoneId: "M001",
		title: "Legacy task without trace",
		status: "complete",
		oneLiner: "Legacy",
		narrative: "Legacy",
		verificationResult: "Legacy",
		// purposeTrace intentionally omitted to simulate a NULL row
	});

	const result = await handleCompleteSlice(
		{
			milestoneId: "M001",
			sliceId: "S01",
			sliceTitle: "Purpose trace slice",
			oneLiner: "Did the thing.",
			narrative: "Did the thing for the doctrine.",
			verification: "All gates passed.",
			uatContent:
				"## UAT Type\n\n- UAT mode: artifact-driven\n- Why: tests cover.\n",
			operationalReadiness: "Ready.",
		},
		project,
	);

	assert.ok(result.error, "expected refusal payload");
	assert.match(result.error, /purpose_trace/i, "error must name purpose_trace");
	assert.match(
		result.error,
		/T02/,
		"error must identify the untraced task id so the operator can fix it",
	);
});

test("complete_slice passes when every task has a purpose_trace", async () => {
	const project = makeProject();

	const t01 = await handleCompleteTask(
		baseTaskParams({
			taskId: "T01",
			purposeTrace: "T01 served the trace-column clause.",
		}),
		project,
	);
	assert.equal(t01.error, undefined, `T01: ${t01.error}`);

	const t02 = await handleCompleteTask(
		baseTaskParams({
			taskId: "T02",
			purposeTrace: "T02 served the gate-refusal clause.",
		}),
		project,
	);
	assert.equal(t02.error, undefined, `T02: ${t02.error}`);

	const result = await handleCompleteSlice(
		{
			milestoneId: "M001",
			sliceId: "S01",
			sliceTitle: "Purpose trace slice",
			oneLiner: "Did the thing.",
			narrative: "Did the thing for the doctrine.",
			verification: "All gates passed.",
			uatContent:
				"## UAT Type\n\n- UAT mode: artifact-driven\n- Why: tests cover.\n",
			operationalReadiness: "Ready.",
		},
		project,
	);

	assert.equal(
		result.error,
		undefined,
		`complete_slice should pass when every task is traced; got: ${result.error}`,
	);
	assert.equal(result.sliceId, "S01");
});
