import assert from "node:assert/strict";
import { afterEach, test } from "vitest";
import {
	closeDatabase,
	getMilestoneSpec,
	getSliceSpec,
	getTaskSpec,
	insertMilestone,
	insertSlice,
	insertTask,
	openDatabase,
	upsertMilestonePlanning,
	upsertSlicePlanning,
	upsertTaskPlanning,
} from "../sf-db.js";

afterEach(() => {
	closeDatabase();
});

test("specTables_when_live_planning_written_persist_write_once_intent", () => {
	openDatabase(":memory:");

	insertMilestone({
		id: "M001",
		title: "Spec source",
		status: "active",
		planning: {
			vision: "Initial milestone vision",
			successCriteria: ["initial criterion"],
			productResearch: {
				purpose: "Validate product research spec persistence.",
				consumer: "Spec table tests.",
				contract: "Initial intent must be preserved in milestone_specs.",
				failureBoundary: "Fail if a replan overwrites original spec intent.",
				evidence: "Unit test fixture.",
				nonGoals: "Do not validate rendered roadmap prose here.",
				invariants: "Spec rows are insert-once intent records.",
				assumptions: "The in-memory DB uses current schema.",
				category: "test harness",
				targetJob: "prove spec persistence",
				comparables: ["Spec Kit durable specs"],
				tableStakes: ["Initial spec data survives replans"],
				differentiators: ["DB spec table remains authoritative"],
				antiPatterns: ["Do not overwrite original intent silently"],
			},
		},
	});
	insertSlice({
		milestoneId: "M001",
		id: "S01",
		title: "Spec slice",
		planning: {
			goal: "Initial slice goal",
			successCriteria: "initial slice criterion",
		},
	});
	insertTask({
		milestoneId: "M001",
		sliceId: "S01",
		id: "T01",
		title: "Spec task",
		planning: {
			verify: "npm test",
			inputs: ["initial input"],
			expectedOutput: ["initial output"],
		},
	});

	upsertMilestonePlanning("M001", {
		vision: "Changed milestone vision",
		successCriteria: ["changed criterion"],
		productResearch: {
			purpose: "Changed product research.",
			consumer: "Changed consumer.",
			contract: "Changed contract.",
			failureBoundary: "Changed failure boundary.",
			evidence: "Changed evidence.",
			nonGoals: "Changed non-goals.",
			invariants: "Changed invariants.",
			assumptions: "Changed assumptions.",
			category: "changed category",
			targetJob: "changed job",
			comparables: ["changed comparable"],
			tableStakes: ["changed table stake"],
			differentiators: ["changed differentiator"],
			antiPatterns: ["changed anti-pattern"],
		},
	});
	upsertSlicePlanning("M001", "S01", {
		goal: "Changed slice goal",
		successCriteria: "changed slice criterion",
	});
	upsertTaskPlanning("M001", "S01", "T01", {
		verify: "npm run test:unit",
		inputs: ["changed input"],
		expectedOutput: ["changed output"],
	});

	const milestoneSpec = getMilestoneSpec("M001");
	assert.equal(milestoneSpec.vision, "Initial milestone vision");
	assert.deepEqual(JSON.parse(milestoneSpec.success_criteria), [
		"initial criterion",
	]);
	assert.equal(
		JSON.parse(milestoneSpec.product_research_json).purpose,
		"Validate product research spec persistence.",
	);
	assert.equal(milestoneSpec.spec_version, 1);

	const sliceSpec = getSliceSpec("M001", "S01");
	assert.equal(sliceSpec.goal, "Initial slice goal");
	assert.equal(sliceSpec.success_criteria, "initial slice criterion");
	assert.equal(sliceSpec.spec_version, 1);

	const taskSpec = getTaskSpec("M001", "S01", "T01");
	assert.equal(taskSpec.verify, "npm test");
	assert.deepEqual(JSON.parse(taskSpec.inputs), ["initial input"]);
	assert.deepEqual(JSON.parse(taskSpec.expected_output), ["initial output"]);
	assert.equal(taskSpec.spec_version, 1);
});

test("specTables_when_shell_milestone_created_before_planning_captures_first_real_plan", () => {
	openDatabase(":memory:");

	insertMilestone({
		id: "M002",
		title: "Shell first",
		status: "active",
	});
	upsertMilestonePlanning("M002", {
		vision: "First real planning payload",
		successCriteria: ["first criterion"],
		productResearch: {
			purpose: "Capture first real product research.",
			consumer: "Milestone planners.",
			contract: "Shell creation must not freeze an empty spec row.",
			failureBoundary: "Fail if milestone_specs stays empty.",
			evidence: "Unit test fixture.",
			nonGoals: "Do not update non-empty immutable spec rows.",
			invariants: "First real planning payload is the spec intent.",
			assumptions: "Shell milestones can exist before planning.",
			category: "planning runtime",
			targetJob: "preserve spec intent",
			comparables: ["Spec Kit first generated spec"],
			tableStakes: ["Specs are not accidentally empty."],
			differentiators: ["DB shell rows can be safely filled later."],
			antiPatterns: ["INSERT OR IGNORE preserving empty placeholders"],
		},
	});
	upsertMilestonePlanning("M002", {
		vision: "Changed planning payload",
		successCriteria: ["changed criterion"],
	});

	const milestoneSpec = getMilestoneSpec("M002");
	assert.equal(milestoneSpec.vision, "First real planning payload");
	assert.deepEqual(JSON.parse(milestoneSpec.success_criteria), [
		"first criterion",
	]);
	assert.equal(
		JSON.parse(milestoneSpec.product_research_json).purpose,
		"Capture first real product research.",
	);
});

test("specTables_when_empty_milestone_spec_exists_upsert_replaces_without_unused_created_at_parameter", () => {
	openDatabase(":memory:");

	insertMilestone({
		id: "M004",
		title: "Shell first",
		status: "active",
	});
	upsertMilestonePlanning("M004", {
		vision: "First real planning payload",
		successCriteria: ["first criterion"],
	});

	const milestoneSpec = getMilestoneSpec("M004");
	assert.equal(milestoneSpec.vision, "First real planning payload");
	assert.deepEqual(JSON.parse(milestoneSpec.success_criteria), [
		"first criterion",
	]);
});

test("specTables_when_shell_task_created_before_planning_captures_first_real_plan", () => {
	openDatabase(":memory:");

	insertMilestone({
		id: "M003",
		title: "Task shell milestone",
		status: "active",
	});
	insertSlice({
		milestoneId: "M003",
		id: "S01",
		title: "Task shell slice",
		status: "pending",
	});
	insertTask({
		milestoneId: "M003",
		sliceId: "S01",
		id: "T01",
		title: "Shell task",
		status: "pending",
	});

	assert.equal(getTaskSpec("M003", "S01", "T01"), undefined);

	upsertTaskPlanning("M003", "S01", "T01", {
		verify: "npm test -- task-shell",
		inputs: ["src/task-input.ts"],
		expectedOutput: ["src/task-output.ts"],
		risk: "high",
		mutationScope: "bounded",
	});
	upsertTaskPlanning("M003", "S01", "T01", {
		verify: "npm test -- changed",
		inputs: ["src/changed-input.ts"],
		expectedOutput: ["src/changed-output.ts"],
		risk: "low",
		mutationScope: "isolated",
	});

	const taskSpec = getTaskSpec("M003", "S01", "T01");
	assert.equal(taskSpec.verify, "npm test -- task-shell");
	assert.deepEqual(JSON.parse(taskSpec.inputs), ["src/task-input.ts"]);
	assert.deepEqual(JSON.parse(taskSpec.expected_output), [
		"src/task-output.ts",
	]);
	assert.equal(taskSpec.risk, "high");
	assert.equal(taskSpec.mutation_scope, "bounded");
	assert.equal(taskSpec.spec_version, 1);
});
