import assert from "node:assert/strict";
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";

import type { ToolInfo } from "@earendil-works/pi-coding-agent";
import {
  buildManifestHash,
  createFallbackCatalog,
  parseGeneratedCatalog,
  rankGroups,
  readCachedCatalog,
  writeCachedCatalog,
} from "../extensions/catalog.ts";

function tool(name: string, description: string): ToolInfo {
  return {
    name,
    description,
    parameters: { type: "object", properties: { query: { type: "string" } } } as ToolInfo["parameters"],
    sourceInfo: { source: "extension", scope: "user", path: `/test/${name}.ts` } as unknown as ToolInfo["sourceInfo"],
  };
}

const constraints = { maxToolsPerGroup: 2, groupOverrides: {} };

test("fallback grouping chunks large prefixes and applies explicit overrides", () => {
  const tools = [tool("ctx_one", "First"), tool("ctx_two", "Second"), tool("ctx_three", "Third")];
  const configured = { maxToolsPerGroup: 2, groupOverrides: { preferred: ["ctx_three"] } };
  const hash = buildManifestHash(tools, configured);
  const catalog = createFallbackCatalog(tools, hash, configured);
  assert.deepEqual(catalog.groups.find((group) => group.id === "preferred")?.tools, ["ctx_three"]);
  assert.ok(catalog.groups.every((group) => group.tools.length <= 2));
  assert.equal(catalog.tools.find((card) => card.name === "ctx_three")?.primaryGroup, "preferred");
});

test("validates exact generated assignments and ranks generated metadata", () => {
  const tools = [tool("web_search", "Find sources"), tool("web_fetch", "Read a source")];
  const hash = buildManifestHash(tools, constraints);
  const generated = {
    groups: [
      {
        id: "web-research",
        title: "Web research",
        summary: "Find and read current sources.",
        useWhen: ["需要网页搜索"],
        avoidWhen: [],
        tools: ["web_search", "web_fetch"],
      },
    ],
    tools: [
      { name: "web_search", summary: "Find sources", useWhen: [], avoidWhen: [], keywords: ["搜索"], primaryGroup: "web-research" },
      { name: "web_fetch", summary: "Read sources", useWhen: [], avoidWhen: [], keywords: ["抓取"], primaryGroup: "web-research" },
    ],
  };
  const catalog = parseGeneratedCatalog(JSON.stringify(generated), tools, hash, constraints, "test/model");
  assert.equal(rankGroups(catalog, "帮我搜索网页")[0]?.group.id, "web-research");
  assert.throws(
    () => parseGeneratedCatalog(JSON.stringify({ ...generated, tools: generated.tools.slice(0, 1) }), tools, hash, constraints, "test/model"),
    /omitted/,
  );
});

test("writes a private cache and rejects a stale manifest hash", async () => {
  const directory = await mkdtemp(join(tmpdir(), "tool-search-catalog-"));
  try {
    const tools = [tool("web_search", "Find sources")];
    const hash = buildManifestHash(tools, constraints);
    const generated = {
      groups: [{ id: "web", title: "Web", summary: "Find sources", useWhen: [], avoidWhen: [], tools: ["web_search"] }],
      tools: [{ name: "web_search", summary: "Find sources", useWhen: [], avoidWhen: [], keywords: [], primaryGroup: "web" }],
    };
    const catalog = parseGeneratedCatalog(JSON.stringify(generated), tools, hash, constraints, "test/model");
    const path = join(directory, "nested", "catalog.json");
    writeCachedCatalog(path, catalog);
    assert.equal(readCachedCatalog(path, tools, hash, constraints)?.groups[0]?.id, "web");
    assert.equal(readCachedCatalog(path, tools, "stale", constraints), undefined);
  } finally {
    await rm(directory, { recursive: true, force: true });
  }
});
