import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, test } from "vitest";
import { preDispatchHealthGate } from "../doctor-proactive.js";
import { nativeAddTracked } from "../native-git-bridge.js";
import {
	formatProtectedSnapshotDeletionMessage,
	listProtectedSnapshotDeletions,
} from "../snapshot-safety.js";

const tmpDirs = [];

function git(cwd, args, env = {}) {
	return execFileSync("git", args, {
		cwd,
		stdio: ["ignore", "pipe", "pipe"],
		encoding: "utf-8",
		env: {
			...process.env,
			GIT_AUTHOR_NAME: "SF Test",
			GIT_AUTHOR_EMAIL: "sf-test@example.invalid",
			GIT_COMMITTER_NAME: "SF Test",
			GIT_COMMITTER_EMAIL: "sf-test@example.invalid",
			...env,
		},
	}).trim();
}

function makeRepo() {
	const dir = mkdtempSync(join(tmpdir(), "sf-snapshot-safety-"));
	tmpDirs.push(dir);
	git(dir, ["init", "-b", "main"]);
	return dir;
}

afterEach(() => {
	while (tmpDirs.length > 0) {
		const dir = tmpDirs.pop();
		if (dir) rmSync(dir, { recursive: true, force: true });
	}
});

describe("snapshot safety", () => {
	test("listProtectedSnapshotDeletions_when_extension_declaration_deleted_returns_path", () => {
		const repo = makeRepo();
		const dts = join(
			repo,
			"src/resources/extensions/sf/code-intelligence.d.ts",
		);
		mkdirSync(join(repo, "src/resources/extensions/sf"), { recursive: true });
		writeFileSync(dts, "export function codebaseSearch(): void;\n");
		git(repo, ["add", "."]);
		git(repo, ["commit", "-m", "seed"]);

		rmSync(dts);

		assert.deepEqual(listProtectedSnapshotDeletions(repo), [
			"src/resources/extensions/sf/code-intelligence.d.ts",
		]);
	});

	test("listProtectedSnapshotDeletions_when_unrelated_declaration_deleted_ignores_path", () => {
		const repo = makeRepo();
		const dts = join(repo, "src/generated/types.d.ts");
		mkdirSync(join(repo, "src/generated"), { recursive: true });
		writeFileSync(dts, "export type Generated = string;\n");
		git(repo, ["add", "."]);
		git(repo, ["commit", "-m", "seed"]);

		rmSync(dts);

		assert.deepEqual(listProtectedSnapshotDeletions(repo), []);
	});

	test("formatProtectedSnapshotDeletionMessage_when_many_paths_limits_output", () => {
		const paths = Array.from(
			{ length: 10 },
			(_, i) => `src/resources/extensions/sf/file-${i}.d.ts`,
		);
		const message = formatProtectedSnapshotDeletionMessage(paths);

		assert.match(message, /refusing automated snapshot/);
		assert.match(message, /file-0\.d\.ts/);
		assert.match(message, /and 2 more/);
	});

	test("preDispatchHealthGate_when_protected_declaration_deleted_blocks_snapshot", async () => {
		const repo = makeRepo();
		const dts = join(
			repo,
			"src/resources/extensions/sf/code-intelligence.d.ts",
		);
		mkdirSync(join(repo, "src/resources/extensions/sf"), { recursive: true });
		writeFileSync(dts, "export function codebaseSearch(): void;\n");
		git(repo, ["add", "."]);
		git(repo, ["commit", "-m", "seed"], {
			GIT_AUTHOR_DATE: "2020-01-01T00:00:00Z",
			GIT_COMMITTER_DATE: "2020-01-01T00:00:00Z",
		});

		rmSync(dts);

		const result = await preDispatchHealthGate(repo);

		assert.equal(result.proceed, false);
		assert.match(result.reason, /Protected declaration deletions detected/);
		assert.equal(git(repo, ["log", "--oneline"]).split("\n").length, 1);
	});

	test("nativeAddTracked_when_tracked_file_deleted_does_not_stage_deletion", () => {
		const repo = makeRepo();
		writeFileSync(join(repo, "keep.txt"), "keep");
		writeFileSync(join(repo, "delete.txt"), "delete");
		git(repo, ["add", "."]);
		git(repo, ["commit", "-m", "seed"]);

		// Delete one tracked file and modify another
		rmSync(join(repo, "delete.txt"));
		writeFileSync(join(repo, "keep.txt"), "modified");

		// Run the snapshot staging function
		nativeAddTracked(repo);

		// The modification should be staged
		const stagedModified = git(repo, [
			"diff",
			"--cached",
			"--name-only",
			"--diff-filter=M",
		]);
		assert.equal(stagedModified, "keep.txt");

		// The deletion should NOT be staged
		const stagedDeleted = git(repo, [
			"diff",
			"--cached",
			"--name-only",
			"--diff-filter=D",
		]);
		assert.equal(stagedDeleted, "");
	});
});
