import { mkdir, mkdtemp, readdir, rm, symlink, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { removeAbandonedMessageResourceStages, stageMessageResources } from "../../src/feishu/resourceStaging.js";
import type { FeishuRuntime } from "../../src/feishu/client.js";

const roots: string[] = [];

describe("Feishu resource staging recovery", () => {
  afterEach(async () => {
    await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
  });

  it("removes every abandoned batch during startup recovery", async () => {
    const root = await tempRoot();
    const batch = join(root, ".cph-staging", "abandoned-batch");
    await mkdir(batch, { recursive: true });
    await writeFile(join(batch, "resource-0"), "sensitive attachment");

    await expect(removeAbandonedMessageResourceStages(root)).resolves.toBe(1);
    await expect(readdir(join(root, ".cph-staging"))).resolves.toEqual([]);
  });

  it("refuses to traverse a staging-base symlink", async () => {
    const root = await tempRoot();
    const outside = await tempRoot();
    await symlink(outside, join(root, ".cph-staging"));

    await expect(removeAbandonedMessageResourceStages(root)).rejects.toThrow(
      /staging base is not a real directory/,
    );
  });

  it("rejects too many resources before contacting Feishu", async () => {
    const root = await tempRoot();
    await expect(stageMessageResources(
      {} as FeishuRuntime,
      "message-1",
      [
        { fileKey: "a", resourceType: "file", workspaceRelativePath: "a" },
        { fileKey: "b", resourceType: "file", workspaceRelativePath: "b" },
      ],
      root,
      { maxFiles: 1, maxBytesPerFile: 10 },
    )).rejects.toThrow("2 resources; limit is 1");
  });
});

async function tempRoot(): Promise<string> {
  const root = await mkdtemp(join(tmpdir(), "cph-resource-staging-test-"));
  roots.push(root);
  return root;
}
