/**
 * sift-retrieval-evidence.test.mjs — Sift retrieval provenance coverage.
 *
 * Purpose: prove live code searches record DB evidence with backend, scope,
 * freshness, and result metadata so context tools can be audited consistently.
 */
import assert from "node:assert/strict";
import {
	chmodSync,
	mkdirSync,
	mkdtempSync,
	rmSync,
	writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, test } from "vitest";
import { registerQueryTools } from "../bootstrap/query-tools.js";
import { closeDatabase, getRetrievalEvidence, openDatabase } from "../sf-db.js";
import registerSubagentExtension from "../subagent/index.js";
import { registerSiftSearchTool } from "../tools/sift-search-tool.js";

const tmpRoots = [];
const originalCwd = process.cwd();
const originalSiftPath = process.env.SIFT_PATH;

afterEach(() => {
	process.chdir(originalCwd);
	closeDatabase();
	if (originalSiftPath === undefined) {
		delete process.env.SIFT_PATH;
	} else {
		process.env.SIFT_PATH = originalSiftPath;
	}
	for (const root of tmpRoots.splice(0)) {
		rmSync(root, { recursive: true, force: true });
	}
});

function makeProject() {
	const root = mkdtempSync(join(tmpdir(), "sf-sift-evidence-"));
	tmpRoots.push(root);
	mkdirSync(join(root, ".sf"), { recursive: true });
	mkdirSync(join(root, "src"), { recursive: true });
	writeFileSync(join(root, "src", "index.js"), "export const value = 1;\n");
	return root;
}

function makeFakeSift() {
	const dir = mkdtempSync(join(tmpdir(), "sf-sift-bin-"));
	tmpRoots.push(dir);
	const bin = join(dir, "sift");
	writeFileSync(
		bin,
		`#!/bin/sh
printf '{"query":"approval policy","strategy":"bm25","hits":[{"path":"src/index.js","score":0.91,"content":"approval policy contract","line_start":1,"line_end":1}]}'`,
	);
	chmodSync(bin, 0o755);
	return bin;
}

function captureTool() {
	let tool = null;
	registerSiftSearchTool({
		registerTool(definition) {
			tool = definition;
		},
	});
	assert.ok(tool, "sift_search tool should register");
	return tool;
}

function captureQueryTool(name) {
	const tools = new Map();
	registerQueryTools({
		registerTool(definition) {
			tools.set(definition.name, definition);
		},
	});
	const tool = tools.get(name);
	assert.ok(tool, `${name} tool should register`);
	return tool;
}

function captureSubagentTool(name) {
	const tools = new Map();
	registerSubagentExtension({
		on() {},
		registerCommand() {},
		registerTool(definition) {
			tools.set(definition.name, definition);
		},
	});
	const tool = tools.get(name);
	assert.ok(tool, `${name} tool should register`);
	return tool;
}

test("sift_search_when_successful_records_retrieval_evidence", async () => {
	const project = makeProject();
	process.chdir(project);
	process.env.SIFT_PATH = makeFakeSift();
	assert.equal(openDatabase(join(project, ".sf", "sf.db")), true);

	const tool = captureTool();
	const result = await tool.execute("call-1", {
		query: "approval policy",
		path: "src",
		strategy: "bm25",
		limit: 5,
	});

	assert.equal(result.isError, undefined);
	assert.equal(result.details.hitCount, 1);
	const rows = getRetrievalEvidence(5);
	assert.equal(rows.length, 1);
	assert.equal(rows[0].backend, "sift");
	assert.equal(rows[0].sourceKind, "code");
	assert.equal(rows[0].query, "approval policy");
	assert.equal(rows[0].strategy, "bm25");
	assert.equal(rows[0].scope, "src");
	assert.equal(rows[0].status, "ok");
	assert.equal(rows[0].hitCount, 1);
	assert.equal(rows[0].projectRoot, project);
	assert.match(rows[0].cachePath, /\.sf\/runtime\/sift\/search-cache$/);
	assert.deepEqual(rows[0].result.hits, [
		{
			path: "src/index.js",
			score: 0.91,
			lineStart: 1,
			lineEnd: 1,
		},
	]);

	const queryTool = captureQueryTool("search_evidence");
	const queryResult = await queryTool.execute("call-2", { limit: 1 });
	assert.match(queryResult.content[0].text, /Retrieval evidence: 1 row/);
	assert.equal(queryResult.details.rows[0].backend, "sift");
	assert.equal(queryResult.details.rows[0].query, "approval policy");
});

test("codebase_search_when_successful_records_retrieval_evidence", async () => {
	const project = makeProject();
	process.chdir(project);
	process.env.SIFT_PATH = makeFakeSift();
	assert.equal(openDatabase(join(project, ".sf", "sf.db")), true);

	const tool = captureSubagentTool("codebase_search");
	const result = await tool.execute("call-1", {
		query: "approval policy",
		scope: "src",
		strategy: "path-hybrid",
		timeoutMs: 10_000,
	});

	assert.equal(result.details.operation, "codebase_search");
	assert.equal(result.details.exitCode, 0);
	const rows = getRetrievalEvidence(5);
	assert.equal(rows.length, 1);
	assert.equal(rows[0].backend, "codebase_search");
	assert.equal(rows[0].sourceKind, "code");
	assert.equal(rows[0].query, "approval policy");
	assert.equal(rows[0].strategy, "path-hybrid");
	assert.equal(rows[0].scope, "src");
	assert.equal(rows[0].status, "ok");
	assert.equal(rows[0].hitCount, 1);
	assert.match(rows[0].result.outputPreview, /approval policy contract/);
});
