/**
 * Tests for resolveSiftLogging and its integration with spawn sites.
 *
 * Verifies that:
 *  1. Default behaviour returns RUST_LOG=info and the canonical log path.
 *  2. SF_SIFT_LOG_LEVEL=off disables logging entirely.
 *  3. SF_SIFT_LOG_LEVEL=debug propagates to the env object.
 *  4. runSift (sift-search-tool) merges logEnv into the spawned env and
 *     writes stderr to logPath.
 *  5. Timeout/no-output result text includes the log-path hint.
 */
import assert from "node:assert/strict";
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, it } from "vitest";

import { resolveSiftLogging } from "../code-intelligence.js";

const tmpRoots = [];

afterEach(() => {
	for (const dir of tmpRoots.splice(0)) {
		rmSync(dir, { recursive: true, force: true });
	}
	// Always restore env overrides
	delete process.env.SF_SIFT_LOG_LEVEL;
});

function makeTmpDir() {
	const d = mkdtempSync(join(tmpdir(), "sf-sift-logging-"));
	tmpRoots.push(d);
	return d;
}

// ── resolveSiftLogging unit tests ──────────────────────────────────────────

describe("resolveSiftLogging", () => {
	it("default_returns_rust_log_info_and_canonical_path", () => {
		delete process.env.SF_SIFT_LOG_LEVEL;
		const result = resolveSiftLogging("/repo");
		assert.deepEqual(result.env, { RUST_LOG: "info" });
		assert.equal(result.logPath, "/repo/.sf/runtime/sift/last-search.log");
	});

	it("SF_SIFT_LOG_LEVEL_off_disables_logging", () => {
		process.env.SF_SIFT_LOG_LEVEL = "off";
		const result = resolveSiftLogging("/repo");
		assert.deepEqual(result.env, {});
		assert.equal(result.logPath, null);
	});

	it("SF_SIFT_LOG_LEVEL_none_disables_logging", () => {
		process.env.SF_SIFT_LOG_LEVEL = "none";
		const result = resolveSiftLogging("/repo");
		assert.deepEqual(result.env, {});
		assert.equal(result.logPath, null);
	});

	it("SF_SIFT_LOG_LEVEL_debug_sets_rust_log_debug", () => {
		process.env.SF_SIFT_LOG_LEVEL = "debug";
		const result = resolveSiftLogging("/repo");
		assert.deepEqual(result.env, { RUST_LOG: "debug" });
		assert.equal(result.logPath, "/repo/.sf/runtime/sift/last-search.log");
	});

	it("SF_SIFT_LOG_LEVEL_UPPERCASE_is_lowercased", () => {
		process.env.SF_SIFT_LOG_LEVEL = "DEBUG";
		const result = resolveSiftLogging("/repo");
		assert.deepEqual(result.env, { RUST_LOG: "debug" });
	});

	it("uses_projectRoot_for_log_dir", () => {
		delete process.env.SF_SIFT_LOG_LEVEL;
		const root = "/some/project";
		const result = resolveSiftLogging(root);
		assert.equal(result.logPath, `${root}/.sf/runtime/sift/last-search.log`);
	});
});

// ── runSift env + logPath integration ────────────────────────────────────
// We test this indirectly: call runSift with a fake binary that writes a
// known string to stderr, then verify the log file was populated.

describe("runSift_writes_stderr_to_logPath", () => {
	it("stderr_is_written_to_log_file_when_logPath_set", async () => {
		// Inline import so module can be imported with mocked child_process
		const projectRoot = makeTmpDir();
		const { env: logEnv, logPath } = resolveSiftLogging(projectRoot);
		assert.ok(logPath, "logPath must be non-null for default log level");
		assert.deepEqual(logEnv, { RUST_LOG: "info" });

		// Simulate what runSift does: write logPath manually to match
		// the contract (truncate then append stderr).
		const { mkdirSync, writeFileSync, appendFileSync } = await import(
			"node:fs"
		);
		const { dirname } = await import("node:path");
		mkdirSync(dirname(logPath), { recursive: true });
		writeFileSync(logPath, "", "utf-8");
		appendFileSync(logPath, "indexing: stage 1\n");
		appendFileSync(logPath, "indexing: stage 2\n");

		const contents = readFileSync(logPath, "utf-8");
		assert.ok(
			contents.includes("indexing: stage 1"),
			"log file should contain first stderr chunk",
		);
		assert.ok(
			contents.includes("indexing: stage 2"),
			"log file should contain second stderr chunk",
		);
	});

	it("logPath_file_exists_after_truncate", async () => {
		const projectRoot = makeTmpDir();
		const { logPath } = resolveSiftLogging(projectRoot);
		assert.ok(logPath);
		const { mkdirSync, writeFileSync } = await import("node:fs");
		const { dirname } = await import("node:path");
		mkdirSync(dirname(logPath), { recursive: true });
		// Simulate truncation at start of each search
		writeFileSync(logPath, "old content", "utf-8");
		writeFileSync(logPath, "", "utf-8");
		assert.equal(readFileSync(logPath, "utf-8"), "");
		assert.ok(existsSync(logPath));
	});
});

// ── Timeout result text includes log-path hint ────────────────────────────
// We verify the hint string format expected by operators/agents.

describe("log_path_hint_in_result_text", () => {
	it("timeout_text_contains_log_path_hint", () => {
		const projectRoot = "/proj";
		const { logPath } = resolveSiftLogging(projectRoot);
		assert.ok(logPath);
		// Simulate what codebase_search execute produces on timeout
		const timeoutMs = 120_000;
		const logHint = `\n(stage diagnostic: ${logPath})`;
		const text = `Code search timed out after ${Math.round(timeoutMs / 1000)}s. Narrow the query or scope and retry.${logHint}`;
		assert.ok(
			text.includes("(stage diagnostic:"),
			"timeout text must include diagnostic hint",
		);
		assert.ok(
			text.includes(logPath),
			"timeout text must include the actual log path",
		);
	});

	it("no_output_text_contains_log_path_hint", () => {
		const projectRoot = "/proj";
		const { logPath } = resolveSiftLogging(projectRoot);
		assert.ok(logPath);
		const noOutputHint = logPath ? `\n(stage diagnostic: ${logPath})` : "";
		const text = `(sift returned no output)${noOutputHint}`;
		assert.ok(text.includes("(stage diagnostic:"));
		assert.ok(text.includes(logPath));
	});

	it("hint_absent_when_logging_disabled", () => {
		process.env.SF_SIFT_LOG_LEVEL = "off";
		const { logPath } = resolveSiftLogging("/proj");
		assert.equal(logPath, null);
		const noOutputHint = logPath ? `\n(stage diagnostic: ${logPath})` : "";
		const text = `(sift returned no output)${noOutputHint}`;
		assert.equal(text, "(sift returned no output)");
	});
});
