/**
 * detector-artifact-flap.test.mjs — artifact flap detector contracts.
 *
 * Purpose: prove R054 catches two-hash oscillation without treating normal
 * multi-hash artifact evolution as stuck.
 */
import assert from "node:assert/strict";
import { test } from "vitest";
import {
	MIN_FLAPS,
	WINDOW_MS,
	detectArtifactFlap,
} from "../detectors/artifact-flap.js";

const NOW = Date.parse("2026-05-17T12:00:00.000Z");

function artifact(overrides = {}) {
	return {
		path: "docs/specs/runtime.md",
		hash: "a",
		timestamp: NOW - 5 * 60 * 1000,
		...overrides,
	};
}

test("detectArtifactFlap_when_two_hashes_transition_at_threshold_returns_stuck", () => {
	const result = detectArtifactFlap(
		[
			artifact({ hash: "a", timestamp: NOW - 20 * 60 * 1000 }),
			artifact({ hash: "b", timestamp: NOW - 15 * 60 * 1000 }),
			artifact({ hash: "a", timestamp: NOW - 10 * 60 * 1000 }),
			artifact({ hash: "b", timestamp: NOW - 5 * 60 * 1000 }),
		],
		{ now: NOW },
	);

	assert.equal(result.stuck, true);
	assert.equal(result.reason, "artifact-flap");
	assert.deepEqual(result.signature, {
		path: "docs/specs/runtime.md",
		flaps: MIN_FLAPS,
		uniqueHashes: 2,
		windowMs: WINDOW_MS,
	});
});

test("detectArtifactFlap_when_transitions_are_below_threshold_returns_not_stuck", () => {
	const result = detectArtifactFlap(
		[
			artifact({ hash: "a", timestamp: NOW - 15 * 60 * 1000 }),
			artifact({ hash: "b", timestamp: NOW - 10 * 60 * 1000 }),
			artifact({ hash: "a", timestamp: NOW - 5 * 60 * 1000 }),
		],
		{ now: NOW },
	);

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

test("detectArtifactFlap_when_more_than_two_hashes_change_returns_not_stuck", () => {
	const result = detectArtifactFlap(
		[
			artifact({ hash: "a", timestamp: NOW - 20 * 60 * 1000 }),
			artifact({ hash: "b", timestamp: NOW - 15 * 60 * 1000 }),
			artifact({ hash: "c", timestamp: NOW - 10 * 60 * 1000 }),
			artifact({ hash: "a", timestamp: NOW - 5 * 60 * 1000 }),
		],
		{ now: NOW },
	);

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

test("detectArtifactFlap_when_multiple_artifacts_flap_returns_first_flapping_path", () => {
	const result = detectArtifactFlap(
		[
			artifact({ path: "b.md", hash: "a", timestamp: NOW - 20 * 60 * 1000 }),
			artifact({ path: "a.md", hash: "x", timestamp: NOW - 20 * 60 * 1000 }),
			artifact({ path: "b.md", hash: "b", timestamp: NOW - 15 * 60 * 1000 }),
			artifact({ path: "a.md", hash: "y", timestamp: NOW - 15 * 60 * 1000 }),
			artifact({ path: "b.md", hash: "a", timestamp: NOW - 10 * 60 * 1000 }),
			artifact({ path: "a.md", hash: "x", timestamp: NOW - 10 * 60 * 1000 }),
			artifact({ path: "b.md", hash: "b", timestamp: NOW - 5 * 60 * 1000 }),
			artifact({ path: "a.md", hash: "y", timestamp: NOW - 5 * 60 * 1000 }),
		],
		{ now: NOW },
	);

	assert.equal(result.stuck, true);
	assert.equal(result.signature.path, "b.md");
});
