/**
 * plan-milestone-vision-trace.test.mjs — ADR-0000 P2 (schema v69).
 *
 * Purpose: prove plan_milestone requires every slice to carry a literal
 * milestone.vision fragment in tracesVisionFragment, persists it to the
 * slices row (schema v69), and rejects fragments that do not appear in
 * milestone.vision. This gives validate-milestone structured grounds for
 * assessment instead of having to re-read milestone.vision through the LLM
 * on every check (restoration of doctrine, not new work).
 *
 * Consumer: plan_milestone validation + validate-milestone (downstream).
 * Contract: rejects missing fragment, rejects fragment-not-in-vision,
 *           persists valid fragment to slices.traces_vision_fragment.
 * Falsifier: a slice without the fragment can be inserted, or a fragment
 *            absent from milestone.vision is accepted.
 */
import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, test } from "vitest";
import { closeDatabase, getMilestoneSlices, openDatabase } from "../sf-db.js";
import { handlePlanMilestone } from "../tools/plan-milestone.js";

const tmpDirs = [];

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

function makeProject() {
	const dir = mkdtempSync(join(tmpdir(), "sf-plan-milestone-vt-"));
	tmpDirs.push(dir);
	mkdirSync(join(dir, ".sf"), { recursive: true });
	openDatabase(join(dir, ".sf", "sf.db"));
	return dir;
}

function baseSlice(overrides = {}) {
	return {
		sliceId: "S01",
		title: "First slice",
		risk: "medium",
		depends: [],
		demo: "User can view correlated VM+PG health on the dashboard.",
		goal: "Render correlated VM+PG health from the joined source.",
		successCriteria: "Dashboard renders correlated VM+PG status for any VM.",
		proofLevel: "integration",
		integrationClosure: "Dashboard ships behind feature flag.",
		observabilityImpact: "Adds dashboard latency metric.",
		...overrides,
	};
}

const baseVision =
	"Users can view correlated VM+PG health on a single dashboard, and operators can drill down into per-VM logs.";

describe("plan_milestone vision trace (schema v69)", () => {
	test("rejects_slice_with_missing_tracesVisionFragment", async () => {
		const base = makeProject();
		const result = await handlePlanMilestone(
			{
				milestoneId: "M050",
				title: "Vision Trace Required",
				vision: baseVision,
				slices: [
					baseSlice({
						// tracesVisionFragment intentionally omitted
					}),
				],
			},
			base,
		);
		expect(result.error).toBeDefined();
		expect(result.error).toMatch(/tracesVisionFragment/);
		// DB write must not have happened.
		expect(getMilestoneSlices("M050")).toHaveLength(0);
	});

	test("rejects_fragment_not_present_in_vision", async () => {
		const base = makeProject();
		const result = await handlePlanMilestone(
			{
				milestoneId: "M051",
				title: "Fragment Must Be Verbatim",
				vision: baseVision,
				slices: [
					baseSlice({
						tracesVisionFragment: "export Grafana dashboards as PDF reports",
					}),
				],
			},
			base,
		);
		expect(result.error).toBeDefined();
		expect(result.error).toMatch(/tracesVisionFragment.*must appear verbatim/i);
		expect(getMilestoneSlices("M051")).toHaveLength(0);
	});

	test("persists_valid_fragment_to_slice_row", async () => {
		const base = makeProject();
		const result = await handlePlanMilestone(
			{
				milestoneId: "M052",
				title: "Vision Trace Persists",
				vision: baseVision,
				slices: [
					baseSlice({
						sliceId: "S01",
						tracesVisionFragment: "view correlated VM+PG health",
					}),
					baseSlice({
						sliceId: "S02",
						title: "Per-VM logs drilldown",
						demo: "Operators drill down into per-VM logs.",
						goal: "Wire operator drilldown to per-VM logs.",
						successCriteria: "Operator can click VM and see filtered logs.",
						tracesVisionFragment: "operators can drill down into per-VM logs",
					}),
				],
			},
			base,
		);
		expect(result.error).toBeUndefined();
		expect(result.milestoneId).toBe("M052");
		const slices = getMilestoneSlices("M052");
		expect(slices).toHaveLength(2);
		const s01 = slices.find((s) => s.id === "S01");
		const s02 = slices.find((s) => s.id === "S02");
		expect(s01?.traces_vision_fragment).toBe("view correlated VM+PG health");
		expect(s02?.traces_vision_fragment).toBe(
			"operators can drill down into per-VM logs",
		);
	});

	test("fragment_match_is_whitespace_and_case_insensitive", async () => {
		// The validator normalizes whitespace and case before substring check,
		// so trivial formatting differences do not cause spurious rejection.
		const base = makeProject();
		const result = await handlePlanMilestone(
			{
				milestoneId: "M053",
				title: "Normalization Works",
				vision: baseVision,
				slices: [
					baseSlice({
						// Mixed case, extra spaces — still a literal clause of vision.
						tracesVisionFragment: "  View Correlated   VM+PG Health  ",
					}),
				],
			},
			base,
		);
		expect(result.error).toBeUndefined();
		const slices = getMilestoneSlices("M053");
		expect(slices).toHaveLength(1);
		// The stored fragment is the normalized planning text (trimmed/squeezed).
		expect(slices[0].traces_vision_fragment).toMatch(
			/view correlated.*vm\+pg health/i,
		);
	});
});
