import { beforeEach, describe, expect, it, vi } from "vitest";

const mockIsConvexEnabled = vi.fn(() => true);
const mockRequireAuthContext = vi.fn();
const mockGetConvexAuthedHttpClient = vi.fn();
const mockBuildDocumentBridgeContext = vi.fn();
const mockBuildDocumentCommandEnvelope = vi.fn();
const mockResolveRustBridgeCommandPlan = vi.fn();
const mockExecuteRustBridgeMutationTransport = vi.fn();
const mockRecordRustBridgeCommandArtifacts = vi.fn();
const mockMaterializeRustTreeStreamDelta = vi.fn();
const mockMaterializeRustTreeDomainEventPlan = vi.fn();
const mockReadRustTreeDomainEventType = vi.fn();
const mockRecordBridgeCommandArtifacts = vi.fn();

vi.mock("@/lib/convex/enabled", () => ({
  isConvexEnabled: () => mockIsConvexEnabled(),
}));

vi.mock("@/lib/auth/authContext", () => ({
  HttpError: class HttpError extends Error {
    status: number;

    constructor(status: number, message: string) {
      super(message);
      this.status = status;
    }
  },
  requireAuthContext: () => mockRequireAuthContext(),
}));

vi.mock("@/lib/convex/api", () => ({
  api: {
    mediaAssets: {
      listByIds: "mediaAssets:listByIds",
      patchById: "mediaAssets:patchById",
    },
    documents: {
      getMeta: "documents:getMeta",
    },
  },
}));

vi.mock("@/lib/convex/server", () => ({
  getConvexAuthedHttpClient: () => mockGetConvexAuthedHttpClient(),
}));

vi.mock("@/lib/documents/bridge", () => ({
  buildDocumentBridgeContext: (...args: unknown[]) => mockBuildDocumentBridgeContext(...args),
  buildDocumentCommandEnvelope: (...args: unknown[]) => mockBuildDocumentCommandEnvelope(...args),
}));

vi.mock("@/lib/documents/rust-runtime", () => ({
  resolveRustBridgeCommandPlan: (...args: unknown[]) => mockResolveRustBridgeCommandPlan(...args),
  executeRustBridgeMutationTransport: (...args: unknown[]) =>
    mockExecuteRustBridgeMutationTransport(...args),
  recordRustBridgeCommandArtifacts: (...args: unknown[]) =>
    mockRecordRustBridgeCommandArtifacts(...args),
  materializeRustTreeStreamDelta: (...args: unknown[]) =>
    mockMaterializeRustTreeStreamDelta(...args),
  materializeRustTreeDomainEventPlan: (...args: unknown[]) =>
    mockMaterializeRustTreeDomainEventPlan(...args),
  readRustTreeDomainEventType: (...args: unknown[]) => mockReadRustTreeDomainEventType(...args),
}));

vi.mock("@/lib/documents/bridge-log", () => ({
  recordBridgeCommandArtifacts: (...args: unknown[]) => mockRecordBridgeCommandArtifacts(...args),
}));

vi.mock("@/lib/url/proxyForBrowser", () => ({
  maybeProxyForBrowserUrl: (_request: Request, url: string) => url,
}));

describe("/api/media/batch route", () => {
  beforeEach(() => {
    vi.resetModules();
    mockIsConvexEnabled.mockReset().mockReturnValue(true);
    mockRequireAuthContext.mockReset().mockResolvedValue({
      userId: "user_1",
    });
    mockBuildDocumentBridgeContext.mockReset().mockResolvedValue({
      requestId: "req_1",
      traceId: "trace_1",
      workspaceId: "ws_1",
      actor: { actorType: "user", actorId: "user_1", sessionId: null },
      source: { channel: "next-route", client: "vitest" },
      tenantId: null,
      deploymentId: null,
      projectId: null,
      authToken: null,
      idempotencyKey: null,
      validateOnly: false,
      dryRun: false,
    });
    mockBuildDocumentCommandEnvelope.mockImplementation((input: unknown) => ({
      ...(input as Record<string, unknown>),
      commandId: "cmd_asset_1",
      idempotencyKey: null,
    }));
    mockResolveRustBridgeCommandPlan.mockReset().mockResolvedValue({
      kind: "command",
      commandName: "tree.resource.move",
      commandId: "cmd_asset_1",
      functionName: "mediaAssets:batchMove",
      argsJson: {
        domainEventPlan: {
          family: "tree",
          schema: "mnote.tree.domain_event",
          schemaVersion: 1,
          eventType: "tree.resource.moved",
        },
        domainEventHint: {
          family: "tree",
          eventType: "tree.resource.moved",
        },
      },
    });
    mockExecuteRustBridgeMutationTransport.mockReset().mockResolvedValue({
      items: [
        {
          id: "asset_1",
          workspace_id: "ws_1",
          document_id: "doc_target",
          asset_type: "file",
          file_url: "/file.pdf",
          thumbnail_url: "/file.pdf",
          file_name: "file.pdf",
          file_size: 1024,
          mime_type: "application/pdf",
          ocr_text: null,
          ocr_status: null,
          created_at: "2026-04-26T00:00:00Z",
          updated_at: "2026-04-26T00:00:00Z",
        },
      ],
    });
    mockRecordRustBridgeCommandArtifacts.mockReset().mockResolvedValue(null);
    mockMaterializeRustTreeStreamDelta.mockReset().mockReturnValue({
        op: "upsert_assets",
        upsertAssets: [{ id: "asset_1" }],
      });
    mockMaterializeRustTreeDomainEventPlan.mockReset().mockReturnValue({
      family: "tree",
      schema: "mnote.tree.domain_event",
      schemaVersion: 1,
      eventType: "tree.resource.moved",
      streamDelta: {
        op: "upsert_assets",
        upsertAssets: [{ id: "asset_1" }],
      },
    });
    mockReadRustTreeDomainEventType.mockReset().mockReturnValue("tree.resource.moved");
    mockRecordBridgeCommandArtifacts.mockReset().mockResolvedValue(undefined);
  });

  it("copy/move 应进入 Rust resource command plan 并记录资源 delta artifact", async () => {
    const client = {
      query: vi.fn(async (name: string, args: Record<string, unknown>) => {
        if (name === "mediaAssets:listByIds") {
          expect(args).toEqual({ userId: "user_1", ids: ["asset_1"] });
          return [{ id: "asset_1", workspace_id: "ws_1" }];
        }
        if (name === "documents:getMeta") {
          expect(args).toEqual({ id: "doc_target" });
          return { id: "doc_target", workspace_id: "ws_1" };
        }
        return null;
      }),
      mutation: vi.fn(),
    };
    mockGetConvexAuthedHttpClient.mockResolvedValue(client);

    const { POST } = await import("./route");
    const response = await POST(
      new Request("http://localhost/api/media/batch", {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({
          action: "move",
          assetIds: ["asset_1"],
          targetDocumentId: "doc_target",
          targetSubPath: "mindmaps/mind_1",
        }),
      }),
    );

    expect(response.status).toBe(200);
    expect(await response.json()).toMatchObject({
      items: [
        {
          id: "asset_1",
          document_id: "doc_target",
        },
      ],
    });
    expect(mockBuildDocumentCommandEnvelope).toHaveBeenCalledWith(
      expect.objectContaining({
        name: "tree.resource.move",
        payload: {
          assetIds: ["asset_1"],
          targetDocumentId: "doc_target",
          targetSubPath: "mindmaps/mind_1",
        },
        target: {
          workspaceId: "ws_1",
          pageId: "doc_target",
        },
      }),
    );
    expect(mockExecuteRustBridgeMutationTransport).toHaveBeenCalledWith(
      expect.objectContaining({
        client,
        plan: expect.objectContaining({
          functionName: "mediaAssets:batchMove",
        }),
      }),
    );
    expect(mockRecordRustBridgeCommandArtifacts).toHaveBeenCalledWith(
      expect.objectContaining({
        envelope: expect.objectContaining({
          name: "tree.resource.move",
        }),
        plan: expect.objectContaining({
          commandName: "tree.resource.move",
          functionName: "mediaAssets:batchMove",
        }),
        result: expect.objectContaining({
          items: [expect.objectContaining({ id: "asset_1" })],
        }),
      }),
    );
    expect(mockRecordBridgeCommandArtifacts).not.toHaveBeenCalled();
  });
});
