import { execFileSync } from "node:child_process";
import { createHash } from "node:crypto";
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { LocalProjectMemoryStore, type ProjectMemorySyncEntry } from "./project-memory.js";
import { ProjectMemorySyncClient } from "./project-memory-sync.js";

const roots: string[] = [];

afterEach(() => {
  while (roots.length > 0) {
    rmSync(roots.pop() as string, { force: true, recursive: true });
  }
});

describe("project memory team sync eval gate", () => {
  it("keeps shared-memory conflict handling on the shipped offline corpus", async () => {
    const remoteWinsWorkspace = createWorkspace("remote-wins");
    const remoteWinsStore = new LocalProjectMemoryStore({
      cwd: remoteWinsWorkspace,
      storageRoot: join(remoteWinsWorkspace, "state"),
    });
    remoteWinsStore.set("Repository uses pnpm.");

    const remoteWinsRequests: string[] = [];
    const remoteWinsClient = new ProjectMemorySyncClient({
      cwd: remoteWinsWorkspace,
      fetchFn: async (input) => {
        remoteWinsRequests.push(String(input));
        return jsonResponse({
          entries: [
            syncEntry("manual", "Repository uses bun.", "2030-01-01T00:00:00.000Z"),
          ],
        });
      },
      projectMemory: remoteWinsStore,
      url: "http://team-memory.test",
    });

    await remoteWinsClient.syncNow();

    expect(remoteWinsRequests).toHaveLength(1);
    expect(remoteWinsStore.get().content.trim()).toBe("Repository uses bun.");
    expect(remoteWinsClient.getStatus()).toMatchObject({
      enabled: true,
      lastPullAt: expect.any(String),
      mode: "shared",
    });
    expect(remoteWinsClient.getStatus().lastPushAt).toBeUndefined();

    const localWinsWorkspace = createWorkspace("local-wins");
    const localWinsStore = new LocalProjectMemoryStore({
      cwd: localWinsWorkspace,
      storageRoot: join(localWinsWorkspace, "state"),
    });
    localWinsStore.set("Repository uses pnpm.");

    const localWinsRequests: Array<{ body?: string; method: string }> = [];
    let serverEntries = [
      syncEntry("manual", "Repository uses npm.", "2026-04-11T00:00:00.000Z"),
      syncEntry(
        generatedKey("Always run tests before shipping."),
        "Always run tests before shipping.",
        "2026-04-11T00:00:00.000Z",
      ),
    ];
    const localWinsClient = new ProjectMemorySyncClient({
      cwd: localWinsWorkspace,
      fetchFn: async (_input, init) => {
        const method = init?.method ?? "GET";
        localWinsRequests.push({
          body: typeof init?.body === "string" ? init.body : undefined,
          method,
        });
        if (method === "PATCH") {
          const body = JSON.parse(String(init?.body)) as {
            entries: Array<{ content: string; key: string }>;
          };
          serverEntries = body.entries.map((entry) => syncEntry(entry.key, entry.content, "2026-04-12T00:00:00.000Z"));
          return jsonResponse({ entries: serverEntries, synced: body.entries.length });
        }
        return jsonResponse({ entries: serverEntries });
      },
      projectMemory: localWinsStore,
      url: "http://team-memory.test",
    });

    await localWinsClient.syncNow();

    expect(localWinsRequests.map((request) => request.method)).toEqual(["GET", "PATCH"]);
    expect(JSON.parse(localWinsRequests[1]?.body ?? "{}")).toMatchObject({
      entries: [
        { content: "Repository uses pnpm.", key: "manual" },
      ],
    });
    expect(localWinsStore.get().content).toContain("Repository uses pnpm.");
    expect(localWinsStore.get().content).not.toContain("Always run tests before shipping.");
    expect(serverEntries).toEqual([
      expect.objectContaining({
        content: "Repository uses pnpm.",
        key: "manual",
      }),
    ]);
    expect(localWinsClient.getStatus()).toMatchObject({
      enabled: true,
      lastConflictAt: expect.any(String),
      lastPullAt: expect.any(String),
      lastPushAt: expect.any(String),
      mode: "shared",
    });
  });

  it("keeps shared-memory secret blocking on the shipped offline corpus", async () => {
    const remoteSecretWorkspace = createWorkspace("remote-secret");
    const remoteSecretStore = new LocalProjectMemoryStore({
      cwd: remoteSecretWorkspace,
      storageRoot: join(remoteSecretWorkspace, "state"),
    });
    const remoteSecretClient = new ProjectMemorySyncClient({
      cwd: remoteSecretWorkspace,
      fetchFn: async () =>
        jsonResponse({
          entries: [
            syncEntry(
              generatedKey("OPENAI_API_KEY=sk-proj-abcdefghijklmnopqrstuvwxyz123456"),
              "OPENAI_API_KEY=sk-proj-abcdefghijklmnopqrstuvwxyz123456",
              "2026-04-12T00:00:00.000Z",
            ),
          ],
        }),
      projectMemory: remoteSecretStore,
      url: "http://team-memory.test",
    });

    await remoteSecretClient.syncNow();

    expect(remoteSecretStore.get().content).not.toContain("OPENAI_API_KEY");

    const localSecretWorkspace = createWorkspace("local-secret");
    const localSecretStore = new LocalProjectMemoryStore({
      cwd: localSecretWorkspace,
      storageRoot: join(localSecretWorkspace, "state"),
    });
    writeProjectMemory(
      localSecretStore,
      [
        "Repository uses pnpm.",
        "",
        "## [2026-04-12] session-local-secret",
        "<!-- best-agent-memory: createdAt=2026-04-12T00:00:00.000Z; lastReferencedAt=2026-04-12T00:00:00.000Z -->",
        "- OPENAI_API_KEY=sk-proj-abcdefghijklmnopqrstuvwxyz123456",
      ].join("\n"),
    );

    const localSecretRequests: Array<{ body?: string; method: string }> = [];
    const localSecretClient = new ProjectMemorySyncClient({
      cwd: localSecretWorkspace,
      fetchFn: async (_input, init) => {
        const method = init?.method ?? "GET";
        localSecretRequests.push({
          body: typeof init?.body === "string" ? init.body : undefined,
          method,
        });
        return jsonResponse({ entries: [] });
      },
      projectMemory: localSecretStore,
      url: "http://team-memory.test",
    });

    await localSecretClient.syncNow();

    expect(localSecretRequests.map((request) => request.method)).toEqual(["GET"]);
    expect(localSecretClient.getStatus()).toMatchObject({
      enabled: true,
      lastPullAt: expect.any(String),
      mode: "shared",
    });
    expect(localSecretClient.getStatus().lastPushAt).toBeUndefined();
  });
});

function createWorkspace(name: string): string {
  const workspaceRoot = mkdtempSync(`${tmpdir()}/best-agent-project-memory-sync-eval-${name}-`);
  roots.push(workspaceRoot);
  execFileSync("git", ["init"], { cwd: workspaceRoot, stdio: "ignore" });
  execFileSync("git", ["remote", "add", "origin", `https://github.com/acme/${name}.git`], {
    cwd: workspaceRoot,
    stdio: "ignore",
  });
  return workspaceRoot;
}

function generatedKey(content: string): string {
  return `generated:${hash(content)}`;
}

function hash(content: string): string {
  return createHash("sha256").update(content).digest("hex");
}

function jsonResponse(body: unknown): Response {
  return new Response(JSON.stringify(body), {
    headers: { "content-type": "application/json" },
    status: 200,
  });
}

function syncEntry(key: string, content: string, updatedAt: string): ProjectMemorySyncEntry {
  return {
    checksum: hash(content),
    content,
    key,
    updatedAt,
  };
}

function writeProjectMemory(store: LocalProjectMemoryStore, content: string): void {
  const path = store.get().path;
  mkdirSync(dirname(path), { recursive: true });
  writeFileSync(path, `${content.trimEnd()}\n`, "utf8");
}
