import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import { ReactFlowProvider } from "@xyflow/react";
import { GroupNode } from "@/components/nodes/GroupNode";

const mockUpdateGroup = vi.fn();
const mockDeleteGroup = vi.fn();
const mockOnNodesChange = vi.fn();
const mockRunGroup = vi.fn();
const mockCancelGroupTask = vi.fn();
const mockUseWorkflowStore = vi.fn();

vi.mock("@/store/workflowStore", () => ({
  useWorkflowStore: (selector?: (state: unknown) => unknown) => {
    const state = mockUseWorkflowStore();
    return selector ? selector(state) : state;
  },
  GROUP_COLORS: {
    neutral: "#262626",
    blue: "#1e3a5f",
    green: "#1a3d2e",
    purple: "#2d2458",
    orange: "#3d2a1a",
    red: "#3d1a1a",
  },
}));

vi.mock("@xyflow/react", async () => {
  const actual = await vi.importActual("@xyflow/react");
  return {
    ...actual,
    NodeToolbar: ({
      children,
      isVisible,
    }: {
      children: React.ReactNode;
      isVisible?: boolean;
    }) => (isVisible ? <div data-testid="node-toolbar">{children}</div> : null),
    NodeResizer: ({
      isVisible,
      minWidth,
      minHeight,
    }: {
      isVisible: boolean;
      minWidth: number;
      minHeight: number;
    }) => (
      <div
        data-testid="node-resizer"
        data-visible={isVisible}
        data-min-width={minWidth}
        data-min-height={minHeight}
      />
    ),
    NodeResizeControl: ({
      children,
      position,
      minWidth,
      minHeight,
    }: {
      children: React.ReactNode;
      position: string;
      minWidth: number;
      minHeight: number;
    }) => (
      <div
        data-testid="node-resize-control"
        data-position={position}
        data-min-width={minWidth}
        data-min-height={minHeight}
      >
        {children}
      </div>
    ),
  };
});

function TestWrapper({ children }: { children: React.ReactNode }) {
  return <ReactFlowProvider>{children}</ReactFlowProvider>;
}

describe("GroupNode", () => {
  const defaultGroup = {
    id: "group-1",
    name: "Test Group",
    color: "blue" as const,
    position: { x: 0, y: 0 },
    size: { width: 300, height: 200 },
    locked: false,
    isNbpInput: false,
  };

  const createNodeProps = (overrides = {}) => ({
    id: "group-node-group-1",
    type: "group" as const,
    data: { groupId: "group-1" },
    selected: false,
    ...overrides,
  });

  beforeEach(() => {
    vi.clearAllMocks();
    mockUseWorkflowStore.mockReturnValue({
      groups: { "group-1": defaultGroup },
      nodes: [],
      onNodesChange: mockOnNodesChange,
      updateGroup: mockUpdateGroup,
      deleteGroup: mockDeleteGroup,
      runGroup: mockRunGroup,
      cancelGroupTask: mockCancelGroupTask,
      runningGroupIds: new Set<string>(),
    });
  });

  it("renders the group name and native resizer", () => {
    render(
      <TestWrapper>
        <GroupNode {...createNodeProps({ selected: true })} />
      </TestWrapper>
    );

    expect(screen.getByText("Test Group")).toBeInTheDocument();
    expect(screen.getByTestId("node-resizer")).toHaveAttribute("data-visible", "true");
  });

  it("shows resize controls when hovering an unselected group", () => {
    const { container } = render(
      <TestWrapper>
        <GroupNode {...createNodeProps()} />
      </TestWrapper>
    );

    expect(screen.getByTestId("node-resizer")).toHaveAttribute("data-visible", "false");
    fireEvent.mouseEnter(container.querySelector(".rounded-xl")!);
    expect(screen.getByTestId("node-resizer")).toHaveAttribute("data-visible", "true");
  });

  it("renders a bottom-right resize icon", () => {
    render(
      <TestWrapper>
        <GroupNode {...createNodeProps()} />
      </TestWrapper>
    );

    expect(screen.getByTestId("node-resize-control")).toHaveAttribute("data-position", "bottom-right");
    expect(screen.getByTestId("node-resize-control")).toHaveAttribute("data-min-width", "200");
    expect(screen.getByTestId("node-resize-control")).toHaveAttribute("data-min-height", "120");
    expect(screen.getByTestId("group-resize-corner")).toHaveAttribute("title", "缩放分组");
  });

  it("keeps resize minimums large enough to contain grouped nodes", () => {
    mockUseWorkflowStore.mockReturnValue({
      groups: { "group-1": defaultGroup },
      nodes: [
        {
          id: "node-1",
          type: "prompt",
          position: { x: 260, y: 140 },
          style: { width: 180, height: 90 },
          groupId: "group-1",
        },
      ],
      edges: [],
      onNodesChange: mockOnNodesChange,
      updateGroup: mockUpdateGroup,
      deleteGroup: mockDeleteGroup,
      runGroup: mockRunGroup,
      cancelGroupTask: mockCancelGroupTask,
      runningGroupIds: new Set<string>(),
    });

    render(
      <TestWrapper>
        <GroupNode {...createNodeProps()} />
      </TestWrapper>
    );

    expect(screen.getByTestId("node-resizer")).toHaveAttribute("data-min-width", "460");
    expect(screen.getByTestId("node-resizer")).toHaveAttribute("data-min-height", "250");
    expect(screen.getByTestId("node-resize-control")).toHaveAttribute("data-min-width", "460");
    expect(screen.getByTestId("node-resize-control")).toHaveAttribute("data-min-height", "250");
  });

  it("returns null when group metadata is missing", () => {
    mockUseWorkflowStore.mockReturnValue({
      groups: {},
      nodes: [],
      onNodesChange: mockOnNodesChange,
      updateGroup: mockUpdateGroup,
      deleteGroup: mockDeleteGroup,
      runGroup: mockRunGroup,
      cancelGroupTask: mockCancelGroupTask,
      runningGroupIds: new Set<string>(),
    });

    const { container } = render(
      <TestWrapper>
        <GroupNode {...createNodeProps()} />
      </TestWrapper>
    );

    expect(container.querySelector(".rounded-xl")).not.toBeInTheDocument();
  });

  it("updates the group name on double click and Enter", () => {
    render(
      <TestWrapper>
        <GroupNode {...createNodeProps()} />
      </TestWrapper>
    );

    fireEvent.doubleClick(screen.getByText("Test Group"));
    const input = screen.getByDisplayValue("Test Group");
    fireEvent.change(input, { target: { value: "New Name" } });
    fireEvent.keyDown(input, { key: "Enter" });

    expect(mockUpdateGroup).toHaveBeenCalledWith("group-1", { name: "New Name" });
  });

  it("only shows the toolbar when the group is selected", () => {
    const { rerender } = render(
      <TestWrapper>
        <GroupNode {...createNodeProps({ selected: false })} />
      </TestWrapper>
    );

    expect(screen.queryByTitle("颜色")).not.toBeInTheDocument();

    rerender(
      <TestWrapper>
        <GroupNode {...createNodeProps({ selected: true })} />
      </TestWrapper>
    );

    expect(screen.getByTitle("颜色")).toBeInTheDocument();
  });

  it("changes group color from the toolbar", () => {
    render(
      <TestWrapper>
        <GroupNode {...createNodeProps({ selected: true })} />
      </TestWrapper>
    );

    fireEvent.click(screen.getByTitle("颜色"));
    fireEvent.click(screen.getByTitle("绿色"));

    expect(mockUpdateGroup).toHaveBeenCalledWith("group-1", { color: "green" });
  });

  it("applies a layout to grouped nodes", () => {
    mockUseWorkflowStore.mockReturnValue({
      groups: { "group-1": defaultGroup },
      nodes: [
        {
          id: "node-1",
          type: "prompt",
          groupId: "group-1",
          position: { x: 20, y: 40 },
          data: {},
          measured: { width: 100, height: 80 },
        },
        {
          id: "node-2",
          type: "prompt",
          groupId: "group-1",
          position: { x: 80, y: 10 },
          data: {},
          measured: { width: 100, height: 80 },
        },
      ],
      onNodesChange: mockOnNodesChange,
      updateGroup: mockUpdateGroup,
      deleteGroup: mockDeleteGroup,
      runGroup: mockRunGroup,
      cancelGroupTask: mockCancelGroupTask,
      runningGroupIds: new Set<string>(),
    });

    render(
      <TestWrapper>
        <GroupNode {...createNodeProps({ selected: true })} />
      </TestWrapper>
    );

    fireEvent.click(screen.getByTitle("布局"));
    fireEvent.click(screen.getByText("水平排列"));

    expect(mockUpdateGroup).toHaveBeenCalledWith("group-1", { layout: "horizontal" });
    expect(mockOnNodesChange).toHaveBeenCalledWith([
      expect.objectContaining({ id: "node-1", type: "position" }),
      expect.objectContaining({ id: "node-2", type: "position" }),
    ]);
  });

  it("ungroups from the toolbar", () => {
    render(
      <TestWrapper>
        <GroupNode {...createNodeProps({ selected: true })} />
      </TestWrapper>
    );

    fireEvent.click(screen.getByTitle("解组"));
    expect(mockDeleteGroup).toHaveBeenCalledWith("group-1");
  });
});
