import assert from "node:assert/strict";
import { test, vi } from "vitest";
import {
	detectZeroProgress,
	MIN_CALLS_FOR_DETECTION,
	STAGNATION_THRESHOLD,
	TOOL_GROWTH,
} from "../detectors/zero-progress.js";
import {
	evaluateRunawayGuard,
	resetRunawayGuardState,
} from "../uok/auto-runaway-guard.js";

function makeConfig(overrides = {}) {
	return {
		enabled: true,
		toolCallWarning: 60,
		tokenWarning: 1_000_000,
		elapsedMs: 20 * 60 * 1000,
		changedFilesWarning: 75,
		diagnosticTurns: 2,
		hardPause: true,
		minIntervalMs: 120_000,
		...overrides,
	};
}

test("detectZeroProgress_when_tool_calls_grow_without_fingerprint_or_db_progress_returns_stuck_and_guard_fails", async () => {
	const collectWorktreeFingerprint = vi.fn(() => "same-fingerprint");

	const result = await detectZeroProgress(
		{
			tool_calls: 20,
			elapsedMs: 30_000,
			iterationsSinceProgress: 3,
		},
		{
			tool_calls: 10,
			elapsedMs: 5_000,
			fingerprint: "same-fingerprint",
		},
		{ collectWorktreeFingerprint, cwd: "/unused" },
	);

	assert.equal(result.stuck, true);
	assert.equal(result.reason, "zero-progress");
	assert.equal(result.signature.toolCallsSinceLastChange, 10);
	assert.equal(result.signature.elapsedMs, 25_000);
	assert.equal(result.signature.fingerprint, "same-fingerprint");
	assert.equal(collectWorktreeFingerprint.mock.calls.length, 1);

	resetRunawayGuardState("execute-task", "M038/S02/R052", {
		sessionTokens: 0,
		changedFiles: 0,
		worktreeFingerprint: "same-fingerprint",
	});
	const decision = evaluateRunawayGuard(
		"execute-task",
		"M038/S02/R052",
		{
			toolCalls: 20,
			sessionTokens: 0,
			elapsedMs: 30_000,
			changedFiles: 0,
			worktreeFingerprint: "same-fingerprint",
			iterationsSinceProgress: 3,
			topTools: {},
		},
		makeConfig(),
	);

	assert.equal(decision.action, "fail");
	assert.equal(decision.reason, "zero-progress");
	assert.equal(decision.metadata.selfFeedback.kind, "zero-progress");
	assert.equal(decision.metadata.selfFeedback.severity, "high");
	assert.equal(decision.metadata.evidence.toolCallsTotal, 20);
	assert.equal(decision.metadata.evidence.fingerprint, "same-fingerprint");
});

test("detectZeroProgress_when_fingerprint_changed_after_tool_growth_returns_not_stuck", async () => {
	const collectWorktreeFingerprint = vi.fn(() => "changed-at-call-15");

	const result = await detectZeroProgress(
		{
			tool_calls: 20,
			elapsedMs: 30_000,
			iterationsSinceProgress: 3,
		},
		{
			tool_calls: 10,
			elapsedMs: 5_000,
			fingerprint: "same-fingerprint",
		},
		{ collectWorktreeFingerprint, cwd: "/unused" },
	);

	assert.equal(result.stuck, false);
	assert.equal(result.reason, "");
	assert.deepEqual(result.signature, {});
});

test("detectZeroProgress_when_below_minimum_call_threshold_returns_not_stuck", async () => {
	const collectWorktreeFingerprint = vi.fn(() => "same-fingerprint");

	const result = await detectZeroProgress(
		{
			tool_calls: 5,
			elapsedMs: 30_000,
			iterationsSinceProgress: 3,
		},
		{
			tool_calls: 0,
			elapsedMs: 5_000,
			fingerprint: "same-fingerprint",
		},
		{ collectWorktreeFingerprint, cwd: "/unused" },
	);

	assert.equal(result.stuck, false);
	assert.equal(5 < MIN_CALLS_FOR_DETECTION, true);
	assert.equal(TOOL_GROWTH, 5);
	assert.equal(STAGNATION_THRESHOLD, 2);
});

test("detectZeroProgress_when_fingerprint_changed_and_iterations_reset_returns_not_stuck", async () => {
	const collectWorktreeFingerprint = vi.fn(() => "changed-fingerprint");

	const result = await detectZeroProgress(
		{
			tool_calls: 50,
			elapsedMs: 60_000,
			iterationsSinceProgress: 0,
		},
		{
			tool_calls: 10,
			elapsedMs: 5_000,
			fingerprint: "old-fingerprint",
		},
		{ collectWorktreeFingerprint, cwd: "/unused" },
	);

	assert.equal(result.stuck, false);
	assert.deepEqual(result.signature, {});
});
