/**
 * Requirements-aware milestone completion (sf-mp74hftw-zud6ba).
 *
 * When every owning requirement for the active milestone is closed,
 * deriveState should route to validating-milestone instead of
 * pre-planning, regardless of slice state. This prevents:
 *
 *   - re-decomposition of milestones whose work was tracked at the
 *     requirements layer rather than via slices
 *   - the autonomous loop burning LLM turns planning a milestone that
 *     REQUIREMENTS.md says is already done
 *
 * Verified end-to-end against dr-repo M003 (8 owning requirements
 * all complete; previously re-planned, now validates).
 */

import assert from "node:assert/strict";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, test } from "vitest";
import {
	closeDatabase,
	insertMilestone,
	insertSlice,
	isDbAvailable,
	openDatabase,
} from "../sf-db.js";
import { deriveState, invalidateStateCache } from "../state.js";

const tmpDirs = [];

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

function makeProject(milestoneId, reqsContent) {
	const dir = mkdtempSync(join(tmpdir(), "sf-state-reqs-"));
	tmpDirs.push(dir);
	mkdirSync(join(dir, ".sf", "milestones", milestoneId, "slices", "S01"), {
		recursive: true,
	});
	writeFileSync(join(dir, ".sf", "REQUIREMENTS.md"), reqsContent);
	openDatabase(join(dir, ".sf", "sf.db"));
	insertMilestone({
		id: milestoneId,
		title: "Requirements-aware milestone",
		status: "active",
	});
	return dir;
}

const REQS_ALL_COMPLETE = `# Requirements

## Validated

### REQ-01 — first req
- Class: Test
- Status: complete
- Description: x
- Primary owning milestone: M050

### REQ-02 — second req
- Class: Test
- Status: complete
- Description: y
- Primary owning milestone: M050
`;

const REQS_MIXED = `# Requirements

## Validated

### REQ-01 — first req
- Class: Test
- Status: complete
- Description: x
- Primary owning milestone: M051

## Active

### REQ-02 — second req
- Class: Test
- Status: active
- Description: y
- Primary owning milestone: M051
`;

test("requirements_all_complete_and_slice_skipped_when_deriving_state_returns_validating_milestone", async () => {
	const dir = makeProject("M050", REQS_ALL_COMPLETE);
	insertSlice({
		milestoneId: "M050",
		id: "S01",
		title: "Migration placeholder",
		status: "skipped",
		sequence: 1,
	});

	const state = await deriveState(dir);

	assert.equal(state.activeMilestone?.id, "M050");
	assert.equal(state.phase, "validating-milestone");
	assert.match(
		state.nextAction,
		/All 2 requirement\(s\) owned by M050 are marked complete/,
	);
	assert.match(state.nextAction, /Run validate-milestone/);
});

test("some_requirements_still_active_when_deriving_state_returns_pre_planning", async () => {
	const dir = makeProject("M051", REQS_MIXED);
	insertSlice({
		milestoneId: "M051",
		id: "S01",
		title: "Migration placeholder",
		status: "skipped",
		sequence: 1,
	});

	const state = await deriveState(dir);

	assert.equal(state.phase, "pre-planning");
});

test("zero_owning_requirements_when_deriving_state_uses_slice_based_check", async () => {
	const reqsNoOwning = `# Requirements
## Validated
### REQ-99 — unrelated
- Class: Other
- Status: complete
- Description: not owned by M052
- Primary owning milestone: M999
`;
	const dir = makeProject("M052", reqsNoOwning);
	insertSlice({
		milestoneId: "M052",
		id: "S01",
		title: "Placeholder",
		status: "skipped",
		sequence: 1,
	});

	const state = await deriveState(dir);

	// No owning requirements means the slice-based check still owns routing.
	// check fires, routes to pre-planning per the existing skipped rule.
	assert.equal(state.phase, "pre-planning");
});

test("missing_requirements_md_when_deriving_state_uses_slice_based_check", async () => {
	const dir = mkdtempSync(join(tmpdir(), "sf-state-reqs-"));
	tmpDirs.push(dir);
	mkdirSync(join(dir, ".sf", "milestones", "M053", "slices", "S01"), {
		recursive: true,
	});
	openDatabase(join(dir, ".sf", "sf.db"));
	insertMilestone({
		id: "M053",
		title: "No requirements file",
		status: "active",
	});
	insertSlice({
		milestoneId: "M053",
		id: "S01",
		title: "Placeholder",
		status: "skipped",
		sequence: 1,
	});

	const state = await deriveState(dir);

	// No REQUIREMENTS.md means the slice-based rule still applies.
	assert.equal(state.phase, "pre-planning");
});

test("requirements_complete_and_real_slice_work_when_deriving_state_returns_validating_milestone", async () => {
	// Even when a real slice exists, all-reqs-complete short-circuits
	// (this path doesn't get to the slice-real-work check).
	const dir = makeProject("M054", REQS_ALL_COMPLETE.replace(/M050/g, "M054"));
	insertSlice({
		milestoneId: "M054",
		id: "S01",
		title: "Real",
		status: "complete",
		sequence: 1,
	});

	const state = await deriveState(dir);

	assert.equal(state.phase, "validating-milestone");
});
