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 { afterEach, test } from "vitest";

/**
 * Regression test for SF build #4787.
 *
 * Background: `computeResourceFingerprint` previously hashed the relative
 * file path + file size only. Same-byte-length edits to bundled prompt
 * templates (e.g. the #4570 retry-cap fix to parallel-research-slices.md)
 * slipped through the fingerprint gate in `initResources`, so existing
 * installs silently kept serving the stale cached copy from
 * the installed resource cache.
 *
 * The fix hashes file CONTENTS (sha256) instead of just size — any edit,
 * regardless of length, produces a different fingerprint and triggers a
 * resync on next launch.
 */

test("computeResourceFingerprint detects same-size content edits (#4787)", async (_t) => {
	const { computeResourceFingerprint } = await import("../resource-loader.ts");

	const tmp = mkdtempSync(join(tmpdir(), "sf-fingerprint-content-"));
	afterEach(() => {
		rmSync(tmp, { recursive: true, force: true });
	});

	const dirA = join(tmp, "bundled-a");
	const dirB = join(tmp, "bundled-b");
	mkdirSync(join(dirA, "prompts"), { recursive: true });
	mkdirSync(join(dirB, "prompts"), { recursive: true });

	// Same byte length (32 bytes each), different content — mirrors the
	// real-world #4787 scenario where a hotfix edit keeps the file size
	// stable but changes load-bearing instructions.
	const contentA = "retry subagent once then BLOCKER"; // 32 bytes
	const contentB = "retry subagent forever never stp"; // 32 bytes
	assert.equal(Buffer.byteLength(contentA), Buffer.byteLength(contentB));

	writeFileSync(join(dirA, "prompts", "foo.md"), contentA);
	writeFileSync(join(dirB, "prompts", "foo.md"), contentB);

	const hashA = computeResourceFingerprint(dirA);
	const hashB = computeResourceFingerprint(dirB);

	assert.notEqual(
		hashA,
		hashB,
		"same-size, different-content trees must yield different fingerprints",
	);
});

test("syncResourceDir overwrites same-size stale content on refresh (#4787)", async (_t) => {
	const { syncResourceDir } = await import("../resource-loader.ts");

	const tmp = mkdtempSync(join(tmpdir(), "sf-sync-samesize-"));
	afterEach(() => {
		rmSync(tmp, { recursive: true, force: true });
	});

	const bundled = join(tmp, "bundled", "prompts");
	const installed = join(tmp, "installed", "prompts");
	mkdirSync(bundled, { recursive: true });
	mkdirSync(installed, { recursive: true });

	// Bundled (new): the post-#4570 fix template
	const newContent = "retry subagent once then BLOCKER";
	// Installed (stale): pre-#4570 template with the same byte length
	const staleContent = "retry subagent forever never stp";
	assert.equal(Buffer.byteLength(newContent), Buffer.byteLength(staleContent));

	writeFileSync(join(bundled, "parallel-research-slices.md"), newContent);
	writeFileSync(join(installed, "parallel-research-slices.md"), staleContent);

	// syncResourceDir always force-copies; this guards that the copy path
	// itself overwrites regardless of size.
	syncResourceDir(join(tmp, "bundled"), join(tmp, "installed"));

	const actual = readFileSync(
		join(installed, "parallel-research-slices.md"),
		"utf-8",
	);
	assert.equal(
		actual,
		newContent,
		"installed prompt must be overwritten with bundled content even when sizes match",
	);
});
