import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import {
	mkdirSync,
	mkdtempSync,
	rmSync,
	statSync,
	writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { test } from 'vitest';
import { profileRepository } from "../repo-profiler.ts";
import {
	closeDatabase,
	getLatestRepoProfile,
	getRepoFileObservations,
	openDatabase,
	recordRepoProfile,
} from "../sf-db.ts";

function run(args: string[], cwd: string): string {
	return execFileSync("git", args, {
		cwd,
		stdio: ["ignore", "pipe", "pipe"],
		encoding: "utf-8",
	}).trim();
}

function makeRepo(): string {
	const dir = mkdtempSync(join(tmpdir(), "sf-repo-profiler-"));
	run(["init", "-b", "main"], dir);
	run(["config", "user.email", "test@example.com"], dir);
	run(["config", "user.name", "SF Test"], dir);
	writeFileSync(join(dir, "README.md"), "# Repo\n");
	writeFileSync(
		join(dir, "package.json"),
		'{"scripts":{"test":"node --test"}}\n',
	);
	run(["add", "README.md", "package.json"], dir);
	run(["commit", "-m", "init"], dir);
	return dir;
}

test("profileRepository_when_untracked_file_exists_marks_observed_only", () => {
	const repo = makeRepo();
	try {
		writeFileSync(join(repo, "README.md"), "# Repo\n\nchanged\n");
		mkdirSync(join(repo, "docs"));
		writeFileSync(join(repo, "docs", "scratch.md"), "# Scratch\nlocal note\n");

		const beforeStatus = run(["status", "--short"], repo);
		const profile = profileRepository(repo, {
			now: () => "2026-04-29T10:00:00.000Z",
		});
		const afterStatus = run(["status", "--short"], repo);

		assert.equal(afterStatus, beforeStatus, "profiling must be read-only");
		assert.equal(profile.git.branch, "main");
		assert.equal(profile.git.dirty, true);
		assert.ok(profile.git.head, "profile records HEAD when available");
		assert.ok(
			profile.stacks.some(
				(signal) =>
					signal.kind === "node" && signal.sourcePath === "package.json",
			),
			"package.json produces a node stack signal",
		);

		const modified = profile.git.changedFiles.find(
			(file) => file.path === "README.md",
		);
		assert.equal(modified?.gitStatus, "modified");
		assert.equal(modified?.ownership, "user_owned");

		const untracked = profile.git.changedFiles.find(
			(file) => file.path === "docs/scratch.md",
		);
		assert.equal(untracked?.gitStatus, "untracked");
		assert.equal(untracked?.ownership, "observed_only");
		assert.equal(
			untracked?.sizeBytes,
			statSync(join(repo, "docs/scratch.md")).size,
		);
		assert.match(untracked?.contentHash ?? "", /^sha256:[a-f0-9]{64}$/);
	} finally {
		rmSync(repo, { recursive: true, force: true });
	}
});

test("recordRepoProfile_when_file_observed_again_preserves_first_seen", () => {
	const repo = makeRepo();
	try {
		mkdirSync(join(repo, "docs"));
		writeFileSync(join(repo, "docs", "scratch.md"), "# Scratch\n");

		closeDatabase();
		assert.equal(openDatabase(":memory:"), true);

		const first = profileRepository(repo, {
			now: () => "2026-04-29T10:00:00.000Z",
		});
		recordRepoProfile(first);

		writeFileSync(join(repo, "docs", "scratch.md"), "# Scratch\nupdated\n");
		const second = profileRepository(repo, {
			now: () => "2026-04-29T10:05:00.000Z",
		});
		recordRepoProfile(second);

		const latest = getLatestRepoProfile();
		assert.equal(latest?.profileId, second.profileId);

		const observations = getRepoFileObservations();
		const scratch = observations.find((obs) => obs.path === "docs/scratch.md");
		assert.equal(scratch?.ownership, "observed_only");
		assert.equal(scratch?.gitStatus, "untracked");
		assert.equal(scratch?.firstSeenAt, "2026-04-29T10:00:00.000Z");
		assert.equal(scratch?.lastSeenAt, "2026-04-29T10:05:00.000Z");
		assert.equal(scratch?.latestProfileId, second.profileId);
	} finally {
		closeDatabase();
		rmSync(repo, { recursive: true, force: true });
	}
});
