/**
 * Regression test for #2675: completing-milestone dispatch rule must block
 * completion when VALIDATION verdict is not "pass".
 *
 * Without this guard, non-pass validation + allSlicesDone can dispatch
 * complete-milestone and incorrectly close a milestone that still needs
 * attention or remediation.
 */

import assert from "node:assert/strict";
import {
	existsSync,
	mkdirSync,
	mkdtempSync,
	readFileSync,
	rmSync,
	writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { test } from 'vitest';

import { DISPATCH_RULES } from "../auto-dispatch.ts";

/** Find the completing-milestone dispatch rule */
const completingRule = DISPATCH_RULES.find(
	(r) => r.name === "completing-milestone → complete-milestone",
);

test("completing-milestone dispatch rule exists", () => {
	assert.ok(completingRule, "rule should exist in DISPATCH_RULES");
});

async function assertNonPassVerdictBlocksCompletion(
	verdict: string,
): Promise<void> {
	const base = mkdtempSync(join(tmpdir(), "sf-remediation-"));
	mkdirSync(join(base, ".sf", "milestones", "M001"), { recursive: true });

	try {
		// Write a VALIDATION file with a non-pass verdict.
		writeFileSync(
			join(base, ".sf", "milestones", "M001", "M001-VALIDATION.md"),
			[
				"---",
				`verdict: ${verdict}`,
				"remediation_round: 0",
				"---",
				"",
				"# Validation Report",
				"",
				"Success criteria need follow-up.",
			].join("\n"),
		);

		const ctx = {
			mid: "M001",
			midTitle: "Test Milestone",
			basePath: base,
			state: { phase: "completing-milestone" } as any,
			prefs: {} as any,
			session: undefined,
		};

		const result = await completingRule!.match(ctx);

		assert.ok(result !== null, "rule should match");
		assert.equal(result!.action, "stop", "should return stop action");
		if (result!.action === "stop") {
			assert.equal(
				result!.level,
				"warning",
				"should be warning level (pausable)",
			);
			assert.ok(
				result!.reason.includes(verdict),
				`reason should mention ${verdict}`,
			);
			assert.ok(
				result!.reason.includes('"pass"'),
				"reason should mention pass as the only automatic completion verdict",
			);
		}
	} finally {
		rmSync(base, { recursive: true, force: true });
	}
}

test("completing-milestone blocks when VALIDATION verdict is needs-remediation (#2675)", async () => {
	await assertNonPassVerdictBlocksCompletion("needs-remediation");
});

test("completing-milestone blocks when VALIDATION verdict is needs-attention", async () => {
	await assertNonPassVerdictBlocksCompletion("needs-attention");
});

test("completing-milestone dispatches validation-attention remediation once when plan is present", async () => {
	const base = mkdtempSync(join(tmpdir(), "sf-remediation-"));
	mkdirSync(join(base, ".sf", "milestones", "M001"), { recursive: true });

	try {
		writeFileSync(
			join(base, ".sf", "milestones", "M001", "M001-VALIDATION.md"),
			[
				"---",
				"verdict: needs-attention",
				"remediation_round: 0",
				"---",
				"",
				"# Validation Report",
				"",
				"Success criteria need follow-up.",
				"",
				"## Remediation Plan",
				"- Fix roadmap tracking.",
				"- Explicitly defer hardware-only validation.",
			].join("\n"),
		);

		const ctx = {
			mid: "M001",
			midTitle: "Test Milestone",
			basePath: base,
			state: { phase: "completing-milestone" } as any,
			prefs: {} as any,
			session: undefined,
		};

		const result = await completingRule!.match(ctx);

		assert.ok(result !== null, "rule should match");
		assert.equal(result!.action, "dispatch");
		if (result!.action === "dispatch") {
			assert.equal(result!.unitType, "rewrite-docs");
			assert.equal(result!.unitId, "M001/validation-attention");
			assert.match(result!.prompt, /Resolve Validation Attention/);
			assert.match(result!.prompt, /verdict: needs-remediation/);
		}

		assert.equal(
			existsSync(
				join(base, ".sf", "runtime", "validation-attention", "M001.json"),
			),
			true,
			"dispatch writes a loop-prevention marker",
		);

		mkdirSync(join(base, ".sf", "runtime", "units"), { recursive: true });
		writeFileSync(
			join(
				base,
				".sf",
				"runtime",
				"units",
				"rewrite-docs-M001-validation-attention.json",
			),
			JSON.stringify({
				version: 1,
				unitType: "rewrite-docs",
				unitId: "M001/validation-attention",
				phase: "dispatched",
			}),
		);

		const second = await completingRule!.match(ctx);
		assert.ok(second !== null, "second rule should match");
		assert.equal(second!.action, "stop", "second pass should not loop");
	} finally {
		rmSync(base, { recursive: true, force: true });
	}
});

test("completing-milestone redispatches validation-attention when marker is stale", async () => {
	const base = mkdtempSync(join(tmpdir(), "sf-remediation-"));
	mkdirSync(join(base, ".sf", "milestones", "M001"), { recursive: true });
	mkdirSync(join(base, ".sf", "runtime", "validation-attention"), {
		recursive: true,
	});

	try {
		writeFileSync(
			join(base, ".sf", "milestones", "M001", "M001-VALIDATION.md"),
			[
				"---",
				"verdict: needs-attention",
				"remediation_round: 0",
				"---",
				"",
				"# Validation Report",
				"",
				"## Remediation Plan",
				"- Fix roadmap tracking.",
			].join("\n"),
		);
		writeFileSync(
			join(base, ".sf", "runtime", "validation-attention", "M001.json"),
			JSON.stringify({
				milestoneId: "M001",
				createdAt: new Date().toISOString(),
			}),
		);

		const ctx = {
			mid: "M001",
			midTitle: "Test Milestone",
			basePath: base,
			state: { phase: "completing-milestone" } as any,
			prefs: {} as any,
			session: undefined,
		};

		const result = await completingRule!.match(ctx);

		assert.ok(result !== null, "rule should match");
		assert.equal(result!.action, "dispatch");
		if (result!.action === "dispatch") {
			assert.equal(result!.unitType, "rewrite-docs");
			assert.equal(result!.unitId, "M001/validation-attention");
		}
	} finally {
		rmSync(base, { recursive: true, force: true });
	}
});

test("completing-milestone revalidates after validation-attention remediation advances round", async () => {
	const base = mkdtempSync(join(tmpdir(), "sf-remediation-"));
	mkdirSync(join(base, ".sf", "milestones", "M001"), { recursive: true });
	mkdirSync(join(base, ".sf", "runtime", "validation-attention"), {
		recursive: true,
	});
	mkdirSync(join(base, ".sf", "runtime", "units"), { recursive: true });

	try {
		writeFileSync(
			join(base, ".sf", "milestones", "M001", "M001-VALIDATION.md"),
			[
				"---",
				"verdict: needs-attention",
				"remediation_round: 1",
				"---",
				"",
				"# Validation Report",
				"",
				"Tracking cleanup was applied. Revalidate before completion.",
			].join("\n"),
		);
		writeFileSync(
			join(base, ".sf", "runtime", "validation-attention", "M001.json"),
			JSON.stringify({
				milestoneId: "M001",
				createdAt: new Date().toISOString(),
				remediationRound: 0,
			}),
		);
		writeFileSync(
			join(
				base,
				".sf",
				"runtime",
				"units",
				"rewrite-docs-M001-validation-attention.json",
			),
			JSON.stringify({
				version: 1,
				unitType: "rewrite-docs",
				unitId: "M001/validation-attention",
				phase: "dispatched",
			}),
		);

		const ctx = {
			mid: "M001",
			midTitle: "Test Milestone",
			basePath: base,
			state: { phase: "completing-milestone" } as any,
			prefs: {} as any,
			session: undefined,
		};

		const result = await completingRule!.match(ctx);

		assert.ok(result !== null, "rule should match");
		assert.equal(result!.action, "dispatch");
		if (result!.action === "dispatch") {
			assert.equal(result!.unitType, "validate-milestone");
			assert.equal(result!.unitId, "M001");
		}

		const marker = JSON.parse(
			readFileSync(
				join(base, ".sf", "runtime", "validation-attention", "M001.json"),
				"utf-8",
			),
		);
		assert.equal(marker.revalidationRound, 1);

		const second = await completingRule!.match(ctx);
		assert.ok(second !== null, "second rule should match");
		assert.equal(
			second!.action,
			"stop",
			"second pass should not revalidate-loop",
		);
	} finally {
		rmSync(base, { recursive: true, force: true });
	}
});

test("completing-milestone proceeds normally when VALIDATION verdict is pass (#2675 guard)", async () => {
	const base = mkdtempSync(join(tmpdir(), "sf-remediation-"));
	mkdirSync(join(base, ".sf", "milestones", "M001"), { recursive: true });

	try {
		// Write a VALIDATION file with pass verdict
		writeFileSync(
			join(base, ".sf", "milestones", "M001", "M001-VALIDATION.md"),
			[
				"---",
				"verdict: pass",
				"---",
				"",
				"# Validation Report",
				"",
				"All criteria met.",
			].join("\n"),
		);

		const ctx = {
			mid: "M001",
			midTitle: "Test Milestone",
			basePath: base,
			state: { phase: "completing-milestone" } as any,
			prefs: {} as any,
			session: undefined,
		};

		const result = await completingRule!.match(ctx);

		// Should NOT return a stop — should either dispatch or return stop for
		// a different reason (e.g. missing SUMMARY files, no implementation)
		if (result && result.action === "stop") {
			assert.ok(
				!result.reason.includes("Only verdict"),
				"pass verdict should NOT trigger the non-pass validation guard",
			);
		}
	} finally {
		rmSync(base, { recursive: true, force: true });
	}
});
