/**
 * canonical-milestone-plan.test.mjs - contract tests for canonical plan reads.
 *
 * Purpose: prove dispatch-facing milestone reads prefer structured state and
 * block Markdown-only roadmaps.
 */
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, test } from "vitest";
import { getCanonicalMilestonePlan } from "../canonical-milestone-plan.js";
import {
	closeDatabase,
	insertMilestone,
	insertSlice,
	openDatabase,
} from "../sf-db.js";

function makeTempDir(prefix) {
	return mkdtempSync(join(tmpdir(), prefix));
}

function cleanup(dir) {
	try {
		rmSync(dir, { recursive: true, force: true });
	} catch {}
}

function writeMilestoneFile(base, milestoneId, fileName, content) {
	const dir = join(base, ".sf", "milestones", milestoneId);
	mkdirSync(dir, { recursive: true });
	writeFileSync(join(dir, fileName), content);
}

describe("getCanonicalMilestonePlan", () => {
	afterEach(() => {
		closeDatabase();
	});

	test("db_plan_when_markdown_is_stale_returns_db_slices", () => {
		const base = makeTempDir("sf-canonical-plan-");
		try {
			mkdirSync(join(base, ".sf"), { recursive: true });
			openDatabase(join(base, ".sf", "sf.db"));
			insertMilestone({
				id: "M321",
				title: "DB milestone",
				status: "active",
				planning: {
					vision: "Use canonical DB slices.",
					successCriteria: ["Dispatch reads DB."],
				},
			});
			insertSlice({
				milestoneId: "M321",
				id: "S01",
				title: "DB slice",
				status: "pending",
				risk: "low",
				depends: [],
				demo: "DB slice demo.",
				sequence: 2,
			});
			insertSlice({
				milestoneId: "M321",
				id: "S00",
				title: "Higher priority DB slice",
				status: "pending",
				risk: "low",
				depends: [],
				demo: "Runs first.",
				sequence: 1,
			});
			writeMilestoneFile(
				base,
				"M321",
				"M321-ROADMAP.md",
				"# M321\n\n| ID | Slice |\n|----|-------|\n| S99 | stale Markdown slice |\n",
			);

			const result = getCanonicalMilestonePlan(base, "M321");

			expect(result.safe).toBe(true);
			expect(result.blocked).toBe(false);
			expect(result.source).toBe("db");
			expect(result.slices).toHaveLength(2);
			expect(result.slices.map((slice) => slice.id)).toEqual(["S00", "S01"]);
		} finally {
			closeDatabase();
			cleanup(base);
		}
	});

	test("projection_fallback_when_db_unavailable_returns_projected_slices", () => {
		const base = makeTempDir("sf-canonical-plan-");
		try {
			writeMilestoneFile(
				base,
				"M322",
				"M322-ROADMAP.json",
				JSON.stringify(
					{
						milestone: {
							id: "M322",
							title: "Projected milestone",
							vision: "Use structured projection.",
						},
						slices: [
							{
								sliceId: "S02",
								title: "Projected slice",
								status: "pending",
								risk: "medium",
								depends: ["S01"],
								demo: "Projection demo.",
							},
						],
					},
					null,
					2,
				),
			);

			const result = getCanonicalMilestonePlan(base, "M322");

			expect(result.safe).toBe(true);
			expect(result.source).toBe("projection");
			expect(result.milestone).toMatchObject({
				id: "M322",
				title: "Projected milestone",
			});
			expect(result.slices[0]).toMatchObject({
				id: "S02",
				title: "Projected slice",
				depends: ["S01"],
			});
		} finally {
			cleanup(base);
		}
	});

	test("db_backed_project_when_db_slices_missing_blocks_projection_fallback", () => {
		const base = makeTempDir("sf-canonical-plan-");
		try {
			mkdirSync(join(base, ".sf"), { recursive: true });
			openDatabase(join(base, ".sf", "sf.db"));
			insertMilestone({
				id: "M324",
				title: "DB missing slices",
				status: "active",
			});
			writeMilestoneFile(
				base,
				"M324",
				"M324-ROADMAP.json",
				JSON.stringify(
					{
						origin: "db-projection",
						milestone: { id: "M324", title: "Projected milestone" },
						slices: [{ id: "S99", title: "Stale projected slice" }],
					},
					null,
					2,
				),
			);

			const result = getCanonicalMilestonePlan(base, "M324");

			expect(result.safe).toBe(false);
			expect(result.source).toBe("db-missing-slices");
			expect(result.reason).toContain(
				"projection files are export/recovery only",
			);
		} finally {
			closeDatabase();
			cleanup(base);
		}
	});

	test("markdown_only_when_no_structured_state_returns_unsafe_blocked_result", () => {
		const base = makeTempDir("sf-canonical-plan-");
		try {
			writeMilestoneFile(
				base,
				"M323",
				"M323-ROADMAP.md",
				"# M323\n\n- [ ] S01 stale row\n",
			);

			const result = getCanonicalMilestonePlan(base, "M323");

			expect(result.safe).toBe(false);
			expect(result.unsafe).toBe(true);
			expect(result.blocked).toBe(true);
			expect(result.source).toBe("markdown-only");
			expect(result.slices).toEqual([]);
			expect(result.reason).toContain("rendered display state only");
		} finally {
			cleanup(base);
		}
	});
});
