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 { test } from 'vitest';
import {
	formatUnitRecoveryStatus,
	inspectUnitRecoveryStatus,
	type RecoveryContext,
	recoverTimedOutUnit,
} from "../auto-timeout-recovery.ts";
import { closeDatabase, isDbAvailable } from "../sf-db.ts";

const ROADMAP_INCOMPLETE = `# M001: Test Milestone

## Slices

- [ ] **S01: Test Slice** \`risk:low\`
> After this: something works
`;

function createCompleteSliceFixture(): string {
	const base = mkdtempSync(join(tmpdir(), "sf-timeout-recovery-test-"));
	const sliceDir = join(base, ".sf", "milestones", "M001", "slices", "S01");
	mkdirSync(sliceDir, { recursive: true });
	writeFileSync(
		join(base, ".sf", "milestones", "M001", "M001-ROADMAP.md"),
		ROADMAP_INCOMPLETE,
		"utf-8",
	);
	writeFileSync(join(sliceDir, "S01-SUMMARY.md"), "# Summary\n", "utf-8");
	return base;
}

function makeRecoveryContext(base: string): RecoveryContext {
	return {
		basePath: base,
		verbose: false,
		currentUnitStartedAt: Date.now(),
		unitRecoveryCount: new Map(),
	};
}

test("inspectUnitRecoveryStatus reports complete-slice gaps even when SUMMARY exists", () => {
	if (isDbAvailable()) closeDatabase();
	const base = createCompleteSliceFixture();
	try {
		const status = inspectUnitRecoveryStatus(
			"complete-slice",
			"M001/S01",
			base,
		);
		const diagnostic = formatUnitRecoveryStatus(status);

		assert.equal(status.verified, false);
		assert.ok(
			!diagnostic.includes("S01-SUMMARY.md"),
			"existing SUMMARY must not be reported as missing",
		);
		assert.match(diagnostic, /UAT missing/);
		assert.match(diagnostic, /roadmap checkbox not marked \[x\]/);
	} finally {
		rmSync(base, { recursive: true, force: true });
	}
});

test("recoverTimedOutUnit steers complete-slice with exact missing durability items", async () => {
	if (isDbAvailable()) closeDatabase();
	const base = createCompleteSliceFixture();
	const sent: string[] = [];
	const notifications: string[] = [];
	try {
		const result = await recoverTimedOutUnit(
			{
				ui: {
					notify: (message: string) => notifications.push(message),
				},
			} as any,
			{
				sendMessage: (message: { content: string }) => {
					sent.push(message.content);
				},
			} as any,
			"complete-slice",
			"M001/S01",
			"idle",
			makeRecoveryContext(base),
		);

		assert.equal(result, "recovered");
		assert.equal(sent.length, 1, "should steer instead of finalizing");
		assert.match(sent[0], /Current durability status:/);
		assert.match(sent[0], /UAT missing/);
		assert.match(sent[0], /roadmap checkbox not marked \[x\]/);
		assert.ok(
			notifications.some((n) => n.includes("to repair")),
			"notification should explain the exact repair target",
		);
		assert.ok(
			notifications.every((n) => !n.includes("durable state already verified")),
			"summary-only state must not be treated as recovered",
		);
		const runtimeRecord = readFileSync(
			join(base, ".sf", "runtime", "units", "complete-slice-M001-S01.json"),
			"utf-8",
		);
		assert.match(runtimeRecord, /"phase": "recovered"/);
	} finally {
		rmSync(base, { recursive: true, force: true });
	}
});
