import { createMockPi, stubGitExec } from "@juicesharp/rpiv-test-utils";
import { beforeEach, describe, expect, it } from "vitest";
import { clearGitContextCache, getGitContext, resetInjectedMarker, takeGitContextIfChanged } from "./git-context.js";

beforeEach(() => {
	clearGitContextCache();
	resetInjectedMarker();
});

describe("getGitContext", () => {
	it("parses branch + commit + user from three exec calls", async () => {
		const { pi } = createMockPi({
			exec: stubGitExec({ branch: "main", commit: "abc1234", user: "alice" }) as never,
		});
		const ctx = await getGitContext(pi);
		expect(ctx).toEqual({ branch: "main", commit: "abc1234", user: "alice" });
	});

	it("remaps literal HEAD to 'detached'", async () => {
		const { pi } = createMockPi({
			exec: stubGitExec({ branch: "HEAD", commit: "abc", user: "alice" }) as never,
		});
		const ctx = await getGitContext(pi);
		expect(ctx?.branch).toBe("detached");
	});

	it("returns null when both branch and commit are empty (not a repo)", async () => {
		const { pi } = createMockPi({ exec: stubGitExec({}) as never });
		expect(await getGitContext(pi)).toBeNull();
	});

	it("falls back to process.env.USER when git config user.name errors", async () => {
		const { pi } = createMockPi({
			exec: stubGitExec({ branch: "main", commit: "abc", userError: new Error("no config") }) as never,
		});
		process.env.USER = "env-alice";
		const ctx = await getGitContext(pi);
		expect(ctx?.user).toBe("env-alice");
	});

	it("falls back to 'unknown' when neither git nor env has user", async () => {
		const origUser = process.env.USER;
		delete process.env.USER;
		try {
			const { pi } = createMockPi({
				exec: stubGitExec({ branch: "main", commit: "abc", userError: new Error("x") }) as never,
			});
			const ctx = await getGitContext(pi);
			expect(ctx?.user).toBe("unknown");
		} finally {
			if (origUser) process.env.USER = origUser;
		}
	});

	it("memoises: subsequent calls do not re-exec", async () => {
		const exec = stubGitExec({ branch: "main", commit: "abc", user: "alice" });
		const { pi } = createMockPi({ exec: exec as never });
		await getGitContext(pi);
		await getGitContext(pi);
		expect(exec).toHaveBeenCalledTimes(3); // 3 initial exec calls, no second-round
	});

	it("clearGitContextCache forces re-read", async () => {
		const exec = stubGitExec({ branch: "main", commit: "abc", user: "alice" });
		const { pi } = createMockPi({ exec: exec as never });
		await getGitContext(pi);
		clearGitContextCache();
		await getGitContext(pi);
		expect(exec).toHaveBeenCalledTimes(6);
	});
});

describe("takeGitContextIfChanged", () => {
	it("returns the context-line on first call", async () => {
		const { pi } = createMockPi({
			exec: stubGitExec({ branch: "main", commit: "abc", user: "alice" }) as never,
		});
		const r = await takeGitContextIfChanged(pi);
		expect(r).toContain("- Branch: main");
		expect(r).toContain("- Commit: abc");
		expect(r).toContain("- User: alice");
	});

	it("returns null on second call when signature unchanged", async () => {
		const { pi } = createMockPi({
			exec: stubGitExec({ branch: "main", commit: "abc", user: "alice" }) as never,
		});
		await takeGitContextIfChanged(pi);
		expect(await takeGitContextIfChanged(pi)).toBeNull();
	});

	it("re-emits after clearGitContextCache + resetInjectedMarker + signature change", async () => {
		const { pi } = createMockPi({
			exec: stubGitExec({ branch: "main", commit: "abc", user: "alice" }) as never,
		});
		await takeGitContextIfChanged(pi);
		clearGitContextCache();
		resetInjectedMarker();
		const { pi: pi2 } = createMockPi({
			exec: stubGitExec({ branch: "feature", commit: "def", user: "alice" }) as never,
		});
		expect(await takeGitContextIfChanged(pi2)).not.toBeNull();
	});

	it("returns null when not in a git repo", async () => {
		const { pi } = createMockPi({ exec: stubGitExec({}) as never });
		expect(await takeGitContextIfChanged(pi)).toBeNull();
	});
});
