import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { execSync, execFileSync } from "node:child_process";

export interface FakeGitRepo {
  /** Absolute path to the repo on disk. */
  path: string;
  /** Stage + commit a set of files. */
  commit: (msg: string, files: Record<string, string>) => Promise<void>;
  /** Remove this repo from the filesystem. Call in afterEach / afterAll. */
  cleanup: () => Promise<void>;
}

/**
 * Creates a throwaway local git repository in a temp directory.
 *
 * Optionally seeds it with initial files. The repo is configured with a
 * dummy user so git commands don't need a real HOME config.
 */
export async function createFakeGitRepo(
  opts: { withFiles?: Record<string, string> } = {},
): Promise<FakeGitRepo> {
  const dir = mkdtempSync(join(tmpdir(), "fake-repo-"));

  const git = (args: string) =>
    execSync(`git ${args}`, {
      cwd: dir,
      env: {
        ...process.env,
        GIT_AUTHOR_NAME: "Test",
        GIT_AUTHOR_EMAIL: "test@test.com",
        GIT_COMMITTER_NAME: "Test",
        GIT_COMMITTER_EMAIL: "test@test.com",
      },
      stdio: "pipe",
    });

  git("init");
  git("config user.email test@test.com");
  git("config user.name Test");
  // Allow pushes to this repo's current branch (used when acting as a "remote")
  git("config receive.denyCurrentBranch updateInstead");

  if (opts.withFiles) {
    for (const [relativePath, content] of Object.entries(opts.withFiles)) {
      const fullPath = join(dir, relativePath);
      mkdirSync(join(dir, relativePath, ".."), { recursive: true });
      writeFileSync(fullPath, content);
    }
    git("add -A");
    git('commit -m "initial commit"');
  }

  const gitEnv = {
    ...process.env,
    GIT_AUTHOR_NAME: "Test",
    GIT_AUTHOR_EMAIL: "test@test.com",
    GIT_COMMITTER_NAME: "Test",
    GIT_COMMITTER_EMAIL: "test@test.com",
  };

  const commit = async (msg: string, files: Record<string, string>): Promise<void> => {
    for (const [relativePath, content] of Object.entries(files)) {
      const fullPath = join(dir, relativePath);
      mkdirSync(join(dir, relativePath, ".."), { recursive: true });
      writeFileSync(fullPath, content);
    }
    git("add -A");
    execFileSync("git", ["commit", "-m", msg], { cwd: dir, env: gitEnv, stdio: "pipe" });
  };

  const cleanup = async (): Promise<void> => {
    rmSync(dir, { recursive: true, force: true });
  };

  return { path: dir, commit, cleanup };
}
