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 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: {
      generateUploadUrl: "mediaAssets:generateUploadUrl",
      createWithStorage: "mediaAssets:createWithStorage",
    },
  },
}));

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),
  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/upload route", () => {
  beforeEach(() => {
    vi.resetModules();
    vi.restoreAllMocks();
    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_upload_asset",
      idempotencyKey: null,
    }));
    mockResolveRustBridgeCommandPlan.mockReset().mockResolvedValue({
      kind: "command",
      commandName: "tree.resource.upload",
      commandId: "cmd_upload_asset",
      functionName: "mediaAssets:createWithStorage",
      workspaceId: "ws_1",
      requestId: "req_1",
      traceId: "trace_1",
      actorId: "user_1",
      idempotencyKey: null,
      payloadJson: "{}",
      argsJson: {
        domainEventPlan: {
          family: "tree",
          schema: "mnote.tree.domain_event",
          schemaVersion: 1,
          eventType: "tree.resource.uploaded",
        },
        assetId: "asset_upload_1",
        workspaceId: "ws_1",
        targetDocumentId: "doc_target",
        targetSubPath: "mindmaps/mind_1",
        fileName: "demo.pdf",
        fileSize: 7,
        mimeType: "application/pdf",
        assetType: "file",
        resourceUploadPlan: {
          action: "upload",
          assetId: "asset_upload_1",
          workspaceId: "ws_1",
          targetDocumentId: "doc_target",
          targetSubPath: "mindmaps/mind_1",
          fileName: "demo.pdf",
          fileSize: 7,
          mimeType: "application/pdf",
          assetType: "file",
        },
      },
    });
    mockMaterializeRustTreeStreamDelta.mockReset().mockReturnValue({
      op: "upsert_assets",
      upsertAssets: [{ id: "asset_upload_1" }],
    });
    mockMaterializeRustTreeDomainEventPlan.mockReset().mockReturnValue({
      family: "tree",
      schema: "mnote.tree.domain_event",
      schemaVersion: 1,
      eventType: "tree.resource.uploaded",
      streamDelta: {
        op: "upsert_assets",
        upsertAssets: [{ id: "asset_upload_1" }],
      },
    });
    mockReadRustTreeDomainEventType.mockReset().mockReturnValue("tree.resource.uploaded");
    mockRecordRustBridgeCommandArtifacts.mockReset().mockResolvedValue(null);
    mockRecordBridgeCommandArtifacts.mockReset().mockResolvedValue(undefined);
  });

  it("上传应通过 tree.resource.upload plan 写入资源元数据并记录资源 delta artifact", async () => {
    const createdAsset = {
      id: "asset_upload_1",
      workspace_id: "ws_1",
      document_id: "doc_target",
      asset_type: "file",
      file_url: "/files/demo.pdf",
      thumbnail_url: "/files/demo.pdf",
      file_name: "demo.pdf",
      file_size: 7,
      mime_type: "application/pdf",
      created_at: "2026-04-26T00:00:00Z",
      updated_at: "2026-04-26T00:00:00Z",
    };
    const client = {
      mutation: vi.fn(async (name: string, args: Record<string, unknown>) => {
        if (name === "mediaAssets:generateUploadUrl") {
          expect(args).toEqual({ userId: "user_1" });
          return "https://convex.test/upload";
        }
        if (name === "mediaAssets:createWithStorage") {
          expect(args).toMatchObject({
            userId: "user_1",
            storageId: "storage_1",
            targetSubPath: "mindmaps/mind_1",
            resourceUploadPlan: {
              action: "upload",
              assetId: "asset_upload_1",
              targetDocumentId: "doc_target",
            },
            asset: {
              id: "asset_upload_1",
              workspace_id: "ws_1",
              document_id: "doc_target",
              file_name: "demo.pdf",
            },
          });
          return createdAsset;
        }
        return null;
      }),
    };
    mockGetConvexAuthedHttpClient.mockResolvedValue(client);
    vi.spyOn(globalThis, "fetch").mockResolvedValue(
      new Response(JSON.stringify({ storageId: "storage_1" }), {
        status: 200,
        headers: { "content-type": "application/json" },
      }),
    );

    const { POST } = await import("./route");
    const formData = new FormData();
    formData.append("file", new Blob(["content"], { type: "application/pdf" }), "demo.pdf");
    formData.set("workspaceId", "ws_1");
    formData.set("documentId", "doc_target");
    formData.set("mindmapId", "mind_1");

    const response = await POST(
      new Request("http://localhost/api/media/upload", {
        method: "POST",
        body: formData,
      }),
    );

    expect(response.status).toBe(200);
    expect(await response.json()).toMatchObject({
      asset: {
        id: "asset_upload_1",
        document_id: "doc_target",
      },
      mindmapUrl: "asset:asset_upload_1",
    });
    expect(mockBuildDocumentCommandEnvelope).toHaveBeenCalledWith(
      expect.objectContaining({
        name: "tree.resource.upload",
        payload: expect.objectContaining({
          workspaceId: "ws_1",
          targetDocumentId: "doc_target",
          targetSubPath: "mindmaps/mind_1",
          fileName: expect.any(String),
        }),
      }),
    );
    expect(mockRecordRustBridgeCommandArtifacts).toHaveBeenCalledWith(
      expect.objectContaining({
        envelope: expect.objectContaining({
          name: "tree.resource.upload",
        }),
        plan: expect.objectContaining({
          commandName: "tree.resource.upload",
          functionName: "mediaAssets:createWithStorage",
        }),
        result: expect.objectContaining({
          items: [expect.objectContaining({ id: "asset_upload_1" })],
        }),
      }),
    );
    expect(mockRecordBridgeCommandArtifacts).not.toHaveBeenCalled();
  });
});
