import { mkdtemp, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
// oxlint-disable-next-line unicorn/import-style -- named import avoids shadowing local `path` vars
import { join } from "node:path";

import { Effect } from "effect";
import { afterEach, beforeEach, describe, expect, it } from "vitest";

import {
  addWorktree,
  commit,
  currentBranch,
  nodeShell,
  push,
} from "./git-local-runtime";

const run = <A, E>(eff: Effect.Effect<A, E>) => Effect.runPromise(eff);

const execPlain = async (cwd: string, cmd: string) => {
  const res = await run(nodeShell.exec("sh", ["-c", cmd], { cwd }));
  return res.stdout.trim();
};

let root = "";
let origin = "";

beforeEach(async () => {
  root = await mkdtemp(join(tmpdir(), "git-local-"));
  origin = join(root, "origin.git");
  await execPlain(root, `git init --bare -b main ${origin}`);
  const seed = join(root, "seed");
  await execPlain(root, `git clone ${origin} ${seed}`);
  await execPlain(
    seed,
    "git config user.email t@t.t && git config user.name t"
  );
  await writeFile(join(seed, "README.md"), "# hi\n");
  await execPlain(
    seed,
    "git add -A && git commit -m init && git push -u origin main"
  );
});

afterEach(async () => {
  await execPlain(root, "chmod -R u+w . 2>/dev/null; true");
});

describe("GitLocalRuntime", () => {
  it("creates a worktree on a new branch", async () => {
    const cloneDir = join(root, "work");
    await run(nodeShell.exec("git", ["clone", origin, cloneDir]));
    const worktree = join(root, "wt-1");
    const result = await run(
      addWorktree(nodeShell, {
        branch: "work/1/fix",
        path: worktree,
        repositoryPath: cloneDir,
      })
    );
    expect(result).toBe(worktree);
    expect(await execPlain(worktree, "git rev-parse --abbrev-ref HEAD")).toBe(
      "work/1/fix"
    );
  });

  it("commits staged changes and returns the new sha", async () => {
    const cloneDir = join(root, "work2");
    await run(nodeShell.exec("git", ["clone", origin, cloneDir]));
    await run(
      nodeShell.exec(
        "git",
        ["worktree", "add", "-b", "work/2", join(root, "wt-2")],
        { cwd: cloneDir }
      )
    );
    const worktree = join(root, "wt-2");
    await writeFile(join(worktree, "a.txt"), "a");
    const sha = await run(
      commit(nodeShell, { message: "add a", repositoryPath: worktree })
    );
    expect(sha).toMatch(/^[0-9a-f]{7,}/u);
  });

  it("reads the current branch", async () => {
    const cloneDir = join(root, "work3");
    await run(nodeShell.exec("git", ["clone", origin, cloneDir]));
    expect(await run(currentBranch(nodeShell, cloneDir))).toBe("main");
  });

  it("pushes a branch to origin", async () => {
    const cloneDir = join(root, "work4");
    await run(nodeShell.exec("git", ["clone", origin, cloneDir]));
    await run(
      nodeShell.exec(
        "git",
        ["worktree", "add", "-b", "work/4", join(root, "wt-4")],
        { cwd: cloneDir }
      )
    );
    const worktree = join(root, "wt-4");
    await writeFile(join(worktree, "b.txt"), "b");
    await run(
      commit(nodeShell, { message: "add b", repositoryPath: worktree })
    );
    await run(push(nodeShell, { refspec: "work/4", repositoryPath: worktree }));
    const branches = await execPlain(origin, "git branch --list");
    expect(branches).toContain("work/4");
  });

  it("maps a non-repo path to NotARepository", async () => {
    const empty = await mkdtemp(join(tmpdir(), "empty-"));
    await expect(run(currentBranch(nodeShell, empty))).rejects.toMatchObject({
      reason: "NotARepository",
    });
  });
});
