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

const mockIsConvexEnabled = vi.fn();
const mockGetAuthedConvexClient = vi.fn();
const mockBuildDocumentBridgeContextWithActor = vi.fn();
const mockBuildDocumentQueryEnvelope = vi.fn();
const mockExecuteRustBridgeQuery = vi.fn();
const mockExecuteRustBridgeQueryTransport = vi.fn();
const mockResolveRustBridgeQueryPlan = vi.fn();
const mockResolveKernelFileTreeProjection = vi.fn();
const mockStreamTreeFrames = vi.fn();

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

vi.mock("@/lib/convex/route", () => ({
  getAuthedConvexClient: () => mockGetAuthedConvexClient(),
}));

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

vi.mock("@/lib/documents/bridge", () => ({
  buildDocumentBridgeContextWithActor: (...args: unknown[]) =>
    mockBuildDocumentBridgeContextWithActor(...args),
  buildDocumentQueryEnvelope: (...args: unknown[]) => mockBuildDocumentQueryEnvelope(...args),
}));

vi.mock("@/lib/documents/rust-runtime", () => ({
  executeRustBridgeQuery: (...args: unknown[]) => mockExecuteRustBridgeQuery(...args),
  executeRustBridgeQueryTransport: (...args: unknown[]) => mockExecuteRustBridgeQueryTransport(...args),
  resolveRustBridgeQueryPlan: (...args: unknown[]) => mockResolveRustBridgeQueryPlan(...args),
}));

vi.mock("@/lib/server/kernel-file-tree", async () => {
  const actual = await vi.importActual<typeof import("@/lib/server/kernel-file-tree")>(
    "@/lib/server/kernel-file-tree",
  );
  return {
    ...actual,
    resolveKernelFileTreeProjection: (...args: unknown[]) =>
      mockResolveKernelFileTreeProjection(...args),
  };
});

vi.mock("@/lib/tree-stream/server", () => ({
  streamTreeFrames: (...args: unknown[]) => mockStreamTreeFrames(...args),
}));

async function* makeFrames() {
  yield {
    event: "snapshot",
    payload: {
      kind: "snapshot",
      stream: "workspace",
      workspaceId: "ws_1",
      rootNodeId: null,
      cursor: "cursor_1",
      projection: "sidebar_tree",
      requestId: "req_1",
      traceId: "trace_1",
      data: { activeWorkspaceId: "ws_1", documents: [] },
      snapshot: { dataset: { active_workspace_id: "ws_1", documents: [] } },
      overview: { command_logs: [], domain_events: [] },
    },
  };
}

describe("/api/mnote-web/stream route", () => {
  beforeEach(() => {
    vi.resetModules();
    mockIsConvexEnabled.mockReset().mockReturnValue(true);
    mockGetAuthedConvexClient.mockReset().mockResolvedValue({
      auth: { userId: "user_1" },
      client: { query: vi.fn(), mutation: vi.fn() },
    });
    mockBuildDocumentBridgeContextWithActor.mockReset().mockReturnValue({
      requestId: "req_1",
      traceId: "trace_1",
      actor: { actorType: "user", actorId: "user_1", sessionId: null },
    });
    mockBuildDocumentQueryEnvelope.mockReset().mockImplementation((input) => input);
    mockResolveRustBridgeQueryPlan.mockReset().mockResolvedValue({
      argsJson: { workspaceId: "ws_1" },
      functionName: "bridgeLogs:listWorkspaceOverview",
    });
    mockExecuteRustBridgeQuery.mockReset();
    mockExecuteRustBridgeQueryTransport.mockImplementation(async ({ plan }) => {
      if (plan?.functionName === "bridgeLogs:listWorkspaceOverview") {
        return {
          command_logs: [],
          domain_events: [],
        };
      }
      return {
        active_workspace_id: "ws_1",
        workspaces: [],
        documents: [],
        trashed_documents: [],
        media_assets: [],
        trashed_media_assets: [],
        mindmap_assets: [],
        trashed_mindmap_assets: [],
        table_assets: [],
        trashed_table_assets: [],
        mindmap_docs: [],
        mindmap_asset_children: {},
      };
    });
    mockResolveKernelFileTreeProjection.mockReset().mockResolvedValue({
      projectionId: "kernel_projection:file_tree:workspace_root",
      projection: "file_tree",
      rootNodeId: null,
      items: [],
      edges: [],
    });
    mockStreamTreeFrames.mockReset().mockReturnValue(makeFrames());
  });

  it("应返回 410，明确要求改用 /api/tree/events，不再代理 mnote-web:3104", async () => {
    const fetchSpy = vi.spyOn(globalThis, "fetch");

    const { GET } = await import("./route");
    const response = await GET(
      new Request(
        "http://127.0.0.1:3000/api/mnote-web/stream?workspaceId=ws_1&cursor=evt_9&rootNodeId=page_root&pollMs=500&maxPolls=0",
        { method: "GET" },
      ),
    );

    expect(response.status).toBe(410);
    expect(response.headers.get("x-mnote-compat-boundary")).toBe(
      "mnote-web-stream-alias-retired",
    );
    expect(await response.json()).toMatchObject({
      error: "Next /api/mnote-web/stream compat alias 已退场，请直接使用 /api/tree/events。",
      redirectTo:
        "http://127.0.0.1:3000/api/tree/events?workspaceId=ws_1&cursor=evt_9&rootNodeId=page_root&pollMs=500&maxPolls=0",
    });
    expect(fetchSpy).not.toHaveBeenCalled();
    expect(mockExecuteRustBridgeQuery).not.toHaveBeenCalled();
    expect(mockStreamTreeFrames).not.toHaveBeenCalled();
  });

  it("Convex 未启用时仍返回 retired alias，不探测 3104", async () => {
    mockIsConvexEnabled.mockReturnValue(false);
    const fetchSpy = vi.spyOn(globalThis, "fetch");

    const { GET } = await import("./route");
    const response = await GET(
      new Request("http://127.0.0.1:3000/api/mnote-web/stream?workspaceId=ws_1", {
        method: "GET",
      }),
    );

    expect(response.status).toBe(410);
    expect(fetchSpy).not.toHaveBeenCalled();
    expect(await response.json()).toMatchObject({
      error: "Next /api/mnote-web/stream compat alias 已退场，请直接使用 /api/tree/events。",
      redirectTo: "http://127.0.0.1:3000/api/tree/events?workspaceId=ws_1",
    });
  });
});
