import assert from "node:assert/strict";
import {
	mkdirSync,
	mkdtempSync,
	readFileSync,
	rmSync,
	writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, test } from "vitest";
import { runSFDoctor } from "../doctor.js";

const tmpDirs = [];

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

function makeProject() {
	const dir = mkdtempSync(join(tmpdir(), "sf-doctor-form-lint-"));
	tmpDirs.push(dir);
	mkdirSync(join(dir, ".sf"), { recursive: true });
	return dir;
}

describe("doctor .sf form lint", () => {
	test("runSFDoctor_scoped_milestone_reports_requested_historical_delimiter_noise", async () => {
		const project = makeProject();
		mkdirSync(join(project, ".sf", "milestones", "M001"), {
			recursive: true,
		});
		writeFileSync(
			join(project, ".sf", "milestones", "M001", "M001-ROADMAP.md"),
			[
				"# M001: Done milestone",
				"",
				"## Slice Overview",
				"| ID | Slice | Risk | Depends | Done | After this |",
				"|----|-------|------|---------|------|------------|",
				"| S01 | Old — noisy | low | — | ✅ | done |",
				"",
			].join("\n"),
			"utf-8",
		);

		const report = await runSFDoctor(project, { scope: "M001" });

		assert.equal(
			report.issues.some(
				(issue) =>
					issue.code === "delimiter_in_title" && issue.unitId === "M001/S01",
			),
			true,
		);
	});

	test("runSFDoctor_reports_invalid_json_yaml_jsonl_and_markdown_frontmatter", async () => {
		const project = makeProject();
		writeFileSync(join(project, ".sf", "bad.json"), "{", "utf-8");
		writeFileSync(join(project, ".sf", "bad.yaml"), "a: [", "utf-8");
		writeFileSync(join(project, ".sf", "bad.jsonl"), '{"ok":true}\n{', "utf-8");
		writeFileSync(
			join(project, ".sf", "bad.md"),
			"---\ntitle: [\n---\n# Bad\n",
			"utf-8",
		);

		const report = await runSFDoctor(project, { scope: "project" });
		const invalidFiles = report.issues
			.filter((issue) => issue.code === "invalid_sf_form")
			.map((issue) => issue.file)
			.sort();

		assert.deepEqual(invalidFiles, [
			".sf/bad.json",
			".sf/bad.jsonl",
			".sf/bad.md",
			".sf/bad.yaml",
		]);
	});

	test("runSFDoctor_fix_repairs_json_object_written_as_jsonl", async () => {
		const project = makeProject();
		const target = join(project, ".sf", "self-feedback-resolved.jsonl");
		writeFileSync(
			target,
			'{\n  "id": "sf-test",\n  "resolvedAt": "2026-05-05T00:00:00.000Z"\n}\n',
			"utf-8",
		);

		const report = await runSFDoctor(project, {
			fix: true,
			fixLevel: "all",
			scope: "project",
		});

		assert.ok(
			report.fixesApplied.includes(
				"repaired .sf form syntax in .sf/self-feedback-resolved.jsonl",
			),
		);
		assert.equal(
			readFileSync(target, "utf-8"),
			'{"id":"sf-test","resolvedAt":"2026-05-05T00:00:00.000Z"}\n',
		);
	});

	test("runSFDoctor_fix_quotes_frontmatter_array_scalars_with_colons_and_newlines", async () => {
		const project = makeProject();
		const target = join(project, ".sf", "milestones", "M001", "SUMMARY.md");
		mkdirSync(join(project, ".sf", "milestones", "M001"), {
			recursive: true,
		});
		writeFileSync(
			target,
			[
				"---",
				"id: M001",
				"key_decisions:",
				"  - Fixed test with clear: false and clear: true cases.",
				"  - Empty files contain one newline (not `[]",
				"`) so split works.",
				"---",
				"# Summary",
				"",
			].join("\n"),
			"utf-8",
		);

		const report = await runSFDoctor(project, {
			fix: true,
			fixLevel: "all",
			scope: "project",
		});

		assert.ok(
			report.fixesApplied.includes(
				"repaired .sf form syntax in .sf/milestones/M001/SUMMARY.md",
			),
		);
		const repaired = readFileSync(target, "utf-8");
		assert.match(
			repaired,
			/ {2}- "Fixed test with clear: false and clear: true cases\."/,
		);
		assert.match(
			repaired,
			/ {2}- "Empty files contain one newline .*\\\\n.* so split works\."/,
		);
	});
});
