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

const mockQuery = vi.fn();
const mockMutation = vi.fn();
const mockExecuteMediaAssetWritebackBridgeCommand = vi.fn();
const mockPrepareOnlyOfficeCallback = vi.fn();

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

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

vi.mock("@/lib/api-utils", () => ({
  apiErrorResponse: vi.fn((message: string, status = 500, details?: unknown) => ({
    message,
    status,
    details,
  })),
}));

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

vi.mock("@/lib/convex/server", () => ({
  getConvexHttpClient: () => ({
    query: mockQuery,
    mutation: mockMutation,
  }),
}));

vi.mock("@/lib/convex/api", () => ({
  api: {
    mediaAssets: {
      getById: { name: "mediaAssets.getById" },
      generateUploadUrl: { name: "mediaAssets.generateUploadUrl" },
      replaceStorageFromUpload: { name: "mediaAssets.replaceStorageFromUpload" },
    },
    documents: {
      getPermissionForUser: { name: "documents.getPermissionForUser" },
    },
  },
}));

vi.mock("@/lib/onlyoffice/internal-url", () => ({
  resolveOnlyOfficeInternalUrl: vi.fn().mockResolvedValue("http://127.0.0.1:8082"),
}));

vi.mock("@/lib/documents/bridge", async () => {
  const actual = await vi.importActual<typeof import("@/lib/documents/bridge")>("@/lib/documents/bridge");
  return {
    ...actual,
    buildDocumentBridgeContextWithActor: vi.fn((input) => ({
      requestId: "req_test_onlyoffice_callback",
      traceId: "trace_test_onlyoffice_callback",
      deploymentId: null,
      projectId: null,
      workspaceId: input.workspaceId ?? null,
      actor: input.actor,
      source: input.source ?? { channel: "onlyoffice-callback", client: "vitest" },
      tenantId: null,
      authToken: null,
      idempotencyKey: input.idempotencyKey ?? null,
      validateOnly: false,
      dryRun: false,
    })),
  };
});

vi.mock("@/lib/documents/media-asset-command-adapter", () => ({
  executeMediaAssetWritebackBridgeCommand: mockExecuteMediaAssetWritebackBridgeCommand,
}));

vi.mock("@/lib/onlyoffice/rust-adapter", () => ({
  prepareOnlyOfficeCallback: mockPrepareOnlyOfficeCallback,
}));

describe("/api/onlyoffice/callback route", () => {
  beforeEach(() => {
    mockQuery.mockReset();
    mockMutation.mockReset();
    mockExecuteMediaAssetWritebackBridgeCommand.mockReset();
    mockPrepareOnlyOfficeCallback.mockReset();
    vi.unstubAllGlobals();
  });

  it("uses media asset bridge adapter for callback writeback", async () => {
    const { POST } = await import("./route");

    mockQuery
      .mockResolvedValueOnce({
        id: "asset_1",
        document_id: "doc_1",
        workspace_id: "ws_1",
        mime_type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
      })
      .mockResolvedValueOnce({
        permission: "edit",
      });
    mockMutation.mockResolvedValueOnce("http://127.0.0.1:3210/api/upload");
    mockExecuteMediaAssetWritebackBridgeCommand.mockResolvedValue({
      requestId: "req_test_onlyoffice_callback",
      traceId: "trace_test_onlyoffice_callback",
      commandId: "cmd_test_onlyoffice_callback",
      commandName: "media.assets.replace_storage",
    });
    mockPrepareOnlyOfficeCallback.mockResolvedValue({
      shouldWrite: true,
      downloadUrl: "http://127.0.0.1:8082/cache/files/output.docx",
      idempotencyKey: "doc_key_1",
      locator: {
        assetId: "asset_1",
        workspaceId: "ws_1",
        documentId: "doc_1",
      },
      session: {
        sessionId: "onlyoffice-callback",
        assetId: "asset_1",
        workspaceId: "ws_1",
        documentId: "doc_1",
        userId: "user_1",
      },
    });

    const fetchMock = vi.fn(async (input: RequestInfo | URL) => {
      const url = String(input);
      if (url === "http://127.0.0.1:8082/cache/files/output.docx") {
        return new Response(Buffer.from("docx-binary"), {
          status: 200,
          headers: { "Content-Type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document" },
        });
      }
      if (url === "http://127.0.0.1:3210/api/upload") {
        return Response.json({ storageId: "storage_new_1" }, { status: 200 });
      }
      throw new Error(`unexpected fetch: ${url}`);
    });
    vi.stubGlobal("fetch", fetchMock);

    const request = new Request(
      "http://127.0.0.1:3001/api/onlyoffice/callback?assetId=asset_1&userId=user_1",
      {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({
          status: 2,
          key: "doc_key_1",
          url: "http://127.0.0.1:8082/cache/files/output.docx",
        }),
      },
    );

    const response = await POST(request);
    const payload = await response.json();

    expect(payload).toEqual({ error: 0 });
    expect(mockMutation).toHaveBeenCalledTimes(1);
    expect(mockMutation.mock.calls[0]?.[1]).toEqual({ userId: "user_1" });
    expect(mockExecuteMediaAssetWritebackBridgeCommand).toHaveBeenCalledTimes(1);
    expect(mockExecuteMediaAssetWritebackBridgeCommand).toHaveBeenCalledWith({
      client: expect.any(Object),
      context: expect.objectContaining({
        workspaceId: "ws_1",
        actor: {
          actorType: "service",
          actorId: "user_1",
          sessionId: "onlyoffice-callback",
        },
        source: {
          channel: "onlyoffice-callback",
          client: "onlyoffice-document-server",
        },
        idempotencyKey: "doc_key_1",
      }),
      envelope: expect.objectContaining({
        name: "media.assets.replace_storage",
        payload: {
          assetId: "asset_1",
          documentId: "doc_1",
          workspaceId: "ws_1",
          storageId: "storage_new_1",
          userId: "user_1",
        },
        target: {
          workspaceId: "ws_1",
          pageId: "doc_1",
        },
      }),
    });
  });
});
