import { mkdir, mkdtemp, readFile, realpath, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { Readable } from "node:stream";
import { describe, expect, it, vi } from "vitest";
import { claudeSdkToolConfigForRole, cphHubMcpToolsForRole } from "../../src/agent/roleTools.js";
import type { FeishuRuntime } from "../../src/feishu/client.js";
import { downloadFeishuMessageResource } from "../../src/feishu/download.js";

const itOnLinux = process.platform === "linux" ? it : it.skip;

describe("Feishu message resource download", () => {
  it("exposes the download tool to default and explicitly configured roles", () => {
    expect(cphHubMcpToolsForRole(undefined)).toContain("feishu_download_resource");
    expect(cphHubMcpToolsForRole(["feishu_download_resource"])).toEqual(["feishu_download_resource"]);
    expect(claudeSdkToolConfigForRole(["feishu_download_resource"]).allowedTools).toEqual([
      "mcp__cph_hub__feishu_download_resource",
    ]);
  });

  itOnLinux("downloads a bound-chat image into the project inbox", async () => {
    const workspaceRoot = await mkdtemp(join(tmpdir(), "hub-feishu-download-"));
    const workspaceDir = join(workspaceRoot, "project");
    await mkdir(workspaceDir);
    try {
      const messageGet = vi.fn(async () => ({ data: { items: [{ chat_id: "chat-1" }] } }));
      const messageResourceGet = vi.fn(async () => ({
        getReadableStream: () => Readable.from([Buffer.from("image bytes")]),
      }));
      const rt = mockRuntime(messageGet, messageResourceGet);

      const result = await downloadFeishuMessageResource(
        { messageId: "message-1", fileKey: "img-key-1", resourceType: "image" },
        { boundChatId: "chat-1", workspaceRoot, workspaceDir },
        rt,
      );

      expect(result).toMatchObject({ resourceType: "image" });
      expect(result.path).toMatch(
        new RegExp(`^${escapeRegExp(join(await realpath(workspaceDir), ".cph", "inbox"))}`),
      );
      expect(result.path).toMatch(/\.png$/);
      await expect(readFile(result.path, "utf8")).resolves.toBe("image bytes");
      expect(messageResourceGet).toHaveBeenCalledWith({
        params: { type: "image" },
        path: { message_id: "message-1", file_key: "img-key-1" },
      });
    } finally {
      await rm(workspaceRoot, { recursive: true, force: true });
    }
  });

  it("rejects resources from a message outside the bound chat", async () => {
    const messageGet = vi.fn(async () => ({ data: { items: [{ chat_id: "chat-other" }] } }));
    const messageResourceGet = vi.fn();
    const rt = mockRuntime(messageGet, messageResourceGet);

    await expect(downloadFeishuMessageResource(
      { messageId: "message-other", fileKey: "img-key-other", resourceType: "image" },
      { boundChatId: "chat-1", workspaceRoot: "/tmp", workspaceDir: "/tmp/project-1" },
      rt,
    )).rejects.toThrow("current project's bound chat");
    expect(messageResourceGet).not.toHaveBeenCalled();
  });
});

function mockRuntime(
  messageGet: (payload: unknown) => Promise<unknown>,
  messageResourceGet: (payload: unknown) => Promise<unknown>,
): FeishuRuntime {
  return {
    client: {
      im: {
        v1: {
          message: { get: messageGet },
          messageResource: { get: messageResourceGet },
        },
      },
    } as unknown as FeishuRuntime["client"],
    logger: {
      info() {},
      warn() {},
      error() {},
      debug() {},
      fatal() {},
      child() { return this; },
      level: "silent",
    } as unknown as FeishuRuntime["logger"],
  };
}

function escapeRegExp(value: string): string {
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
