import { act, useEffect } from "react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createRoot, type Root } from "react-dom/client";
import type { SidebarInitialData } from "@/components/sidebar/types";
import { buildSidebarInitialData } from "@/lib/sidebar-data";
import type { DocumentRecord } from "@/lib/documents";
import { usePreferredSidebarSnapshot } from "./use-preferred-sidebar-snapshot";

function buildDocument(overrides: Partial<DocumentRecord> = {}): DocumentRecord {
  return {
    access_scope: "private",
    id: "doc-1",
    workspace_id: "ws-1",
    title: "标题 A",
    parent_id: null,
    sort_order: 0,
    is_starred: false,
    is_template: false,
    created_at: "2026-04-21T00:00:00.000Z",
    updated_at: "2026-04-21T00:00:00.000Z",
    ...overrides,
  };
}

function buildSidebarData(documents: DocumentRecord[]): SidebarInitialData {
  return buildSidebarInitialData({
    activeWorkspaceId: "ws-1",
    workspaces: [],
    documents,
    trashedDocuments: [],
    mindmaps: [],
    mediaAssets: [],
    trashedMediaAssets: [],
    tables: [],
  });
}

function Harness(props: {
  initialData: SidebarInitialData;
  sidebarQueryData: SidebarInitialData | null;
  treeStreamData: SidebarInitialData | null;
  treeStreamStatus?: "idle" | "connecting" | "live" | "fallback";
  treeStreamCursor?: string | null;
  onState: (state: ReturnType<typeof usePreferredSidebarSnapshot>) => void;
}) {
  const state = usePreferredSidebarSnapshot(props);

  useEffect(() => {
    props.onState(state);
  }, [props, state]);

  return null;
}

describe("usePreferredSidebarSnapshot", () => {
  let container: HTMLDivElement;
  let root: Root;
  const onState = vi.fn();

  beforeEach(() => {
    (globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT =
      true;
    container = document.createElement("div");
    document.body.appendChild(container);
    root = createRoot(container);
    onState.mockClear();
  });

  afterEach(() => {
    act(() => {
      root.unmount();
    });
    container.remove();
  });

  it("tree stream 仍停在 initial 时，query 新快照不应被旧 stream 压住", async () => {
    const initialData = buildSidebarData([buildDocument({ title: "标题 A" })]);
    const staleTreeStream = buildSidebarData([buildDocument({ title: "标题 A" })]);
    const refreshedQuery = buildSidebarData([
      buildDocument({
        title: "标题 B",
        updated_at: "2026-04-21T00:00:01.000Z",
      }),
    ]);
    const caughtUpTreeStream = buildSidebarData([
      buildDocument({
        title: "标题 B",
        updated_at: "2026-04-21T00:00:01.000Z",
      }),
    ]);
    await act(async () => {
      root.render(
        <Harness
          initialData={initialData}
          sidebarQueryData={initialData}
          treeStreamData={staleTreeStream}
          treeStreamStatus="live"
          onState={onState}
        />,
      );
    });

    expect(onState.mock.lastCall?.[0]).toMatchObject({
      source: "tree_stream",
      data: expect.objectContaining({
        kernelSidebarTree: [expect.objectContaining({ title: "标题 A" })],
      }),
    });

    await act(async () => {
      root.render(
        <Harness
          initialData={initialData}
          sidebarQueryData={refreshedQuery}
          treeStreamData={staleTreeStream}
          treeStreamStatus="live"
          onState={onState}
        />,
      );
    });

    expect(onState.mock.lastCall?.[0]).toMatchObject({
      source: "query",
      data: expect.objectContaining({
        kernelSidebarTree: [expect.objectContaining({ title: "标题 B" })],
      }),
    });

    await act(async () => {
      root.render(
        <Harness
          initialData={initialData}
          sidebarQueryData={refreshedQuery}
          treeStreamData={caughtUpTreeStream}
          treeStreamStatus="live"
          onState={onState}
        />,
      );
    });

    expect(onState.mock.lastCall?.[0]).toMatchObject({
      source: "tree_stream",
      data: expect.objectContaining({
        kernelSidebarTree: [expect.objectContaining({ title: "标题 B" })],
      }),
    });
  });

  it("query 版本比已更新过的 stream 更新时，应优先 query，避免旧 stream 回压", async () => {
    const initialData = buildSidebarData([buildDocument({ title: "标题 A" })]);
    const staleTreeStream = buildSidebarData([
      buildDocument({
        title: "标题 B",
        updated_at: "2026-04-21T00:00:01.000Z",
      }),
    ]);
    const refreshedQuery = buildSidebarData([
      buildDocument({
        title: "标题 C",
        updated_at: "2026-04-21T00:00:02.000Z",
      }),
    ]);

    await act(async () => {
      root.render(
        <Harness
          initialData={initialData}
          sidebarQueryData={refreshedQuery}
          treeStreamData={staleTreeStream}
          treeStreamStatus="live"
          treeStreamCursor="cursor_stream_1"
          onState={onState}
        />,
      );
    });

    expect(onState.mock.lastCall?.[0]).toMatchObject({
      source: "query",
      cursor: null,
      data: expect.objectContaining({
        kernelSidebarTree: [expect.objectContaining({ title: "标题 C" })],
      }),
    });
  });

  it("stream 与 query 版本相同时，应优先 stream 并暴露 cursor", async () => {
    const initialData = buildSidebarData([buildDocument({ title: "标题 A" })]);
    const queryData = buildSidebarData([
      buildDocument({
        title: "标题 B",
        updated_at: "2026-04-21T00:00:01.000Z",
      }),
    ]);
    const treeStreamData = buildSidebarData([
      buildDocument({
        title: "标题 B",
        updated_at: "2026-04-21T00:00:01.000Z",
      }),
    ]);

    await act(async () => {
      root.render(
        <Harness
          initialData={initialData}
          sidebarQueryData={queryData}
          treeStreamData={treeStreamData}
          treeStreamStatus="live"
          treeStreamCursor="cursor_stream_2"
          onState={onState}
        />,
      );
    });

    expect(onState.mock.lastCall?.[0]).toMatchObject({
      source: "tree_stream",
      cursor: "cursor_stream_2",
      data: expect.objectContaining({
        kernelSidebarTree: [expect.objectContaining({ title: "标题 B" })],
      }),
    });
  });

  it("stream 与 query freshness 相同但内容不同时，应优先 query 避免资源行被旧 stream 压住", async () => {
    const initialData = buildSidebarData([buildDocument({ title: "标题 A" })]);
    const treeStreamData = buildSidebarData([
      buildDocument({
        title: "标题 B",
        updated_at: "2026-04-21T00:00:01.000Z",
      }),
    ]);
    const queryData = buildSidebarInitialData({
      activeWorkspaceId: "ws-1",
      workspaces: [],
      documents: [
        buildDocument({
          title: "标题 B",
          updated_at: "2026-04-21T00:00:01.000Z",
        }),
      ],
      trashedDocuments: [],
      mindmaps: [
        {
          mindmap_id: "mind-1",
          document_id: "doc-1",
          workspace_id: "ws-1",
          created_at: "2026-04-21T00:00:01.000Z",
          updated_at: "2026-04-21T00:00:01.000Z",
        },
      ],
      mediaAssets: [],
      trashedMediaAssets: [],
      tables: [],
    });

    await act(async () => {
      root.render(
        <Harness
          initialData={initialData}
          sidebarQueryData={queryData}
          treeStreamData={treeStreamData}
          treeStreamStatus="live"
          treeStreamCursor="cursor_stream_3"
          onState={onState}
        />,
      );
    });

    expect(onState.mock.lastCall?.[0]).toMatchObject({
      source: "query",
      cursor: null,
    });
    expect(
      (onState.mock.lastCall?.[0] as ReturnType<typeof usePreferredSidebarSnapshot>).data.kernelFileTreeProjection.items.some(
        (item) => item.resourceMeta.assetId === "mind-1",
      ),
    ).toBe(true);
  });

  it("tree stream 进入 fallback 后应回退到 query 快照", async () => {
    const initialData = buildSidebarData([buildDocument({ title: "标题 A" })]);
    const staleTreeStream = buildSidebarData([buildDocument({ title: "标题 A" })]);
    const refreshedQuery = buildSidebarData([
      buildDocument({
        title: "标题 B",
        updated_at: "2026-04-21T00:00:01.000Z",
      }),
    ]);
    const caughtUpTreeStream = buildSidebarData([
      buildDocument({
        title: "标题 B",
        updated_at: "2026-04-21T00:00:01.000Z",
      }),
    ]);

    await act(async () => {
      root.render(
        <Harness
          initialData={initialData}
          sidebarQueryData={refreshedQuery}
          treeStreamData={staleTreeStream}
          treeStreamStatus="fallback"
          onState={onState}
        />,
      );
    });

    expect(onState.mock.lastCall?.[0]).toMatchObject({
      source: "query",
      data: expect.objectContaining({
        kernelSidebarTree: [expect.objectContaining({ title: "标题 B" })],
      }),
    });

    await act(async () => {
      root.render(
        <Harness
          initialData={initialData}
          sidebarQueryData={refreshedQuery}
          treeStreamData={caughtUpTreeStream}
          treeStreamStatus="live"
          onState={onState}
        />,
      );
    });

    expect(onState.mock.lastCall?.[0]).toMatchObject({
      source: "tree_stream",
      data: expect.objectContaining({
        kernelSidebarTree: [expect.objectContaining({ title: "标题 B" })],
      }),
    });
  });

  it("fallback 且 query 不可用时应回到 initial 快照，而不是继续复用旧 stream 数据", async () => {
    const initialData = buildSidebarData([buildDocument({ title: "初始标题" })]);
    const staleTreeStream = buildSidebarData([
      buildDocument({
        title: "旧 stream 标题",
        updated_at: "2026-04-21T00:00:01.000Z",
      }),
    ]);

    await act(async () => {
      root.render(
        <Harness
          initialData={initialData}
          sidebarQueryData={initialData}
          treeStreamData={staleTreeStream}
          treeStreamStatus="live"
          onState={onState}
        />,
      );
    });

    await act(async () => {
      root.render(
        <Harness
          initialData={initialData}
          sidebarQueryData={null}
          treeStreamData={staleTreeStream}
          treeStreamStatus="fallback"
          onState={onState}
        />,
      );
    });

    expect(onState.mock.lastCall?.[0]).toMatchObject({
      source: "initial",
      data: expect.objectContaining({
        kernelSidebarTree: [expect.objectContaining({ title: "初始标题" })],
      }),
    });
  });
});
