/**
 * auto-worktree-nested-guard.test.ts
 *
 * Verifies that createAutoWorktree() refuses to create a nested worktree
 * when basePath is itself a git worktree (.git file, not directory).
 */

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

import { SFError } from "../errors.ts";
import type { JournalEntry } from "../journal.ts";
import { createAutoWorktree } from "../auto-worktree.ts";

// ─── Helpers ──────────────────────────────────────────────────────────────────

/** Write a fake .git file so the directory looks like a git worktree. */
function makeWorktreeDir(dir: string): void {
	writeFileSync(
		join(dir, ".git"),
		"gitdir: /some/repo/.git/worktrees/fake-wt\n",
		"utf-8",
	);
}

/** Read all journal entries from a temp .sf/journal directory. */
function readJournalEntries(basePath: string): JournalEntry[] {
	const journalDir = join(basePath, ".sf", "journal");
	try {
		const files = readdirSync(journalDir)
			.filter((f) => f.endsWith(".jsonl"))
			.sort();
		const entries: JournalEntry[] = [];
		for (const file of files) {
			const raw = readFileSync(join(journalDir, file), "utf-8");
			for (const line of raw.split("\n")) {
				if (!line.trim()) continue;
				entries.push(JSON.parse(line) as JournalEntry);
			}
		}
		return entries;
	} catch {
		return [];
	}
}

// ─── Tests ────────────────────────────────────────────────────────────────────

describe("createAutoWorktree nested-worktree guard", () => {
	let tmp: string;

	beforeEach(() => {
		tmp = mkdtempSync(join(tmpdir(), "sf-nested-wt-guard-"));
	});

	afterEach(() => {
		rmSync(tmp, { recursive: true, force: true });
	});

	test("throws SFError when basePath is a git worktree", () => {
		makeWorktreeDir(tmp);

		assert.throws(
			() => createAutoWorktree(tmp, "M001"),
			(err: unknown) => {
				assert.ok(err instanceof SFError, "should be an SFError");
				return true;
			},
		);
	});

	test("error message mentions 'nested worktree' and the offending basePath", () => {
		makeWorktreeDir(tmp);

		assert.throws(
			() => createAutoWorktree(tmp, "M001"),
			(err: unknown) => {
				assert.ok(err instanceof Error);
				assert.ok(
					err.message.includes("nested worktree"),
					`expected 'nested worktree' in message, got: ${err.message}`,
				);
				assert.ok(
					err.message.includes(tmp),
					`expected basePath in message, got: ${err.message}`,
				);
				return true;
			},
		);
	});

	test("emits worktree-create-failed journal event with nested-worktree-rejected reason", () => {
		// Ensure .sf/journal directory is writable for the journal emit.
		mkdirSync(join(tmp, ".sf", "journal"), { recursive: true });
		makeWorktreeDir(tmp);

		assert.throws(() => createAutoWorktree(tmp, "M001"));

		const entries = readJournalEntries(tmp);
		const failed = entries.find(
			(e) => e.eventType === "worktree-create-failed",
		);
		assert.ok(failed, "worktree-create-failed event should be emitted");
		assert.equal(
			failed!.data?.reason,
			"nested-worktree-rejected",
			"reason must be nested-worktree-rejected",
		);
		assert.equal(
			failed!.data?.milestoneId,
			"M001",
			"milestoneId must be recorded",
		);
		assert.equal(
			failed!.data?.basePath,
			tmp,
			"basePath must be recorded in event data",
		);
	});

	test("succeeds (does not throw) when basePath is a regular repo directory (control case)", () => {
		// Simulate a real repo: .git is a *directory*, not a file.
		mkdirSync(join(tmp, ".git"), { recursive: true });

		// createAutoWorktree will fail with a git error (no real git binary
		// operations succeed on this fake repo), but it must NOT throw the
		// nested-worktree guard error — i.e. it must get past the guard.
		assert.throws(
			() => createAutoWorktree(tmp, "M001"),
			(err: unknown) => {
				assert.ok(err instanceof Error);
				assert.ok(
					!err.message.includes("nested worktree"),
					`guard must NOT fire for a regular repo; got: ${err.message}`,
				);
				return true;
			},
		);
	});
});
