import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { ToolRegistry } from "../src/tools.js";
import { registerCodeQueryTools } from "../src/tools/code-query.js";

describe("code-query tools", () => {
  let tmp: string;
  let registry: ToolRegistry;

  beforeEach(() => {
    tmp = mkdtempSync(join(tmpdir(), "reasonix-code-query-"));
    registry = new ToolRegistry();
    registerCodeQueryTools(registry, { rootDir: tmp });
  });

  afterEach(() => {
    rmSync(tmp, { recursive: true, force: true });
  });

  describe("get_symbols", () => {
    it("returns symbols for a TS file under the project root", async () => {
      writeFileSync(
        join(tmp, "foo.ts"),
        "export function add(a: number, b: number) { return a + b; }\nexport class C {}\n",
      );
      const raw = await registry.dispatch("get_symbols", JSON.stringify({ path: "foo.ts" }));
      const parsed = JSON.parse(raw) as {
        path: string;
        symbols: Array<{ name: string; kind: string }>;
      };
      expect(parsed.path).toBe("foo.ts");
      expect(parsed.symbols.map((s) => `${s.kind}:${s.name}`)).toEqual(["function:add", "class:C"]);
    });

    it("reports unsupported language for non-JS/TS/Python/Go/Rust/Java files", async () => {
      writeFileSync(join(tmp, "a.cpp"), "int main(){}\n");
      const raw = await registry.dispatch("get_symbols", JSON.stringify({ path: "a.cpp" }));
      const parsed = JSON.parse(raw) as { error?: string };
      expect(parsed.error).toMatch(/language not supported/);
    });

    it("extracts symbols for Python files", async () => {
      writeFileSync(join(tmp, "a.py"), "def hello():\n    pass\n");
      const raw = await registry.dispatch("get_symbols", JSON.stringify({ path: "a.py" }));
      const parsed = JSON.parse(raw) as { symbols: Array<{ name: string; kind: string }> };
      expect(parsed.symbols.map((s) => `${s.kind}:${s.name}`)).toEqual(["function:hello"]);
    });

    it("treats leading slash as project-root-relative", async () => {
      writeFileSync(join(tmp, "x.ts"), "function f(){}");
      const raw = await registry.dispatch("get_symbols", JSON.stringify({ path: "/x.ts" }));
      const parsed = JSON.parse(raw) as { symbols: Array<{ name: string }> };
      expect(parsed.symbols.map((s) => s.name)).toEqual(["f"]);
    });
  });

  describe("find_in_code", () => {
    it("finds an identifier across roles", async () => {
      writeFileSync(join(tmp, "a.ts"), "function foo(){}\nfoo();\nconst x = foo;\n");
      const raw = await registry.dispatch(
        "find_in_code",
        JSON.stringify({ path: "a.ts", name: "foo" }),
      );
      const parsed = JSON.parse(raw) as {
        matches: Array<{ kind: string; line: number }>;
      };
      const kinds = parsed.matches.map((m) => m.kind).sort();
      expect(kinds).toEqual(["call", "definition", "reference"]);
    });

    it("respects kind filter", async () => {
      writeFileSync(
        join(tmp, "a.ts"),
        "function helper(){}\nhelper();\nhelper();\nconst r = helper;\n",
      );
      const raw = await registry.dispatch(
        "find_in_code",
        JSON.stringify({ path: "a.ts", name: "helper", kind: "call" }),
      );
      const parsed = JSON.parse(raw) as { matches: Array<{ kind: string }> };
      expect(parsed.matches.length).toBe(2);
      expect(parsed.matches.every((m) => m.kind === "call")).toBe(true);
    });

    it("reports unsupported language", async () => {
      writeFileSync(join(tmp, "a.cpp"), "int main(){}\n");
      const raw = await registry.dispatch(
        "find_in_code",
        JSON.stringify({ path: "a.cpp", name: "main" }),
      );
      const parsed = JSON.parse(raw) as { error?: string };
      expect(parsed.error).toMatch(/language not supported/);
    });

    it("finds call sites in Rust", async () => {
      writeFileSync(join(tmp, "a.rs"), "fn helper(){}\nfn main(){ helper(); helper(); }\n");
      const raw = await registry.dispatch(
        "find_in_code",
        JSON.stringify({ path: "a.rs", name: "helper", kind: "call" }),
      );
      const parsed = JSON.parse(raw) as { matches: Array<{ kind: string }> };
      expect(parsed.matches.length).toBe(2);
    });
  });

  describe("registration", () => {
    it("registers both tools as read-only and parallel-safe", () => {
      const specs = registry.specs();
      const names = specs.map((s) => s.function.name);
      expect(names).toContain("get_symbols");
      expect(names).toContain("find_in_code");
    });
  });
});