import assert from "node:assert/strict";
import {
	existsSync,
	mkdirSync,
	mkdtempSync,
	readFileSync,
	rmSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, test } from "vitest";
import { getCanonicalMilestonePlan } from "../canonical-milestone-plan.js";
import { renderRoadmapFromDb } from "../markdown-renderer.js";
import {
	closeDatabase,
	insertMilestone,
	insertSlice,
	openDatabase,
} from "../sf-db.js";

const tmpDirs = [];

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

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

describe("roadmap JSON projection", () => {
	test("renderRoadmapFromDb_writes_db_origin_projection_with_sequence", async () => {
		const project = makeProject();
		insertMilestone({
			id: "M451",
			title: "Structured fallback",
			status: "active",
			planning: {
				vision: "Keep dispatch off rendered Markdown.",
				successCriteria: ["JSON projection exists."],
				visionMeeting: {
					trigger: "Need category research before locking the roadmap.",
					pm: "The roadmap must make planning state dependable.",
					userAdvocate:
						"Users need dispatch to stay stable after rendering changes.",
					customerPanel:
						"Maintainers value deterministic state; operators value readable projections.",
					business: "Broken dispatch blocks adoption.",
					researcher:
						"Comparable systems: Spec Kit keeps specs as durable workflow artifacts; Forge should keep DB state authoritative while rendering readable projections.",
					deliveryLead: "One projection slice is enough.",
					partner: "DB-origin projection is the right direction.",
					combatant:
						"Rendered markdown could drift from DB if not regenerated.",
					architect:
						"The DB projection is the stable contract; markdown is a view.",
					moderator:
						"Proceed with DB-origin projection and explicit rendering.",
					weightedSynthesis:
						"Keep dispatch on JSON and render markdown for humans.",
					confidenceByArea: "High for projection, medium for migration.",
					recommendedRoute: "planning",
				},
				productResearch: {
					purpose: "Decide how roadmap projections compare to spec systems.",
					consumer: "Milestone planners deciding projection requirements.",
					contract:
						"Identify what comparable spec systems store durably before locking slices.",
					failureBoundary:
						"If findings are weak, keep projection scope internal and avoid product claims.",
					evidence:
						"Spec Kit was inspected as a comparable spec workflow artifact system.",
					nonGoals: "Do not copy Spec Kit's project layout or command surface.",
					invariants:
						"Forge keeps DB state authoritative and renders markdown as a projection.",
					assumptions:
						"Spec Kit behavior is representative enough for this projection decision.",
					category: "agent planning systems",
					targetJob: "keep roadmap state machine-readable and human-readable",
					comparables: ["Spec Kit keeps specs as durable workflow artifacts."],
					tableStakes: ["Rendered plans remain readable to humans."],
					differentiators: ["DB-origin projection stays authoritative."],
					antiPatterns: ["Do not make markdown the dispatch source of truth."],
				},
			},
		});
		insertSlice({
			milestoneId: "M451",
			id: "S01",
			title: "Projected slice",
			status: "pending",
			risk: "medium",
			depends: [],
			demo: "Dispatch can read JSON.",
			sequence: 1,
		});

		const result = await renderRoadmapFromDb(project, "M451");

		assert.equal(existsSync(result.roadmapPath), true);
		const roadmap = readFileSync(result.roadmapPath, "utf-8");
		assert.match(roadmap, /## Product \/ Competitor Research/);
		assert.match(roadmap, /### Purpose Contract/);
		assert.match(roadmap, /Purpose: Decide how roadmap projections/);
		assert.match(
			roadmap,
			/Comparable: Spec Kit keeps specs as durable workflow artifacts/,
		);
		assert.equal(existsSync(result.roadmapJsonPath), true);
		const json = JSON.parse(readFileSync(result.roadmapJsonPath, "utf-8"));
		assert.equal(json.schemaVersion, 1);
		assert.equal(json.origin, "db-projection");
		assert.equal(typeof json.generatedAt, "string");
		assert.equal(json.sourceDbPath, join(project, ".sf", "sf.db"));
		assert.equal(json.milestone.id, "M451");
		assert.equal(
			json.milestone.productResearch.purpose,
			"Decide how roadmap projections compare to spec systems.",
		);
		assert.deepEqual(
			json.slices.map((slice) => [
				slice.id,
				slice.title,
				slice.risk,
				slice.sequence,
			]),
			[["S01", "Projected slice", "medium", 1]],
		);
		closeDatabase();
		rmSync(join(project, ".sf", "sf.db"), { force: true });

		const fallback = getCanonicalMilestonePlan(project, "M451", {
			allowProjectionFallback: true,
		});

		assert.equal(fallback.safe, true);
		assert.equal(fallback.source, "projection");
		assert.equal(fallback.slices[0].id, "S01");
	});
});
