import * as Fs from "node:fs";
import * as Os from "node:os";
import * as Path from "node:path";
import { execFileSync } from "node:child_process";
import { createHash } from "node:crypto";
import * as NodeCrypto from "@effect/platform-node/NodeCrypto";
import * as NodeServices from "@effect/platform-node/NodeServices";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import { assert, it } from "@effect/vitest";
import { CheckpointRef } from "@t3tools/contracts";
import * as ServerConfig from "../config.ts";
import * as GitVcsDriver from "./GitVcsDriver.ts";
import * as VcsProcess from "./VcsProcess.ts";

const evidenceDir = "/tmp/lead-32374-r1/evidence-capture-1";
const before = "80e62619f5c01d84579bcf70ea6d566712007412";
const after = "049b1e8e9cdb888d0090c1f5240f9ae027f8b68e";
const revision = execFileSync("git", ["rev-parse", "HEAD"], { encoding: "utf8" }).trim();
if (revision !== before && revision !== after) throw new Error("Unexpected revision");
const label = revision === before ? "before" : "after";
const runDir = Fs.mkdtempSync(Path.join(evidenceDir, `${label}-${new Date().toISOString().replaceAll(":", "-")}-`));
const transcript = Path.join(runDir, `${label}-transcript.txt`);
const log = (event: string, data?: unknown) => {
  const line = data === undefined ? event : `${event} ${JSON.stringify(data)}`;
  Fs.appendFileSync(transcript, `${line}\n`);
  console.log(line);
};

// Isolate Git configuration while keeping the real subprocess/layers used by the driver tests.
for (const key of Object.keys(process.env)) {
  if (/^GIT_/.test(key)) delete process.env[key];
}
Fs.writeFileSync(Path.join(runDir, "global.gitconfig"), "");
Object.assign(process.env, {
  GIT_CONFIG_GLOBAL: Path.join(runDir, "global.gitconfig"),
  GIT_CONFIG_SYSTEM: "/dev/null",
  GIT_CONFIG_NOSYSTEM: "1",
  GIT_TERMINAL_PROMPT: "0",
  GIT_AUTHOR_NAME: "Synthetic Fixture",
  GIT_AUTHOR_EMAIL: "fixture@example.invalid",
  GIT_COMMITTER_NAME: "Synthetic Fixture",
  GIT_COMMITTER_EMAIL: "fixture@example.invalid",
  GIT_AUTHOR_DATE: "2026-01-01T00:00:00Z",
  GIT_COMMITTER_DATE: "2026-01-01T00:00:00Z",
  LC_ALL: "C",
});
const configs = [
  ["core.autocrlf", "false"],
  ["core.filemode", "false"],
  ["core.longpaths", "true"],
  ["commit.gpgsign", "false"],
  ["tag.gpgsign", "false"],
  ["init.defaultBranch", "main"],
] as const;
process.env.GIT_CONFIG_COUNT = String(configs.length);
configs.forEach(([key, value], index) => {
  process.env[`GIT_CONFIG_KEY_${index}`] = key;
  process.env[`GIT_CONFIG_VALUE_${index}`] = value;
});

const gitVersion = execFileSync("git", ["--version"], { encoding: "utf8" }).trim();
log(`Environment: ${Os.type()} ${Os.release()} ${Os.arch()}; ${gitVersion}; Node ${process.version}; before=${before}; after=${after}; selected=${revision}`);
log("Protocol", {
  runDir,
  entrypoint: "GitVcsDriver.makeVcsDriverShape → checkpoints.captureCheckpoint → filesystem edit → checkpoints.restoreCheckpoint({fallbackToHead:false})",
  process: "Real Git through unmodified VcsProcess.layer / ProcessRunner.layer / NodeServices.layer / NodeCrypto.layer; forwarding logger only; no response shim; no old Git binary",
  gitIsolation: "empty synthetic global config; no system config; per-command deterministic config above; fixture-local core.excludesFile only",
  attribution: "AI-assisted; verified with the tests and tools listed above.",
});

const layerGitContract = Layer.mergeAll(GitVcsDriver.layerVcs, GitVcsDriver.layer).pipe(
  Layer.provide(ServerConfig.layerTest(process.cwd(), Path.join(runDir, "server-config"))),
  Layer.provideMerge(VcsProcess.layer),
  Layer.provideMerge(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer)),
);
const bytes = (path: string) => Fs.existsSync(path)
  ? { exists: true, utf8: Fs.readFileSync(path, "utf8"), hex: Fs.readFileSync(path).toString("hex") }
  : { exists: false };
const digest = (path: string) => createHash("sha256").update(Fs.readFileSync(path)).digest("hex");
const listFiles = (root: string): string[] => Fs.readdirSync(root, { withFileTypes: true })
  .filter((entry) => entry.name !== ".git")
  .flatMap((entry) => entry.isDirectory()
    ? listFiles(Path.join(root, entry.name)).map((file) => `${entry.name}/${file}`)
    : [entry.name])
  .sort();
const errorInfo = (error: unknown) => {
  const value = error as { _tag?: string; operation?: string; detail?: string; message?: string; exitCode?: number };
  return { tag: value._tag, operation: value.operation, detail: value.detail, message: value.message, exitCode: value.exitCode };
};

it.live("captures the minimum revision-paired runtime evidence F01 F02 F04", () =>
  Effect.gen(function* () {
    const liveProcess = yield* VcsProcess.VcsProcess;
    let phase = "initialization";
    let row = "";
    let commandId = 0;
    const commandRecords: { row: string; phase: string; args: readonly string[] }[] = [];
    const driver = yield* GitVcsDriver.makeVcsDriverShape().pipe(
      Effect.provideService(VcsProcess.VcsProcess, {
        run: (input) => Effect.gen(function* () {
          const id = ++commandId;
          commandRecords.push({ row, phase, args: input.args });
          log("git.command", {
            row, phase, id, operation: input.operation, command: input.command,
            cwd: input.cwd, args: input.args, stdin: input.stdin,
            scratchIndex: input.env?.GIT_INDEX_FILE,
          });
          const result = yield* Effect.result(liveProcess.run(input));
          if (result._tag === "Failure") {
            log("git.failure", { id, error: errorInfo(result.failure) });
            return yield* result.failure;
          }
          log("git.result", { id, ...result.success });
          return result.success;
        }),
      }),
    );
    const summaries: Record<string, unknown>[] = [];
    const observations: boolean[] = [];
    for (const fixture of [
      { id: "F01", subdirectory: "", ignore: "a.txt\r\n\r\nb.txt\r\n", source: ".gitignore" },
      { id: "F02", subdirectory: "sub", ignore: "a.txt\r\n\r\nb.txt\r\n", source: ".gitignore" },
      { id: "F04", subdirectory: "outer space/line \nbreak ", ignore: "   \n", source: "core.excludesFile" },
    ]) {
      row = fixture.id;
      phase = "fixture-setup";
      const fixtureDir = Path.join(runDir, fixture.id);
      const repo = Path.join(fixtureDir, "repo");
      const cwd = Path.join(repo, fixture.subdirectory);
      Fs.mkdirSync(cwd, { recursive: true });
      const git = (args: readonly string[], gitCwd = repo, allowNonZeroExit = false) => driver.execute({
        operation: `evidence.${phase}`, cwd: gitCwd, args, allowNonZeroExit,
      });
      log("fixture.begin", { row, revision, cwd, subdirectory: fixture.subdirectory, ignoreSource: fixture.source, ignoreUtf8: fixture.ignore, ignoreHex: Buffer.from(fixture.ignore).toString("hex") });
      yield* git(["init"]);
      yield* git(["config", "user.name", "Synthetic Fixture"]);
      yield* git(["config", "user.email", "fixture@example.invalid"]);
      const ignorePath = fixture.source === ".gitignore"
        ? Path.join(repo, ".gitignore") : Path.join(fixtureDir, "excludes");
      Fs.writeFileSync(ignorePath, fixture.ignore);
      if (fixture.source === "core.excludesFile") yield* git(["config", "core.excludesFile", ignorePath]);
      Fs.writeFileSync(Path.join(repo, "tracked.txt"), "tracked HEAD\n");
      if (fixture.subdirectory !== "") Fs.writeFileSync(Path.join(repo, "outside-sibling.txt"), "outside HEAD\n");
      yield* git(["add", "--", "tracked.txt", ...(fixture.source === ".gitignore" ? [".gitignore"] : []), ...(fixture.subdirectory === "" ? [] : ["outside-sibling.txt"])]);
      yield* git(["commit", "-m", "synthetic fixture baseline"]);
      const notesPath = Path.join(cwd, "notes.md");
      Fs.writeFileSync(notesPath, "notes at capture\n");
      if (fixture.subdirectory === "") Fs.writeFileSync(Path.join(repo, "tracked.txt"), "tracked at capture\n");
      else Fs.writeFileSync(Path.join(repo, "outside-sibling.txt"), "outside modified before capture\n");
      const originalIndex = Fs.readFileSync(Path.join(repo, ".git/index"));
      phase = "diagnostic-before";
      const head = (yield* git(["rev-parse", "HEAD"])).stdout.trim();
      const prefix = (yield* git(["rev-parse", "--show-prefix"], cwd)).stdout;
      const workspaceTracked = (yield* git(["ls-files", "--cached", "-z", "--", "."], cwd)).stdout.split("\0").filter(Boolean);
      log("fixture.ready", { row, headState: "committed", head, prefixRaw: prefix, workspaceTracked, notes: bytes(notesPath), workspaceFiles: listFiles(cwd), outsideSibling: fixture.subdirectory === "" ? "not applicable (root workspace)" : bytes(Path.join(repo, "outside-sibling.txt")), realIndexSha256: digest(Path.join(repo, ".git/index")) });
      const checkpointRef = CheckpointRef.make(`refs/t3/checkpoints/evidence-${fixture.id.toLowerCase()}`);
      phase = "driver-capture";
      const captured = yield* Effect.result(driver.checkpoints.captureCheckpoint({ cwd, checkpointRef }));
      const indexUnchanged = originalIndex.equals(Fs.readFileSync(Path.join(repo, ".git/index")));
      phase = "inspect-capture";
      const ref = yield* git(["show-ref", "--verify", "--hash", checkpointRef], repo, true);
      const refExists = ref.exitCode === 0;
      const captureCommands = commandRecords.filter((command) => command.row === row && command.phase === "driver-capture").map((command) => command.args);
      log("capture.result", { row, status: captured._tag, ...(captured._tag === "Failure" ? { error: errorInfo(captured.failure) } : { result: captured.success ?? null }), checkpointRef, refExists, oid: refExists ? ref.stdout.trim() : null, realIndexUnchanged: indexUnchanged, realIndexSha256: digest(Path.join(repo, ".git/index")), remainingScratchIndexes: Fs.readdirSync(Path.join(repo, ".git")).filter((name) => name.startsWith("t3-checkpoint-index-")), captureCommands });
      observations.push(indexUnchanged);
      const expectRejection = label === "before" && fixture.id !== "F01";
      observations.push(expectRejection ? captured._tag === "Failure" && !refExists : captured._tag === "Success" && refExists);
      if (captured._tag === "Failure") {
        observations.push(expectRejection && captured.failure._tag === "VcsUnsupportedOperationError");
        log("restore.skipped", { row, reason: "Capture rejected; checkpoint ref absent. No filesystem edits, no restore, no HEAD fallback substituted.", workspaceFiles: listFiles(cwd), notes: bytes(notesPath), outsideSibling: bytes(Path.join(repo, "outside-sibling.txt")), ignoreAfter: bytes(ignorePath) });
        summaries.push({ row, capture: "rejected", error: errorInfo(captured.failure), refExists, realIndexUnchanged: indexUnchanged, restore: "unavailable because no checkpoint was published" });
        continue;
      }
      const tree = yield* git(["ls-tree", "-r", "-z", "--name-only", checkpointRef]);
      const notesInRepo = fixture.subdirectory === "" ? "notes.md" : `${fixture.subdirectory}/notes.md`;
      const blob = yield* git(["show", `${checkpointRef}:${notesInRepo}`], repo, true);
      log("checkpoint.tree", { row, pathsNulDecoded: tree.stdout.split("\0").filter(Boolean), rawHex: Buffer.from(tree.stdout).toString("hex"), notesBlob: { exitCode: blob.exitCode, utf8: blob.stdout, hex: Buffer.from(blob.stdout).toString("hex"), stderr: blob.stderr } });
      observations.push(label === "before" ? blob.exitCode !== 0 : blob.exitCode === 0 && blob.stdout === "notes at capture\n");
      phase = "deterministic-edit";
      Fs.writeFileSync(notesPath, "notes edited after snapshot\n");
      Fs.writeFileSync(Path.join(cwd, "later.txt"), "created after snapshot\n");
      if (fixture.subdirectory === "") Fs.writeFileSync(Path.join(cwd, "tracked.txt"), "tracked edited after snapshot\n");
      log("workspace.edited", { row, workspaceFiles: listFiles(cwd), notes: bytes(notesPath), later: bytes(Path.join(cwd, "later.txt")), tracked: bytes(Path.join(cwd, "tracked.txt")), outsideSibling: bytes(Path.join(repo, "outside-sibling.txt")) });
      phase = "driver-restore";
      const restored = yield* Effect.result(driver.checkpoints.restoreCheckpoint({ cwd, checkpointRef, fallbackToHead: false }));
      const laterExists = Fs.existsSync(Path.join(cwd, "later.txt"));
      const notes = bytes(notesPath);
      const outsideSibling = bytes(Path.join(repo, "outside-sibling.txt"));
      const ignoreUnchanged = Fs.readFileSync(ignorePath, "utf8") === fixture.ignore;
      phase = "inspect-restore";
      const status = yield* git(["status", "--porcelain=v1", "-z", "--untracked-files=all"]);
      log("restore.result", { row, status: restored._tag, ...(restored._tag === "Failure" ? { error: errorInfo(restored.failure) } : { result: restored.success }), fallbackToHead: false, workspaceFiles: listFiles(cwd), notes, laterExists, tracked: bytes(Path.join(cwd, "tracked.txt")), outsideSibling, ignoreUnchanged, ignoreAfter: bytes(ignorePath), repositoryStatusNulDecoded: status.stdout.split("\0").filter(Boolean) });
      observations.push(restored._tag === "Success" && restored.success === true, !laterExists, ignoreUnchanged);
      observations.push(label === "before" ? !notes.exists : notes.exists && notes.utf8 === "notes at capture\n");
      if (fixture.subdirectory === "") observations.push(Fs.readFileSync(Path.join(cwd, "tracked.txt"), "utf8") === "tracked at capture\n");
      else observations.push(outsideSibling.exists && outsideSibling.utf8 === "outside modified before capture\n");
      summaries.push({ row, capture: "published", refExists, checkpointOid: ref.stdout.trim(), realIndexUnchanged: indexUnchanged, restore: restored._tag === "Success" ? restored.success : errorInfo(restored.failure), notes, laterExists, outsideSibling, ignoreUnchanged });
    }
    const allExpectedObservations = observations.every(Boolean);
    log("run.summary", { revision, label, rows: summaries, allExpectedObservations });
    Fs.writeFileSync(Path.join(runDir, "results.json"), JSON.stringify({ revision, label, transcript, rows: summaries, allExpectedObservations }, null, 2));
    log("run.end", { transcript, runDir });
    assert.isTrue(allExpectedObservations, "runtime observations match the fixture matrix");
  }).pipe(Effect.scoped, Effect.provide(layerGitContract)),
);
