import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { ThreadId } from "@best-agent/protocol";
import { describe, expect, it, vi } from "vitest";
import { WorkspaceState } from "./workspace-state.js";

vi.mock("node:fs", async (importOriginal) => {
  const actual = await importOriginal<typeof import("node:fs")>();
  return {
    ...actual,
    readFileSync: vi.fn(actual.readFileSync),
  };
});

describe("WorkspaceState", () => {
  it("reuses the cached file hash for unchanged reads", () => {
    const workspace = mkdtempSync(join(tmpdir(), "best-agent-workspace-"));
    const path = join(workspace, "note.txt");
    writeFileSync(path, "hello");

    const readSpy = vi.mocked(readFileSync);
    readSpy.mockClear();

    try {
      const state = new WorkspaceState();
      state.recordRead("thread_1" as ThreadId, path);
      expect(readSpy).toHaveBeenCalledTimes(1);

      state.recordRead("thread_1" as ThreadId, path);
      expect(readSpy).toHaveBeenCalledTimes(1);
    } finally {
      readSpy.mockClear();
      rmSync(workspace, { force: true, recursive: true });
    }
  });

  it("rejects stale writes after the file changes on disk", () => {
    const workspace = mkdtempSync(join(tmpdir(), "best-agent-workspace-"));
    const path = join(workspace, "note.txt");
    writeFileSync(path, "hello");

    try {
      const state = new WorkspaceState();
      state.recordRead("thread_1" as ThreadId, path);
      writeFileSync(path, "updated");

      let error: unknown;
      try {
        state.assertMutationAllowed("thread_1" as ThreadId, path);
      } catch (candidate) {
        error = candidate;
      }
      expect(error).toMatchObject({ code: "WORKSPACE_STALE_VIEW" });
    } finally {
      rmSync(workspace, { force: true, recursive: true });
    }
  });
});
