import assert from "node:assert/strict";
import { lstat, mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { test } from "bun:test";

import { createCloudApiServer } from "./server.ts";
import { discoverSkillsForStdio } from "./codex-stdio-session-helpers.ts";

test("cloud api skills upload/list/tree/preview keeps preinstalled and uploaded skill roots separate", async () => {
  const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-skills-api-"));
  const preinstalledDir = path.join(root, "app-skills");
  const userSkillsDir = path.join(root, "user-skills");
  const workspace = path.join(root, "workspace", "hwlab");
  await mkdir(path.join(preinstalledDir, "same-name"), { recursive: true });
  await writeFile(path.join(preinstalledDir, "same-name", "SKILL.md"), skillMarkdown("same-name", "Preinstalled skill."));

  const server = createCloudApiServer({
    env: {
      PATH: process.env.PATH,
      HWLAB_PREINSTALLED_SKILLS_DIR: preinstalledDir,
      HWLAB_USER_SKILLS_DIR: userSkillsDir,
      HWLAB_CODE_AGENT_WORKSPACE: workspace,
      HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspace,
      HWLAB_CODE_AGENT_CODEX_HOME: path.join(root, "codex-home")
    }
  });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));

  try {
    const baseUrl = `http://127.0.0.1:${server.address().port}`;
    const created = await fetchJson(`${baseUrl}/v1/skills/uploads`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        files: [
          {
            relativePath: "uploaded-same-name/SKILL.md",
            contentBase64: Buffer.from(skillMarkdown("same-name", "Uploaded skill."), "utf8").toString("base64")
          },
          {
            relativePath: "uploaded-same-name/references/note.txt",
            contentBase64: Buffer.from("uploaded note", "utf8").toString("base64")
          }
        ]
      })
    });
    assert.equal(created.status, 201);
    assert.equal(created.body.accepted, true);
    assert.equal(created.body.skill.source, "uploaded");
    assert.equal(created.body.skill.name, "same-name");
    assert.equal(created.body.skill.rootDir.startsWith(userSkillsDir), true);
    assert.equal(created.body.skill.rootDir.startsWith(preinstalledDir), false);
    assert.equal(created.body.skill.manifestPath, path.join(created.body.skill.rootDir, "SKILL.md"));

    const list = await fetchJson(`${baseUrl}/v1/skills`);
    assert.equal(list.status, 200);
    assert.equal(list.body.roots.preinstalled, preinstalledDir);
    assert.equal(list.body.roots.uploaded, userSkillsDir);
    assert.equal(list.body.agentRunAssembly.status, "ready");
    assert.equal(list.body.agentRunAssembly.resourceBundle.kind, "gitbundle");
    assert.equal(list.body.agentRunAssembly.resourceBundle.skillsDir, ".agents/skills");
    assert.deepEqual(list.body.agentRunAssembly.bundles, [
      { name: "hwlab-tools", subpath: "tools", target_path: "tools" },
      { name: "hwlab-agent-skills", subpath: "skills", target_path: ".agents/skills" }
    ]);
    assert.deepEqual(list.body.agentRunAssembly.promptRefs.map((item) => item.name), ["hwlab-v02-runtime"]);
    assert.equal(list.body.agentRunAssembly.promptAssembly.status, "ready");
    assert.equal(list.body.agentRunAssembly.promptAssembly.valuesPrinted, false);
    assert.equal(list.body.agentRunAssembly.promptAssembly.requiredCount, 1);
    assert.deepEqual(list.body.agentRunAssembly.promptAssembly.injectStages, ["thread-start"]);
    assert.deepEqual(list.body.agentRunAssembly.promptAssembly.refs, [{
      name: "hwlab-v02-runtime",
      path: "internal/agent/prompts/hwlab-v02-runtime.md",
      inject: "thread-start",
      stage: "thread-start initial prompt",
      required: true,
      source: "ResourceBundleRef.promptRefs"
    }]);
    assert.equal(Object.hasOwn(list.body.agentRunAssembly, "toolAliases"), false);
    assert.equal(Object.hasOwn(list.body.agentRunAssembly, "skillRefs"), false);
    const sameName = list.body.skills.filter((skill) => skill.name === "same-name");
    assert.equal(sameName.length, 2);
    assert.deepEqual(sameName.map((skill) => skill.source).sort(), ["preinstalled", "uploaded"]);

    const tree = await fetchJson(`${baseUrl}/v1/skills/${encodeURIComponent(created.body.skill.id)}/tree`);
    assert.equal(tree.status, 200);
    assert.ok(tree.body.tree.some((entry) => entry.path === "SKILL.md" && entry.type === "file"));
    assert.ok(tree.body.tree.some((entry) => entry.path === "references/note.txt" && entry.type === "file"));

    const preview = await fetchJson(`${baseUrl}/v1/skills/${encodeURIComponent(created.body.skill.id)}/files?path=SKILL.md`);
    assert.equal(preview.status, 200);
    assert.match(preview.body.file.content, /Uploaded skill/u);

    assert.equal(await readFile(path.join(preinstalledDir, "same-name", "SKILL.md"), "utf8"), skillMarkdown("same-name", "Preinstalled skill."));
    const aggregation = path.join(workspace, ".agents", "skills");
    assert.equal((await lstat(path.join(aggregation, "preinstalled-same-name"))).isSymbolicLink(), true);
    assert.equal((await lstat(path.join(aggregation, `uploaded-${created.body.skill.id}`))).isSymbolicLink(), true);
  } finally {
    await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
    await rm(root, { recursive: true, force: true });
  }
});

test("codex stdio skills discovery keeps same-name manifests from different roots", async () => {
  const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-skills-discovery-"));
  const preinstalledDir = path.join(root, "app-skills");
  const userSkillsDir = path.join(root, "user-skills");
  await mkdir(path.join(preinstalledDir, "same-name"), { recursive: true });
  await mkdir(path.join(userSkillsDir, "skill-uploaded"), { recursive: true });
  await writeFile(path.join(preinstalledDir, "same-name", "SKILL.md"), skillMarkdown("same-name", "Preinstalled skill."));
  await writeFile(path.join(userSkillsDir, "skill-uploaded", "SKILL.md"), skillMarkdown("same-name", "Uploaded skill."));

  try {
    const discovered = await discoverSkillsForStdio({
      env: {
        HWLAB_CODE_AGENT_SKILLS_DIRS: `${preinstalledDir}${path.delimiter}${userSkillsDir}`
      }
    });
    assert.equal(discovered.status, "ready");
    const sameName = discovered.items.filter((skill) => skill.name === "same-name");
    assert.equal(sameName.length, 2);
    assert.deepEqual(sameName.map((skill) => skill.sourceRoot).sort(), [preinstalledDir, userSkillsDir].sort());
  } finally {
    await rm(root, { recursive: true, force: true });
  }
});

function skillMarkdown(name, description) {
  return [
    "---",
    `name: ${name}`,
    `description: ${description}`,
    "version: v-test",
    "---",
    "",
    `# ${name}`,
    "",
    description
  ].join("\n");
}

async function fetchJson(url, options = {}) {
  const response = await fetch(url, options);
  return {
    status: response.status,
    body: await response.json()
  };
}
