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 { test } from 'vitest';

import {
	buildCodeIntelligenceContextBlock,
	detectProjectRag,
	detectSift,
	ensureProjectRagMcpConfig,
	ensureSiftIndexWarmup,
	findProjectRagSourceDir,
	formatProjectRagStatus,
	formatSiftStatus,
	PROJECT_RAG_MCP_SERVER_NAME,
	resolveEffectiveCodebaseIndexerBackendName,
	resolveProjectRagBinaryForProject,
	resolveProjectRagBuildJobs,
	resolveSiftBinary,
} from "../code-intelligence.ts";

function makeProject(): string {
	const projectRoot = mkdtempSync(join(tmpdir(), "sf-code-intel-"));
	mkdirSync(join(projectRoot, ".sf"), { recursive: true });
	return projectRoot;
}

function cleanup(projectRoot: string): void {
	rmSync(projectRoot, { recursive: true, force: true });
}

function writeFakeSiftBinary(projectRoot: string): string {
	const binDir = join(projectRoot, "bin");
	const binaryPath = join(
		binDir,
		process.platform === "win32" ? "sift.exe" : "sift",
	);
	mkdirSync(binDir, { recursive: true });
	writeFileSync(binaryPath, "", "utf-8");
	return binaryPath;
}

test("detectProjectRag finds a project-rag server even when the MCP name is generic", () => {
	const projectRoot = makeProject();
	try {
		writeFileSync(
			join(projectRoot, ".mcp.json"),
			`${JSON.stringify(
				{
					mcpServers: {
						project: {
							command: "/opt/project-rag/target/release/project-rag",
						},
					},
				},
				null,
				2,
			)}\n`,
			"utf-8",
		);

		const detection = detectProjectRag(projectRoot);
		assert.equal(detection.status, "configured");
		assert.equal(detection.serverName, "project");
		assert.match(detection.command ?? "", /project-rag/);
	} finally {
		cleanup(projectRoot);
	}
});

test("detectProjectRag honors explicit server preference and disabled mode", () => {
	const projectRoot = makeProject();
	try {
		writeFileSync(
			join(projectRoot, ".mcp.json"),
			`${JSON.stringify(
				{
					mcpServers: {
						code: {
							command: "/bin/project-rag",
						},
					},
				},
				null,
				2,
			)}\n`,
			"utf-8",
		);

		assert.equal(
			detectProjectRag(projectRoot, { project_rag_server: "code" }).serverName,
			"code",
		);
		assert.equal(
			detectProjectRag(projectRoot, { project_rag: "off" }).status,
			"disabled",
		);
	} finally {
		cleanup(projectRoot);
	}
});

test("ensureProjectRagMcpConfig preserves existing servers and writes project-rag", () => {
	const projectRoot = makeProject();
	try {
		writeFileSync(
			join(projectRoot, ".mcp.json"),
			`${JSON.stringify(
				{
					mcpServers: {
						existing: {
							command: "npx",
							args: ["other-mcp"],
						},
					},
				},
				null,
				2,
			)}\n`,
			"utf-8",
		);

		const result = ensureProjectRagMcpConfig(projectRoot, {
			...process.env,
			SF_PROJECT_RAG_BIN: "/tmp/project-rag",
			RUST_LOG: "warn",
		});
		assert.equal(result.status, "updated");
		assert.equal(result.serverName, PROJECT_RAG_MCP_SERVER_NAME);

		const parsed = JSON.parse(readFileSync(result.configPath, "utf-8")) as {
			mcpServers?: Record<
				string,
				{ command?: string; args?: string[]; env?: Record<string, string> }
			>;
		};
		assert.deepEqual(parsed.mcpServers?.existing, {
			command: "npx",
			args: ["other-mcp"],
		});
		assert.equal(
			parsed.mcpServers?.[PROJECT_RAG_MCP_SERVER_NAME]?.command,
			"/tmp/project-rag",
		);
		assert.equal(
			parsed.mcpServers?.[PROJECT_RAG_MCP_SERVER_NAME]?.env?.RUST_LOG,
			"warn",
		);
	} finally {
		cleanup(projectRoot);
	}
});

test("resolveProjectRagBinaryForProject finds a vendored release binary", () => {
	const projectRoot = makeProject();
	try {
		const sourceDir = join(projectRoot, "vendor", "project-rag");
		const binaryPath = join(
			sourceDir,
			"target",
			"release",
			process.platform === "win32" ? "project-rag.exe" : "project-rag",
		);
		mkdirSync(join(sourceDir, "target", "release"), { recursive: true });
		writeFileSync(
			join(sourceDir, "Cargo.toml"),
			'[package]\nname = "project-rag"\n',
			"utf-8",
		);
		writeFileSync(binaryPath, "", "utf-8");

		assert.equal(findProjectRagSourceDir(projectRoot), sourceDir);
		assert.equal(
			resolveProjectRagBinaryForProject(projectRoot, { PATH: "" }),
			binaryPath,
		);
	} finally {
		cleanup(projectRoot);
	}
});

test("ensureProjectRagMcpConfig uses vendored release binary when available", () => {
	const projectRoot = makeProject();
	try {
		const sourceDir = join(projectRoot, "vendor", "project-rag");
		const binaryPath = join(
			sourceDir,
			"target",
			"release",
			process.platform === "win32" ? "project-rag.exe" : "project-rag",
		);
		mkdirSync(join(sourceDir, "target", "release"), { recursive: true });
		writeFileSync(
			join(sourceDir, "Cargo.toml"),
			'[package]\nname = "project-rag"\n',
			"utf-8",
		);
		writeFileSync(binaryPath, "", "utf-8");

		const result = ensureProjectRagMcpConfig(projectRoot, { PATH: "" });
		const parsed = JSON.parse(readFileSync(result.configPath, "utf-8")) as {
			mcpServers?: Record<string, { command?: string }>;
		};
		assert.equal(
			parsed.mcpServers?.[PROJECT_RAG_MCP_SERVER_NAME]?.command,
			binaryPath,
		);
	} finally {
		cleanup(projectRoot);
	}
});

test("resolveProjectRagBuildJobs caps project-rag builds by default and supports overrides", () => {
	assert.equal(resolveProjectRagBuildJobs({}), "2");
	assert.equal(resolveProjectRagBuildJobs({ CARGO_BUILD_JOBS: "4" }), "4");
	assert.equal(
		resolveProjectRagBuildJobs({
			CARGO_BUILD_JOBS: "4",
			SF_PROJECT_RAG_BUILD_JOBS: "1",
		}),
		"1",
	);
	assert.equal(
		resolveProjectRagBuildJobs({ SF_PROJECT_RAG_BUILD_JOBS: "nope" }),
		"2",
	);
});

test("formatProjectRagStatus reports source-only project-rag as not operational", () => {
	const projectRoot = makeProject();
	try {
		const sourceDir = join(projectRoot, "vendor", "project-rag");
		mkdirSync(sourceDir, { recursive: true });
		writeFileSync(
			join(sourceDir, "Cargo.toml"),
			'[package]\nname = "project-rag"\n',
			"utf-8",
		);

		const status = formatProjectRagStatus(projectRoot);
		assert.match(status, /Source:/);
		assert.match(status, /release binary is missing/i);
		assert.match(status, /\/sf codebase rag build/i);
	} finally {
		cleanup(projectRoot);
	}
});

test("buildCodeIntelligenceContextBlock injects project-rag usage guidance when configured", () => {
	const projectRoot = makeProject();
	try {
		writeFileSync(
			join(projectRoot, ".mcp.json"),
			`${JSON.stringify(
				{
					mcpServers: {
						"project-rag": {
							command: "/tmp/project-rag",
						},
					},
				},
				null,
				2,
			)}\n`,
			"utf-8",
		);

		const block = buildCodeIntelligenceContextBlock(projectRoot, {
			indexer_backend: "projectRag",
		});
		assert.match(block, /PROJECT CODE INTELLIGENCE/);
		assert.match(block, /Project RAG: configured/);
		assert.match(block, /query_codebase/);
		assert.match(block, /search_by_filters/);
		assert.match(block, /If any Project RAG tool is missing or fails/);
	} finally {
		cleanup(projectRoot);
	}
});

test("effective codebase indexer uses project-rag only when explicitly selected", () => {
	const projectRoot = makeProject();
	try {
		writeFakeSiftBinary(projectRoot);
		writeFileSync(
			join(projectRoot, ".mcp.json"),
			`${JSON.stringify(
				{
					mcpServers: {
						"project-rag": {
							command: join(projectRoot, "bin", "sift"),
						},
					},
				},
				null,
				2,
			)}\n`,
			"utf-8",
		);

		assert.equal(
			resolveEffectiveCodebaseIndexerBackendName(
				projectRoot,
				{ indexer_backend: "projectRag" },
				{ PATH: join(projectRoot, "bin") },
			),
			"projectRag",
		);
		assert.equal(
			resolveEffectiveCodebaseIndexerBackendName(
				projectRoot,
				undefined,
				{ PATH: join(projectRoot, "bin") },
			),
			"sift",
		);
	} finally {
		cleanup(projectRoot);
	}
});

test("effective codebase indexer defaults to sift when project-rag is absent", () => {
	const projectRoot = makeProject();
	try {
		writeFakeSiftBinary(projectRoot);

		assert.equal(
			resolveEffectiveCodebaseIndexerBackendName(
				projectRoot,
				undefined,
				{ PATH: join(projectRoot, "bin") },
			),
			"sift",
		);
		assert.match(
			buildCodeIntelligenceContextBlock(projectRoot, undefined, {
				PATH: join(projectRoot, "bin"),
			}),
			/Sift: configured/,
		);
	} finally {
		cleanup(projectRoot);
	}
});

test("ensureSiftIndexWarmup starts page-index-hybrid warmup and writes marker", () => {
	const projectRoot = makeProject();
	try {
		const fakeSift = writeFakeSiftBinary(projectRoot);
		const calls: Array<{ command: string; args: string[]; cwd?: string }> = [];
		const fakeSpawn = ((command: string, args: string[], options?: { cwd?: string }) => {
			calls.push({ command, args, cwd: options?.cwd });
			return { unref() {} };
		}) as unknown as typeof import("node:child_process").spawn;

		const result = ensureSiftIndexWarmup(projectRoot, undefined, {
			env: { PATH: join(projectRoot, "bin") },
			spawnFn: fakeSpawn,
			now: Date.parse("2026-04-30T12:00:00.000Z"),
		});

		assert.equal(result.status, "started");
		assert.equal(calls.length, 1);
		assert.equal(calls[0].command, fakeSift);
		assert.deepEqual(calls[0].args.slice(0, 7), [
			"search",
			"--json",
			"--strategy",
			"page-index-hybrid",
			"--limit",
			"1",
			"--retriever-timeout-ms",
		]);
		assert.equal(calls[0].args.at(-2), ".");
		assert.equal(calls[0].cwd, projectRoot);
		assert.match(calls[0].args.at(-1) ?? "", /repo architecture/);
		assert.match(
			readFileSync(
				join(projectRoot, ".sf", "runtime", "sift-index-warmup.json"),
				"utf-8",
			),
			/page-index-hybrid/,
		);
	} finally {
		cleanup(projectRoot);
	}
});

test("ensureSiftIndexWarmup skips recent marker and explicit non-sift backends", () => {
	const projectRoot = makeProject();
	try {
		writeFakeSiftBinary(projectRoot);
		let spawnCount = 0;
		const fakeSpawn = (() => {
			spawnCount += 1;
			return { unref() {} };
		}) as unknown as typeof import("node:child_process").spawn;

		const first = ensureSiftIndexWarmup(projectRoot, undefined, {
			env: { PATH: join(projectRoot, "bin") },
			spawnFn: fakeSpawn,
			now: Date.parse("2026-04-30T12:00:00.000Z"),
		});
		assert.equal(first.status, "started");

		const second = ensureSiftIndexWarmup(projectRoot, undefined, {
			env: { PATH: join(projectRoot, "bin") },
			spawnFn: fakeSpawn,
			now: Date.parse("2026-04-30T12:01:00.000Z"),
		});
		assert.equal(second.status, "skipped");
		assert.equal(spawnCount, 1);

		const explicitProjectRag = ensureSiftIndexWarmup(
			projectRoot,
			{ indexer_backend: "projectRag" },
			{
				env: { PATH: join(projectRoot, "bin") },
				force: true,
				spawnFn: fakeSpawn,
			},
		);
		assert.equal(explicitProjectRag.status, "skipped");
		assert.match(explicitProjectRag.reason, /projectRag/);

		const disabled = ensureSiftIndexWarmup(
			projectRoot,
			{ indexer_backend: "none" },
			{
				env: { PATH: join(projectRoot, "bin") },
				force: true,
				spawnFn: fakeSpawn,
			},
		);
		assert.equal(disabled.status, "skipped");
		assert.match(disabled.reason, /none/);
		assert.equal(spawnCount, 1);
	} finally {
		cleanup(projectRoot);
	}
});

test("ensureSiftIndexWarmup wraps sift with timeout(1) when available", () => {
	const projectRoot = makeProject();
	try {
		const fakeSift = writeFakeSiftBinary(projectRoot);
		const fakeTimeout = join(projectRoot, "bin", "timeout");
		writeFileSync(fakeTimeout, "", "utf-8");

		const calls: Array<{ command: string; args: string[] }> = [];
		const fakeSpawn = ((command: string, args: string[]) => {
			calls.push({ command, args });
			return { unref() {} };
		}) as unknown as typeof import("node:child_process").spawn;

		const result = ensureSiftIndexWarmup(projectRoot, undefined, {
			env: { PATH: join(projectRoot, "bin") },
			spawnFn: fakeSpawn,
			hardTimeoutSec: 42,
			force: true,
			now: Date.parse("2026-05-02T12:00:00.000Z"),
		});

		assert.equal(result.status, "started");
		assert.equal(calls.length, 1);
		assert.equal(calls[0].command, fakeTimeout);
		assert.deepEqual(calls[0].args.slice(0, 3), [
			"--kill-after=10",
			"42",
			fakeSift,
		]);
		assert.equal(calls[0].args[3], "search");
		assert.match(result.reason, /hard cap 42s/);
		const marker = JSON.parse(
			readFileSync(
				join(projectRoot, ".sf", "runtime", "sift-index-warmup.json"),
				"utf-8",
			),
		);
		assert.equal(marker.command, fakeTimeout);
		assert.equal(marker.siftBinary, fakeSift);
		assert.equal(marker.hardTimeoutSec, 42);
	} finally {
		cleanup(projectRoot);
	}
});

test("ensureSiftIndexWarmup honors SF_SIFT_HARD_TIMEOUT_DISABLE", () => {
	const projectRoot = makeProject();
	try {
		const fakeSift = writeFakeSiftBinary(projectRoot);
		writeFileSync(join(projectRoot, "bin", "timeout"), "", "utf-8");

		const calls: Array<{ command: string; args: string[] }> = [];
		const fakeSpawn = ((command: string, args: string[]) => {
			calls.push({ command, args });
			return { unref() {} };
		}) as unknown as typeof import("node:child_process").spawn;

		const result = ensureSiftIndexWarmup(projectRoot, undefined, {
			env: {
				PATH: join(projectRoot, "bin"),
				SF_SIFT_HARD_TIMEOUT_DISABLE: "1",
			},
			spawnFn: fakeSpawn,
			force: true,
			now: Date.parse("2026-05-02T12:00:00.000Z"),
		});

		assert.equal(result.status, "started");
		assert.equal(calls.length, 1);
		assert.equal(calls[0].command, fakeSift);
		assert.equal(calls[0].args[0], "search");
		assert.match(result.reason, /hard cap disabled/);
	} finally {
		cleanup(projectRoot);
	}
});

test("ensureSiftIndexWarmup ignores stale absolute-path warmup markers", () => {
	const projectRoot = makeProject();
	try {
		writeFakeSiftBinary(projectRoot);
		mkdirSync(join(projectRoot, ".sf", "runtime"), { recursive: true });
		writeFileSync(
			join(projectRoot, ".sf", "runtime", "sift-index-warmup.json"),
			`${JSON.stringify({
				startedAt: "2026-04-30T12:00:00.000Z",
				command: join(projectRoot, "bin", "sift"),
				args: ["search", projectRoot, "repo architecture"],
			})}\n`,
			"utf-8",
		);
		let spawnCount = 0;
		const fakeSpawn = (() => {
			spawnCount += 1;
			return { unref() {} };
		}) as unknown as typeof import("node:child_process").spawn;

		const result = ensureSiftIndexWarmup(projectRoot, undefined, {
			env: { PATH: join(projectRoot, "bin") },
			spawnFn: fakeSpawn,
			now: Date.parse("2026-04-30T12:01:00.000Z"),
		});

		assert.equal(result.status, "started");
		assert.equal(spawnCount, 1);
		assert.match(
			readFileSync(
				join(projectRoot, ".sf", "runtime", "sift-index-warmup.json"),
				"utf-8",
			),
			/"schemaVersion": 2/,
		);
	} finally {
		cleanup(projectRoot);
	}
});

test("resolveSiftBinary honors SIFT_PATH before PATH lookup", () => {
	const projectRoot = makeProject();
	try {
		const fakeSift = writeFakeSiftBinary(projectRoot);

		assert.equal(
			resolveSiftBinary({
				SIFT_PATH: "/custom/sift",
				PATH: join(projectRoot, "bin"),
			}),
			"/custom/sift",
		);
		assert.equal(
			resolveSiftBinary({ PATH: join(projectRoot, "bin") }),
			fakeSift,
		);
		assert.equal(resolveSiftBinary({ PATH: "" }), null);
	} finally {
		cleanup(projectRoot);
	}
});

test("detectSift finds mocked sift binary on PATH and honors disabled backend", () => {
	const projectRoot = makeProject();
	try {
		const fakeSift = writeFakeSiftBinary(projectRoot);

		const detection = detectSift(
			projectRoot,
			{ indexer_backend: "sift" },
			{ PATH: join(projectRoot, "bin") },
		);
		assert.equal(detection.status, "configured");
		assert.equal(detection.binaryPath, fakeSift);
		assert.match(detection.reason, /PATH/);

		const disabled = detectSift(
			projectRoot,
			{ indexer_backend: "none" },
			{ PATH: join(projectRoot, "bin") },
		);
		assert.equal(disabled.status, "disabled");
	} finally {
		cleanup(projectRoot);
	}
});

test("formatSiftStatus reports configured and missing sift backends without running sift", () => {
	const projectRoot = makeProject();
	try {
		const fakeSift = writeFakeSiftBinary(projectRoot);

		const configured = formatSiftStatus(
			projectRoot,
			{ indexer_backend: "sift" },
			{ PATH: join(projectRoot, "bin") },
		);
		assert.match(configured, /Sift Status/);
		assert.match(configured, /Status: configured/);
		assert.match(
			configured,
			new RegExp(fakeSift.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")),
		);
		assert.match(configured, /sift search --json/);

		const missing = formatSiftStatus(
			projectRoot,
			{ indexer_backend: "sift" },
			{ PATH: "" },
		);
		assert.match(missing, /Status: missing/);
		assert.match(missing, /SIFT_PATH/);
	} finally {
		cleanup(projectRoot);
	}
});

test("buildCodeIntelligenceContextBlock injects sift guidance when selected", () => {
	const projectRoot = makeProject();
	try {
		writeFakeSiftBinary(projectRoot);

		const block = buildCodeIntelligenceContextBlock(
			projectRoot,
			{ indexer_backend: "sift" },
			{ PATH: join(projectRoot, "bin") },
		);
		assert.match(block, /PROJECT CODE INTELLIGENCE/);
		assert.match(block, /Sift: configured/);
		assert.match(block, /sift search --json/);
		assert.match(block, /page-index-hybrid/);
	} finally {
		cleanup(projectRoot);
	}
});
