/**
 * state-reconcile.test.mjs — reconcileTaskFromSummary remediation path.
 *
 * Purpose: prove the operator-invoked remediation correctly applies on-disk
 * SUMMARY metadata into a pending DB row, and returns structured outcomes
 * for the cases where it should NOT apply.
 */
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 {
	closeDatabase,
	getTask,
	insertMilestone,
	insertSlice,
	insertTask,
	openDatabase,
} from "../sf-db.js";
import { reconcileTaskFromSummary } from "../state-reconcile.js";

const tmpDirs = [];

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

function makeForgeProject() {
	const dir = mkdtempSync(join(tmpdir(), "sf-state-reconcile-"));
	tmpDirs.push(dir);
	mkdirSync(join(dir, ".sf"), { recursive: true });
	writeFileSync(
		join(dir, "package.json"),
		JSON.stringify({ name: "singularity-forge" }),
	);
	openDatabase(join(dir, ".sf", "sf.db"));
	return dir;
}

function writeTaskSummary(dir, milestoneId, sliceId, taskId, content) {
	const taskDir = join(
		dir,
		".sf",
		"milestones",
		milestoneId,
		"slices",
		sliceId,
		"tasks",
	);
	mkdirSync(taskDir, { recursive: true });
	writeFileSync(join(taskDir, `${taskId}-SUMMARY.md`), content);
}

function seedPendingTask(milestoneId, sliceId, taskId) {
	insertMilestone({ id: milestoneId, title: milestoneId, status: "active" });
	insertSlice({
		milestoneId,
		id: sliceId,
		title: sliceId,
		status: "active",
		risk: "medium",
		sequence: 1,
	});
	insertTask({
		milestoneId,
		sliceId,
		id: taskId,
		title: `Task ${taskId}`,
		status: "pending",
		description: "",
		estimate: "",
		files: [],
		sequence: 1,
	});
}

const VALID_SUMMARY = `---
id: T02
parent: S05
milestone: M001-6377a4
key_files:
  - docs/dev/drafts/sf-ace-patterns.md
key_decisions:
  - (none)
duration:
verification_result: passed
completed_at: 2026-05-13T00:05:40.130Z
blocker_discovered: false
---

# T02: Finalized ACE Compatibility Reference Document

**ACE-compatible patterns reference complete.**

## What Happened

The doc was authored, audited, and parked under drafts/.
`;

describe("reconcileTaskFromSummary", () => {
	test("applies frontmatter + body to a pending task row", () => {
		const dir = makeForgeProject();
		seedPendingTask("M001-6377a4", "S05", "T02");
		writeTaskSummary(dir, "M001-6377a4", "S05", "T02", VALID_SUMMARY);

		const result = reconcileTaskFromSummary(dir, "M001-6377a4", "S05", "T02");

		expect(result.ok).toBe(true);
		expect(result.applied.status).toBe("complete");
		expect(result.applied.completedAt).toBe("2026-05-13T00:05:40.130Z");
		expect(result.applied.verificationResult).toBe("passed");
		expect(result.applied.blockerDiscovered).toBe(false);
		expect(result.applied.keyFiles).toEqual([
			"docs/dev/drafts/sf-ace-patterns.md",
		]);

		const updated = getTask("M001-6377a4", "S05", "T02");
		expect(updated.status).toBe("complete");
		expect(updated.completed_at).toBe("2026-05-13T00:05:40.130Z");
		expect(updated.verification_result).toBe("passed");
		// rowToTask parses the key_files JSON column into an array.
		expect(updated.key_files).toEqual(["docs/dev/drafts/sf-ace-patterns.md"]);
		expect(updated.full_summary_md).toContain("Finalized ACE Compatibility");
	});

	test("returns summary-missing when no SUMMARY on disk", () => {
		const dir = makeForgeProject();
		seedPendingTask("M001-6377a4", "S05", "T02");

		const result = reconcileTaskFromSummary(dir, "M001-6377a4", "S05", "T02");
		expect(result).toEqual({ ok: false, reason: "summary-missing" });
	});

	test("returns task-not-in-db when row does not exist", () => {
		const dir = makeForgeProject();
		writeTaskSummary(dir, "M001-6377a4", "S05", "T02", VALID_SUMMARY);

		const result = reconcileTaskFromSummary(dir, "M001-6377a4", "S05", "T02");
		expect(result).toEqual({ ok: false, reason: "task-not-in-db" });
	});

	test("returns summary-invalid when file is empty/whitespace", () => {
		const dir = makeForgeProject();
		seedPendingTask("M001-6377a4", "S05", "T02");
		writeTaskSummary(dir, "M001-6377a4", "S05", "T02", "   \n   ");

		const result = reconcileTaskFromSummary(dir, "M001-6377a4", "S05", "T02");
		expect(result.ok).toBe(false);
		expect(result.reason).toBe("summary-invalid");
	});

	test("returns already-done when task is already complete (no-op)", () => {
		const dir = makeForgeProject();
		insertMilestone({ id: "M001-6377a4", title: "M001", status: "active" });
		insertSlice({
			milestoneId: "M001-6377a4",
			id: "S05",
			title: "S05",
			status: "active",
			risk: "medium",
			sequence: 1,
		});
		insertTask({
			milestoneId: "M001-6377a4",
			sliceId: "S05",
			id: "T02",
			title: "Already done",
			status: "complete",
			description: "",
			estimate: "",
			files: [],
			sequence: 1,
		});
		writeTaskSummary(dir, "M001-6377a4", "S05", "T02", VALID_SUMMARY);

		const result = reconcileTaskFromSummary(dir, "M001-6377a4", "S05", "T02");
		expect(result.ok).toBe(false);
		expect(result.reason).toBe("already-done");
	});

	test("defaults completedAt to now when frontmatter is missing it", () => {
		const dir = makeForgeProject();
		seedPendingTask("M001-6377a4", "S05", "T02");
		const minimal = `---
id: T02
verification_result: passed
---

# T02: Done

**Done.**

## What Happened

Done.
`;
		writeTaskSummary(dir, "M001-6377a4", "S05", "T02", minimal);

		const before = Date.now();
		const result = reconcileTaskFromSummary(dir, "M001-6377a4", "S05", "T02");
		const after = Date.now();

		expect(result.ok).toBe(true);
		const t = new Date(result.applied.completedAt).getTime();
		expect(t).toBeGreaterThanOrEqual(before);
		expect(t).toBeLessThanOrEqual(after);
	});
});
