import test from "node:test";
import assert from "node:assert/strict";
import { mkdtemp, mkdir, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { createProjectWorkspace, listProjectFolders, listPublicWorkspaces } from "./workspaces.js";

test("public workspaces use the actual final folder name", () => {
  const result = listPublicWorkspaces({ workspaces: [
    { id: "remote", label: "Code Remote Manager", path: "C:\\Project\\Codex-remote" }
  ] });

  assert.deepEqual(result, [{ id: "remote", label: "Codex-remote" }]);
});

test("project folders lists only direct child directories without paths", async () => {
  const root = await mkdtemp(join(tmpdir(), "codex-projects-"));
  try {
    await mkdir(join(root, "Alpha"));
    await mkdir(join(root, "Beta"));
    const result = await listProjectFolders({ projectsRoot: root, workspaces: [
      { id: "alpha", label: "Old label", path: join(root, "Alpha") }
    ] });

    assert.deepEqual(result, [
      { name: "Alpha", workspaceId: "alpha" },
      { name: "Beta" }
    ]);
    assert.equal(JSON.stringify(result).includes(root), false);
  } finally {
    await rm(root, { recursive: true, force: true });
  }
});

test("creates a new empty project and persists it as a workspace", async () => {
  const root = await mkdtemp(join(tmpdir(), "codex-projects-"));
  const config = { projectsRoot: root, workspaces: [] };
  let persisted;
  try {
    const result = await createProjectWorkspace(config, "config.local.json", { name: "Fresh App", create: true }, {
      randomUUID: () => "12345678-1234-1234-1234-123456789abc",
      persistConfig: async (_path, nextConfig) => { persisted = nextConfig; }
    });

    assert.equal(result.workspace.label, "Fresh App");
    assert.equal("path" in result.workspace, false);
    assert.equal(persisted.workspaces[0].path, join(root, "Fresh App"));
    assert.equal(config.workspaces[0].path, join(root, "Fresh App"));
  } finally {
    await rm(root, { recursive: true, force: true });
  }
});

test("registers an existing project folder without exposing its path", async () => {
  const root = await mkdtemp(join(tmpdir(), "codex-projects-"));
  const config = { projectsRoot: root, workspaces: [] };
  try {
    await mkdir(join(root, "Existing"));
    const result = await createProjectWorkspace(config, "config.local.json", { name: "Existing", create: false }, {
      persistConfig: async () => {}
    });
    assert.equal(result.workspace.label, "Existing");
    assert.equal(JSON.stringify(result).includes(root), false);
  } finally {
    await rm(root, { recursive: true, force: true });
  }
});

test("rejects traversal and invalid Windows project names", async () => {
  const config = { projectsRoot: "C:\\Project", workspaces: [] };
  for (const name of ["..", "../secret", "bad/name", "CON", "trailing."]) {
    await assert.rejects(
      createProjectWorkspace(config, "config.local.json", { name, create: true }),
      (error) => error.message === "invalid_project_name" && error.status === 400
    );
  }
});

test("rejects invalid project names before filesystem or config writes", async () => {
  const config = { projectsRoot: "C:\\Project", workspaces: [] };
  const calls = [];

  for (const name of ["", "   ", "..", "../secret", "C:\\Users\\person\\secret", "bad/name"]) {
    await assert.rejects(
      createProjectWorkspace(config, "config.local.json", { name, create: true }, {
        makeDirectory: async () => { calls.push("mkdir"); },
        readStats: async () => { calls.push("stat"); },
        persistConfig: async () => { calls.push("persist"); }
      }),
      (error) => error.message === "invalid_project_name" && error.status === 400
    );
  }

  assert.deepEqual(calls, []);
});

test("serializes concurrent workspace registrations without losing either project", async () => {
  const root = await mkdtemp(join(tmpdir(), "codex-projects-"));
  const config = { projectsRoot: root, workspaces: [] };
  const persisted = [];
  try {
    const dependencies = {
      randomUUID: () => crypto.randomUUID(),
      persistConfig: async (_path, nextConfig) => {
        await new Promise((resolve) => setTimeout(resolve, 5));
        persisted.push(nextConfig.workspaces.map((item) => item.label));
      }
    };
    await Promise.all([
      createProjectWorkspace(config, "config.local.json", { name: "Alpha", create: true }, dependencies),
      createProjectWorkspace(config, "config.local.json", { name: "CRUBLY", create: true }, dependencies)
    ]);

    assert.deepEqual(config.workspaces.map((item) => item.label), ["Alpha", "CRUBLY"]);
    assert.deepEqual(persisted.at(-1), ["Alpha", "CRUBLY"]);
  } finally {
    await rm(root, { recursive: true, force: true });
  }
});
