import assert from "node:assert/strict";
import {
	chmodSync,
	existsSync,
	mkdirSync,
	mkdtempSync,
	readFileSync,
	rmSync,
	writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, test } from "vitest";

import {
	buildSiftEnv,
	detectSift,
	ensureSiftRuntimeDirs,
	resolveEffectiveCodebaseIndexerBackendName,
	resolveSiftSearchScope,
	resolveSiftWarmupRuntimeDirs,
} from "../code-intelligence.js";

const tmpRoots = [];

afterEach(() => {
	for (const dir of tmpRoots.splice(0)) {
		rmSync(dir, { recursive: true, force: true });
	}
});

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

function makeFakeSift(script) {
	const dir = mkdtempSync(join(tmpdir(), "sf-sift-bin-"));
	tmpRoots.push(dir);
	const bin = join(dir, "sift");
	writeFileSync(bin, script);
	chmodSync(bin, 0o755);
	return { dir, bin };
}

test("buildSiftEnv_uses_project_scoped_cache_from_project_root", () => {
	const projectRoot = makeProject();

	const env = buildSiftEnv(projectRoot, { PATH: "/usr/bin" });

	assert.equal(
		env.SIFT_SEARCH_CACHE,
		join(projectRoot, ".sf", "runtime", "sift", "search-cache"),
	);
	assert.equal(env.TMPDIR, join(projectRoot, ".sf", "runtime", "sift", "tmp"));
});

test("resolveSiftSearchScope_normalizes_project_absolute_paths_to_relative", () => {
	const projectRoot = makeProject();

	assert.equal(resolveSiftSearchScope(projectRoot), ".");
	assert.equal(resolveSiftSearchScope(projectRoot, projectRoot), ".");
	assert.equal(
		resolveSiftSearchScope(projectRoot, join(projectRoot, "src")),
		"src",
	);
	assert.equal(resolveSiftSearchScope(projectRoot, "src"), "src");
});

test("ensureSiftRuntimeDirs_creates_repo_local_cache_directories", () => {
	const projectRoot = makeProject();
	const dirs = ensureSiftRuntimeDirs(projectRoot);

	assert.equal(
		dirs.searchCache,
		join(projectRoot, ".sf", "runtime", "sift", "search-cache"),
	);
	assert.ok(existsSync(dirs.searchCache));
	assert.ok(existsSync(dirs.tmpDir));
});

test("detectSift_when_probe_times_out_reports_degraded_not_configured", () => {
	const projectRoot = makeProject();
	const { bin } = makeFakeSift("#!/bin/sh\nsleep 5\n");

	const result = detectSift(
		projectRoot,
		{},
		{
			PATH: process.env.PATH ?? "",
			SIFT_PATH: bin,
			SF_SIFT_HEALTH_TIMEOUT_MS: "50",
		},
	);

	assert.equal(result.status, "degraded");
	assert.equal(result.binaryPath, bin);
	assert.match(result.reason, /health probe timed out/i);
});

test("resolveEffectiveCodebaseIndexerBackendName_when_sift_is_cold_still_selects_sift", () => {
	const projectRoot = makeProject();
	const { bin } = makeFakeSift("#!/bin/sh\nsleep 5\n");

	const result = resolveEffectiveCodebaseIndexerBackendName(
		projectRoot,
		{},
		{
			PATH: process.env.PATH ?? "",
			SIFT_PATH: bin,
			SF_SIFT_HEALTH_TIMEOUT_MS: "50",
		},
	);

	assert.equal(result, "sift");
});

test("detectSift_when_probe_succeeds_reports_project_cache", () => {
	const projectRoot = makeProject();
	const { bin } = makeFakeSift("#!/bin/sh\nprintf '{\"hits\":[]}\\n'\n");

	const result = detectSift(
		projectRoot,
		{},
		{
			PATH: process.env.PATH ?? "",
			SIFT_PATH: bin,
			SF_SIFT_HEALTH_TIMEOUT_MS: "1000",
		},
	);
	const dirs = resolveSiftWarmupRuntimeDirs(projectRoot);

	assert.equal(result.status, "configured");
	assert.equal(result.binaryPath, bin);
	assert.equal(result.searchCache, dirs.searchCache);
	assert.equal(result.tmpDir, dirs.tmpDir);
});

test("detectSift_when_warmup_pid_is_dead_marks_marker_completed_when_cache_exists", () => {
	const projectRoot = makeProject();
	const { bin } = makeFakeSift("#!/bin/sh\nprintf '{\"hits\":[]}\\n'\n");
	const dirs = ensureSiftRuntimeDirs(projectRoot);
	const artifactDir = join(dirs.searchCache, "artifacts", "indexes", "repo");
	mkdirSync(artifactDir, { recursive: true });
	writeFileSync(join(artifactDir, "index.bin"), "indexed");
	const markerPath = join(
		projectRoot,
		".sf",
		"runtime",
		"sift-index-warmup.json",
	);
	writeFileSync(
		markerPath,
		JSON.stringify(
			{
				schemaVersion: 3,
				status: "warming",
				startedAt: new Date().toISOString(),
				command: bin,
				cwd: projectRoot,
				args: ["search", "."],
				scope: ".",
				siftBinary: bin,
				hardTimeoutSec: 600,
				searchCache: dirs.searchCache,
				tmpDir: dirs.tmpDir,
				pid: 99999999,
			},
			null,
			2,
		),
	);

	const result = detectSift(
		projectRoot,
		{},
		{
			PATH: process.env.PATH ?? "",
			SIFT_PATH: bin,
			SF_SIFT_HEALTH_TIMEOUT_MS: "1000",
		},
	);
	const marker = JSON.parse(readFileSync(markerPath, "utf-8"));

	assert.equal(result.status, "configured");
	assert.equal(marker.status, "completed");
	assert.match(marker.terminalReason, /pid 99999999 is no longer running/);
	assert.equal(marker.artifactCount, 1);
	assert.equal(marker.artifactCountCapped, false);
	assert.equal(typeof marker.finishedAt, "string");
	assert.equal(typeof marker.latestArtifactAt, "string");
});

test("detectSift_when_warmup_pid_is_dead_marks_marker_stale_without_cache", () => {
	const projectRoot = makeProject();
	const { bin } = makeFakeSift("#!/bin/sh\nprintf '{\"hits\":[]}\\n'\n");
	const dirs = ensureSiftRuntimeDirs(projectRoot);
	const markerPath = join(
		projectRoot,
		".sf",
		"runtime",
		"sift-index-warmup.json",
	);
	writeFileSync(
		markerPath,
		JSON.stringify(
			{
				schemaVersion: 3,
				status: "warming",
				startedAt: new Date().toISOString(),
				command: bin,
				cwd: projectRoot,
				args: ["search", "."],
				scope: ".",
				siftBinary: bin,
				hardTimeoutSec: 600,
				searchCache: dirs.searchCache,
				tmpDir: dirs.tmpDir,
				pid: 99999999,
			},
			null,
			2,
		),
	);

	detectSift(
		projectRoot,
		{},
		{
			PATH: process.env.PATH ?? "",
			SIFT_PATH: bin,
			SF_SIFT_HEALTH_TIMEOUT_MS: "1000",
		},
	);
	const marker = JSON.parse(readFileSync(markerPath, "utf-8"));

	assert.equal(marker.status, "stale");
	assert.match(marker.terminalReason, /pid 99999999 is no longer running/);
	assert.equal(marker.artifactCount, 0);
	assert.equal(marker.artifactCountCapped, false);
	assert.equal(marker.latestArtifactAt, null);
});

test("detectSift_when_cache_manifest_contains_ignored_paths_reports_degraded", () => {
	const projectRoot = makeProject();
	const { bin } = makeFakeSift("#!/bin/sh\nprintf '{\"hits\":[]}\\n'\n");
	const dirs = ensureSiftRuntimeDirs(projectRoot);
	const manifestDir = join(dirs.searchCache, "artifacts", "manifests");
	mkdirSync(manifestDir, { recursive: true });
	writeFileSync(
		join(manifestDir, "bad"),
		`./src/index.js\n${projectRoot}/.claude/worktrees/agent/src/index.js\n`,
	);

	const result = detectSift(
		projectRoot,
		{},
		{
			PATH: process.env.PATH ?? "",
			SIFT_PATH: bin,
			SF_SIFT_HEALTH_TIMEOUT_MS: "1000",
		},
	);

	assert.equal(result.status, "degraded");
	assert.match(result.reason, /cache contains ignored\/generated paths/i);
	assert.equal(result.cacheInspection.polluted, true);
});
