import { mkdtempSync, readFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, test } from "vitest";
import {
	appendAutonomousSolverCheckpoint,
	appendAutonomousSolverSteering,
	assessAutonomousSolverTurn,
	beginAutonomousSolverIteration,
	buildAutonomousSolverMissingCheckpointRepairPrompt,
	buildAutonomousSolverPromptBlock,
	classifyAutonomousSolverMissingCheckpointFailure,
	consumePendingAutonomousSolverSteering,
	getConfiguredAutonomousSolverMaxIterations,
	readLatestAutonomousSolverCheckpoint,
	recordAutonomousSolverMissingCheckpointRetry,
} from "../autonomous-solver.js";

let tempDirs = [];

function makeProject() {
	const dir = mkdtempSync(join(tmpdir(), "sf-autonomous-solver-"));
	tempDirs.push(dir);
	return dir;
}

afterEach(() => {
	for (const dir of tempDirs) {
		rmSync(dir, { recursive: true, force: true });
	}
	tempDirs = [];
});

function pdd(overrides = {}) {
	return {
		purpose: "Protect autonomous execution from vague hidden retries.",
		consumer: "/autonomous unit executor.",
		contract:
			"Checkpoint contains outcome, progress, evidence, and remaining work.",
		failureBoundary:
			"Blocked or decide outcomes pause instead of continuing blind.",
		evidence: "Projection and JSONL history are written.",
		nonGoals: "Does not replace the normal task completion tool.",
		invariants: "Each checkpoint is tied to one unit id.",
		assumptions: "Filesystem writes are available under .sf/runtime.",
		...overrides,
	};
}

describe("autonomous solver", () => {
	test("beginAutonomousSolverIteration_same_unit_advances_iteration", () => {
		const project = makeProject();
		const first = beginAutonomousSolverIteration(
			project,
			"execute-task",
			"M001/S01/T01",
		);
		const second = beginAutonomousSolverIteration(
			project,
			"execute-task",
			"M001/S01/T01",
		);

		expect(first.iteration).toBe(1);
		expect(second.iteration).toBe(2);
	});

	test("beginAutonomousSolverIteration_new_unit_resets_iteration", () => {
		const project = makeProject();
		beginAutonomousSolverIteration(project, "execute-task", "M001/S01/T01");
		const next = beginAutonomousSolverIteration(
			project,
			"execute-task",
			"M001/S01/T02",
		);

		expect(next.iteration).toBe(1);
	});

	test("appendAutonomousSolverCheckpoint_writes_pdd_projection_and_history", () => {
		const project = makeProject();
		beginAutonomousSolverIteration(project, "execute-task", "M001/S01/T01");

		appendAutonomousSolverCheckpoint(project, {
			unitType: "execute-task",
			unitId: "M001/S01/T01",
			outcome: "continue",
			summary: "Implemented the first bounded repair.",
			completedItems: ["Read the failing path", "Added regression test"],
			remainingItems: ["Finish implementation", "Run full verification"],
			verificationEvidence: ["npm run test:unit -- autonomous-solver"],
			pdd: pdd(),
		});

		const latest = readLatestAutonomousSolverCheckpoint(
			project,
			"execute-task",
			"M001/S01/T01",
		);
		const projection = readFileSync(
			join(project, ".sf/runtime/autonomous-solver/LOOP.md"),
			"utf-8",
		);
		const history = readFileSync(
			join(project, ".sf/runtime/autonomous-solver/iterations.jsonl"),
			"utf-8",
		);

		expect(latest.outcome).toBe("continue");
		expect(projection).toContain("## PDD Contract");
		expect(projection).toContain("Purpose: Protect autonomous execution");
		expect(projection).toContain("Finish implementation");
		expect(JSON.parse(history.trim()).outcome).toBe("continue");
	});

	test("buildAutonomousSolverPromptBlock_names_pdd_and_checkpoint_outcomes", () => {
		const prompt = buildAutonomousSolverPromptBlock({
			unitType: "execute-task",
			unitId: "M001/S01/T01",
			iteration: 3,
			maxIterations: 12,
		});

		expect(prompt).toContain("/autonomous iteration 3 of 12");
		expect(prompt).toContain("sf_autonomous_checkpoint");
		expect(prompt).toContain("Writing SUMMARY.md");
		expect(prompt).toContain("is not a checkpoint");
		expect(prompt).toContain("final autonomous action");
		expect(prompt).toContain("Purpose:");
		expect(prompt).toContain("Consumer:");
		expect(prompt).toContain("Failure boundary:");
		expect(prompt).toContain('outcome: "decide"');
	});

	test("buildAutonomousSolverMissingCheckpointRepairPrompt_rejects_file_substitutes", () => {
		const prompt = buildAutonomousSolverMissingCheckpointRepairPrompt(
			{ iteration: 2 },
			"research-slice",
			"M012/parallel-research",
		);

		expect(prompt).toContain("actual sf_autonomous_checkpoint tool");
		expect(prompt).toContain("Do not write a summary file as a substitute");
		expect(prompt).toContain("tool call succeeds");
		expect(prompt).toContain("final action");
	});

	test("buildAutonomousSolverMissingCheckpointRepairPrompt_escalates_to_confidence_gated_decide", () => {
		const prompt = buildAutonomousSolverMissingCheckpointRepairPrompt(
			{ iteration: 2 },
			"research-slice",
			"M012/parallel-research",
			{ classification: "file-substituted-for-checkpoint", summary: "summary" },
			3,
			4,
		);

		expect(prompt).toContain("Repair attempt: 3 of 4");
		expect(prompt).toContain("confidence");
		expect(prompt).toContain("0.98");
		expect(prompt).toContain('outcome="decide"');
		expect(prompt).toContain("decisionQuestion");
	});

	test("assessAutonomousSolverTurn_missing_checkpoint_escalates_repairs_then_pauses", () => {
		const project = makeProject();
		beginAutonomousSolverIteration(project, "execute-task", "M001/S01/T01");

		for (let attempt = 1; attempt <= 4; attempt++) {
			const assessment = assessAutonomousSolverTurn(
				project,
				"execute-task",
				"M001/S01/T01",
			);
			expect(assessment.action).toBe("missing-checkpoint-retry");
			expect(assessment.repairAttempt).toBe(attempt);
			recordAutonomousSolverMissingCheckpointRetry(
				project,
				"execute-task",
				"M001/S01/T01",
			);
		}

		const final = assessAutonomousSolverTurn(
			project,
			"execute-task",
			"M001/S01/T01",
		);
		expect(final.action).toBe("pause");
		expect(final.reason).toBe("solver-missing-checkpoint");
		expect(final.repairAttempts).toBe(4);
	});

	test("classifyAutonomousSolverMissingCheckpointFailure_detects_summary_substitute", () => {
		const diagnosis = classifyAutonomousSolverMissingCheckpointFailure([
			{
				role: "assistant",
				content:
					"The checkpoint is now saved to .sf/milestones/M012/slices/parallel-research/tasks/T01-SUMMARY.md",
			},
		]);

		expect(diagnosis.classification).toBe("file-substituted-for-checkpoint");
		expect(diagnosis.summary).toContain("summary");
	});

	test("assessAutonomousSolverTurn_continue_and_blocked_are_authoritative", () => {
		const project = makeProject();
		beginAutonomousSolverIteration(project, "execute-task", "M001/S01/T01");
		appendAutonomousSolverCheckpoint(project, {
			unitType: "execute-task",
			unitId: "M001/S01/T01",
			outcome: "continue",
			summary: "More work remains.",
			completedItems: ["First pass"],
			remainingItems: ["Second pass"],
			verificationEvidence: ["npx vitest run focused.test.mjs"],
			pdd: pdd(),
		});
		expect(
			assessAutonomousSolverTurn(project, "execute-task", "M001/S01/T01")
				.action,
		).toBe("continue");

		beginAutonomousSolverIteration(project, "execute-task", "M001/S01/T01");
		appendAutonomousSolverCheckpoint(project, {
			unitType: "execute-task",
			unitId: "M001/S01/T01",
			outcome: "blocked",
			summary: "Credentials unavailable.",
			completedItems: [],
			remainingItems: ["Wait for credentials"],
			verificationEvidence: ["provider returned 401"],
			blockerReason: "Missing provider token.",
			pdd: pdd(),
		});
		const blocked = assessAutonomousSolverTurn(
			project,
			"execute-task",
			"M001/S01/T01",
		);
		expect(blocked.action).toBe("pause");
		expect(blocked.reason).toBe("solver-blocked");
	});

	test("assessAutonomousSolverTurn_max_iterations_pauses_before_unbounded_retry", () => {
		const project = makeProject();
		beginAutonomousSolverIteration(project, "execute-task", "M001/S01/T01", {
			maxIterations: 1,
		});
		appendAutonomousSolverCheckpoint(project, {
			unitType: "execute-task",
			unitId: "M001/S01/T01",
			outcome: "continue",
			summary: "More work remains.",
			completedItems: ["First pass"],
			remainingItems: ["Second pass"],
			verificationEvidence: ["npx vitest run focused.test.mjs"],
			pdd: pdd(),
		});

		const result = assessAutonomousSolverTurn(
			project,
			"execute-task",
			"M001/S01/T01",
		);
		expect(result.action).toBe("pause");
		expect(result.reason).toBe("solver-max-iterations");
	});

	test("steering_append_consume_is_idempotent", () => {
		const project = makeProject();
		appendAutonomousSolverSteering(project, "Prefer runtime enforcement.");
		appendAutonomousSolverSteering(project, "Keep /autonomous only.");

		const first = consumePendingAutonomousSolverSteering(project);
		const second = consumePendingAutonomousSolverSteering(project);

		expect(first).toHaveLength(2);
		expect(first[0].text).toBe("Prefer runtime enforcement.");
		expect(second).toHaveLength(0);
	});

	test("buildAutonomousSolverMissingCheckpointRepairPrompt_no_transcript_includes_filesystem_guidance", () => {
		const prompt = buildAutonomousSolverMissingCheckpointRepairPrompt(
			{ iteration: 2 },
			"research-slice",
			"M012/parallel-research",
			{
				classification: "no-transcript",
				summary: "No agent-end transcript was available to classify.",
			},
			2,
			4,
		);

		expect(prompt).toContain("no-transcript");
		expect(prompt).toContain("No transcript was captured");
		expect(prompt).toContain(".sf/runtime/autonomous-solver/LOOP.md");
		expect(prompt).toContain("SUMMARY.md");
		expect(prompt).toContain("outcome='decide'");
	});

	test("getConfiguredAutonomousSolverMaxIterations_clamps_preference", () => {
		expect(getConfiguredAutonomousSolverMaxIterations()).toBe(30000);
		expect(
			getConfiguredAutonomousSolverMaxIterations({
				auto_supervisor: { solver_max_iterations: 0 },
			}),
		).toBe(1);
		expect(
			getConfiguredAutonomousSolverMaxIterations({
				auto_supervisor: { solver_max_iterations: 150000 },
			}),
		).toBe(100000);
	});
});
