import { expect } from "e2e";
import { execFileSync } from "node:child_process";
import { randomBytes } from "node:crypto";
import { readFileSync, writeFileSync, readdirSync, statSync, existsSync, watch } from "node:fs";
import { join } from "node:path";
import { test, WEB, makeState, runPromise } from "./t3.ts";
import { APP_DIR, T3_HOME } from "../e2e.config.ts";

test("Interrupted checkpoint objects are cleaned up and recovery publishes readable objects", WEB, async ({ t3, app, screen, browser }) => {
  const project = await t3.project("checkpoint-proof");
  const git = (...args: string[]) => execFileSync("git", args, { cwd: project, encoding: "utf8" }).trim();
  const sha = execFileSync("git", ["rev-parse", "HEAD"], { cwd: APP_DIR, encoding: "utf8" }).trim();
  const main = git("rev-parse", "main");
  const index = readFileSync(join(project, ".git/index")).toString("base64");
  git("config", "core.bigFileThreshold", "1m");
  git("config", "filter.slow.required", "true");
  git("config", "filter.slow.clean", "sleep 45; cat");
  writeFileSync(join(project, ".git/info/attributes"), "z-gate filter=slow\n");
  writeFileSync(join(project, "a-media.bin"), randomBytes(5_000_000));
  writeFileSync(join(project, "z-gate"), "checkpoint gate\n");
  const snapshots: unknown[] = [];
  const snapshot = (phase: string) => {
    const pack = join(project, ".git/objects/pack");
    const files = existsSync(pack) ? readdirSync(pack).map(name => ({ name, bytes: statSync(join(pack, name)).size })) : [];
    const privateDirectories = readdirSync(join(project, ".git/objects")).filter(name => name.startsWith("tmp_"));
    const temporaryIndexes = readdirSync(join(project, ".git")).filter(name => name.startsWith("t3-checkpoint-index-"));
    const leaked = files.filter(file => file.name.startsWith("tmp_"));
    const data = { phase, files, privateDirectories, temporaryIndexes, temporaryPackCount: leaked.length, temporaryPackBytes: leaked.reduce((n, f) => n + f.bytes, 0), countObjects: git("count-objects", "-v"), mainPreserved: git("rev-parse", "main") === main, indexPreserved: readFileSync(join(project, ".git/index")).toString("base64") === index, status: git("status", "--porcelain"), checkpointRefs: git("for-each-ref", "--format=%(refname)", "refs/t3") };
    snapshots.push(data);
    console.log(JSON.stringify(data));
    writeFileSync(join(process.env.EVIDENCE_DIR!, `${sha}.json`), JSON.stringify({ sha, environment: "Linux, real T3 server and Chromium web client, Replay agent, fresh database", snapshots }, null, 2));
    return data;
  };
  const thread = await t3.thread({ project, title: "Interrupted checkpoint proof", messages: ["First interrupted capture"], model: "multi-turn" });
  snapshot("After first interrupted turn");
  await t3.open(thread);
  await screen.getByRole("textbox").waitFor({ state: "visible" });
  await screen.getByRole("button", "Toggle terminal drawer").tap();
  await screen.getByRole("textbox", "Terminal input").waitFor({ state: "attached" });
  let captureNumber = 0;
  const capture = async (label: string) => {
    const marker = `evidence-ready-${++captureNumber}`;
    const completed = new Promise<void>((resolve) => {
      const watcher = watch(join(project, ".git"), (_event, name) => {
        if (name === marker) { watcher.close(); resolve(); }
      });
    });
    await screen.getByRole("textbox", "Terminal input").press("Control+L");
    await browser.keyboard.type(`PS1='$ '; printf '\\033c'; echo '${label}'; git count-objects -v 2>/dev/null | tail -2; find .git/objects -name 'tmp_*' -printf '%f %s bytes\\n'; echo '---'; git status --porcelain; touch .git/${marker}`);
    await browser.keyboard.press("Enter");
    await completed;
    await app.screenshot(label);
  };
  await capture("first-interruption");
  const { cli } = makeState(T3_HOME);
  await runPromise(cli(["thread", "send", thread.threadId, "Second interrupted capture", "--wait", "--json"]));
  snapshot("After second interrupted turn");
  await capture("second-interruption");
  git("config", "filter.slow.clean", "cat");
  await runPromise(cli(["thread", "send", thread.threadId, "Recovery capture", "--wait", "--json"]));
  const recovered = snapshot("After successful recovery turn");
  const refs = git("for-each-ref", "--format=%(refname)", "refs/t3").split("\n").filter(Boolean);
  const readable = refs.every(ref => git("cat-file", "-s", `${ref}:a-media.bin`) === "5000000");
  await capture("recovery");
  expect({ temporaryPackCount: recovered.temporaryPackCount, privateDirectories: recovered.privateDirectories, temporaryIndexes: recovered.temporaryIndexes, mainPreserved: recovered.mainPreserved, indexPreserved: recovered.indexPreserved, readableCheckpointObjects: readable && refs.length > 0 }).toEqual({ temporaryPackCount: 0, privateDirectories: [], temporaryIndexes: [], mainPreserved: true, indexPreserved: true, readableCheckpointObjects: true });
});
