import { afterEach, describe, expect, it, vi } from "vitest";
import {
  copyFileTreeResourceAssets,
  deleteFileTreeResourceAssets,
  preflightFileTreeDelete,
  preflightFileTreeInternalDrop,
  preflightFileTreePaste,
  preflightFileTreeUploadTarget,
  moveFileTreeResourceAssets,
  renameFileTreeResourceAsset,
  restoreFileTreeResourceAssets,
  uploadFileTreeResourceAsset,
} from "./resource-command-client";

describe("file-tree resource command client", () => {
  afterEach(() => {
    vi.restoreAllMocks();
  });

  it("copy/move 应通过统一资源 command client 发送到 media batch route", async () => {
    const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue({
      ok: true,
      json: async () => ({ items: [{ id: "asset_1" }] }),
    } as Response);

    await copyFileTreeResourceAssets({
      assetIds: ["asset_1", "asset_1", " asset_2 "],
      targetDocumentId: "doc_target",
      targetSubPath: "mindmaps/mind_1",
    });
    await moveFileTreeResourceAssets({
      assetIds: ["asset_3"],
      targetDocumentId: "doc_target_2",
    });

    expect(fetchMock).toHaveBeenCalledTimes(2);
    expect(fetchMock).toHaveBeenNthCalledWith(
      1,
      "/api/media/batch",
      expect.objectContaining({
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          action: "copy",
          assetIds: ["asset_1", "asset_2"],
          targetDocumentId: "doc_target",
          targetSubPath: "mindmaps/mind_1",
        }),
      }),
    );
    expect(fetchMock).toHaveBeenNthCalledWith(
      2,
      "/api/media/batch",
      expect.objectContaining({
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          action: "move",
          assetIds: ["asset_3"],
          targetDocumentId: "doc_target_2",
        }),
      }),
    );
  });

  it("rename/delete/restore 也应复用同一 batch transport 边界", async () => {
    const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue({
      ok: true,
      json: async () => ({ ok: true }),
    } as Response);

    await renameFileTreeResourceAsset({ assetId: "asset_1", newName: "新文件.pdf" });
    await deleteFileTreeResourceAssets(["asset_1", "asset_2"]);
    await restoreFileTreeResourceAssets(["asset_3"]);

    expect(fetchMock.mock.calls.map((call) => JSON.parse(String(call[1]?.body)))).toEqual([
      { action: "rename", assetIds: ["asset_1"], newName: "新文件.pdf" },
      { action: "delete", assetIds: ["asset_1", "asset_2"] },
      { action: "restore", assetIds: ["asset_3"] },
    ]);
  });

  it("后端返回错误时应抛出稳定 fallback 或服务端消息", async () => {
    vi.spyOn(globalThis, "fetch").mockResolvedValue({
      ok: false,
      json: async () => ({ error: "Rust resource preflight rejected" }),
    } as Response);

    await expect(
      moveFileTreeResourceAssets({
        assetIds: ["asset_1"],
        targetDocumentId: "doc_target",
      }),
    ).rejects.toThrow("Rust resource preflight rejected");
  });

  it("upload 应通过统一资源 command client 构造 FormData transport", async () => {
    const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue({
      ok: true,
      json: async () => ({ asset: { id: "asset_upload_1" } }),
    } as Response);
    const file = new File(["content"], "demo.pdf", { type: "application/pdf" });

    await uploadFileTreeResourceAsset({
      file,
      workspaceId: "ws_1",
      documentId: "doc_1",
      mindmapId: "mind_1",
    });

    expect(fetchMock).toHaveBeenCalledWith(
      "/api/media/upload",
      expect.objectContaining({
        method: "POST",
        body: expect.any(FormData),
      }),
    );
    const body = fetchMock.mock.calls[0]?.[1]?.body as FormData;
    expect(body.get("file")).toBe(file);
    expect(body.get("workspaceId")).toBe("ws_1");
    expect(body.get("documentId")).toBe("doc_1");
    expect(body.get("mindmapId")).toBe("mind_1");
  });

  it("internal drop preflight 应发送到 tree filetree drop route 并返回 Rust plan", async () => {
    const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue({
      ok: true,
      json: async () => ({
        plan: {
          copy: false,
          targetDocumentId: "doc_target",
          targetMindmapId: null,
          targetSubPath: null,
          rowIds: ["doc:doc_1"],
          docIds: ["doc_1"],
          topLevelDocIds: ["doc_1"],
          copyableAssetIds: [],
          sourceAssetDocumentIds: [],
          documentTransferPlan: {
            action: "move",
            targetParentId: "doc_target",
            documentIds: ["doc_1"],
            topLevelDocumentIds: ["doc_1"],
            copyItems: [{ documentId: "doc_1", recursive: true }],
          },
          resourceTransferPlan: null,
        },
      }),
    } as Response);

    const plan = await preflightFileTreeInternalDrop({
      workspaceId: "ws_1",
      copy: false,
      targetDocumentId: "doc_target",
      targetRowId: null,
      focusedRowId: null,
      activeDocumentId: null,
      rowIds: ["doc:doc_1"],
      rows: [],
      documentParents: [],
    });

    expect(plan.topLevelDocIds).toEqual(["doc_1"]);
    expect(plan.documentTransferPlan?.topLevelDocumentIds).toEqual(["doc_1"]);
    expect(fetchMock).toHaveBeenCalledWith(
      "/api/tree/filetree/drop-preflight",
      expect.objectContaining({
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          workspaceId: "ws_1",
          copy: false,
          targetDocumentId: "doc_target",
          targetRowId: null,
          focusedRowId: null,
          activeDocumentId: null,
          rowIds: ["doc:doc_1"],
          rows: [],
          documentParents: [],
        }),
      }),
    );
  });

  it("delete preflight 应发送到 tree filetree delete route 并返回 Rust plan", async () => {
    const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue({
      ok: true,
      json: async () => ({
        plan: {
          rowIds: ["doc:doc_1", "asset:asset_1"],
          docIds: ["doc_1"],
          assetIds: ["asset_1"],
          assetDocumentIds: ["doc_other"],
        },
      }),
    } as Response);

    const plan = await preflightFileTreeDelete({
      workspaceId: "ws_1",
      rowIds: ["doc:doc_1", "asset:asset_1"],
      rows: [],
      documentParents: [],
    });

    expect(plan.docIds).toEqual(["doc_1"]);
    expect(plan.assetIds).toEqual(["asset_1"]);
    expect(fetchMock).toHaveBeenCalledWith(
      "/api/tree/filetree/delete-preflight",
      expect.objectContaining({
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          workspaceId: "ws_1",
          rowIds: ["doc:doc_1", "asset:asset_1"],
          rows: [],
          documentParents: [],
        }),
      }),
    );
  });

  it("paste preflight 应发送到 tree filetree paste route 并返回 Rust plan", async () => {
    const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue({
      ok: true,
      json: async () => ({
        plan: {
          targetDocumentId: "doc_target",
          targetMindmapId: "mind_1",
          targetSubPath: "mindmaps/mind_1",
          rowIds: ["index:doc_1", "asset:asset_1"],
          docItems: [{ documentId: "doc_1", recursive: false }],
          copyableAssetIds: ["asset_1"],
          resourceTransferPlan: {
            action: "copy",
            assetIds: ["asset_1"],
            targetDocumentId: "doc_target",
            targetSubPath: "mindmaps/mind_1",
          },
        },
      }),
    } as Response);

    const plan = await preflightFileTreePaste({
      workspaceId: "ws_1",
      targetDocumentId: null,
      focusedRowId: "asset-folder:mind_1",
      activeDocumentId: "doc_active",
      rowIds: ["index:doc_1", "asset:asset_1"],
      rows: [],
    });

    expect(plan.docItems).toEqual([{ documentId: "doc_1", recursive: false }]);
    expect(plan.resourceTransferPlan?.targetSubPath).toBe("mindmaps/mind_1");
    expect(fetchMock).toHaveBeenCalledWith(
      "/api/tree/filetree/paste-preflight",
      expect.objectContaining({
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          workspaceId: "ws_1",
          targetDocumentId: null,
          focusedRowId: "asset-folder:mind_1",
          activeDocumentId: "doc_active",
          rowIds: ["index:doc_1", "asset:asset_1"],
          rows: [],
        }),
      }),
    );
  });

  it("upload target preflight 应发送到 tree filetree upload-target route 并返回 Rust plan", async () => {
    const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue({
      ok: true,
      json: async () => ({
        plan: {
          workspaceId: "ws_1",
          targetDocumentId: "doc_target",
          targetMindmapId: "mind_1",
          targetSubPath: "mindmaps/mind_1",
        },
      }),
    } as Response);

    const plan = await preflightFileTreeUploadTarget({
      workspaceId: "ws_fallback",
      targetDocumentId: null,
      targetRowId: "asset:asset_child_1",
      focusedRowId: null,
      activeDocumentId: "doc_active",
      rows: [],
      documentWorkspaces: [{ documentId: "doc_target", workspaceId: "ws_1" }],
    });

    expect(plan).toEqual({
      workspaceId: "ws_1",
      targetDocumentId: "doc_target",
      targetMindmapId: "mind_1",
      targetSubPath: "mindmaps/mind_1",
    });
    expect(fetchMock).toHaveBeenCalledWith(
      "/api/tree/filetree/upload-target-preflight",
      expect.objectContaining({
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          workspaceId: "ws_fallback",
          targetDocumentId: null,
          targetRowId: "asset:asset_child_1",
          focusedRowId: null,
          activeDocumentId: "doc_active",
          rows: [],
          documentWorkspaces: [{ documentId: "doc_target", workspaceId: "ws_1" }],
        }),
      }),
    );
  });
});
