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

// Mock the workflow store
const mockUseWorkflowStore = vi.fn();

vi.mock("@/store/workflowStore", () => ({
  useWorkflowStore: (selector: (state: unknown) => unknown) => mockUseWorkflowStore(selector),
}));

// Mock useReactFlow
const mockGetNodes = vi.fn(() => []);
const mockSetNodes = vi.fn();

vi.mock("@xyflow/react", async () => {
  const actual = await vi.importActual("@xyflow/react");
  return {
    ...actual,
    useReactFlow: () => ({
      getNodes: mockGetNodes,
      setNodes: mockSetNodes,
    }),
  };
});

// Wrapper component for React Flow context
function TestWrapper({ children }: { children: React.ReactNode }) {
  return <ReactFlowProvider>{children}</ReactFlowProvider>;
}

describe("BaseNode", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    // Default mock implementation
    mockUseWorkflowStore.mockImplementation((selector) => {
      const state = {
        currentNodeId: null,
        groups: {},
        nodes: [],
      };
      return selector(state);
    });
  });

  const defaultProps = {
    id: "test-node-1",
    title: "Test Node",
    children: <div data-testid="test-children">Test Content</div>,
  };

  describe("Basic Rendering", () => {
    it("should render the title", () => {
      render(
        <TestWrapper>
          <BaseNode {...defaultProps} />
        </TestWrapper>
      );

      expect(screen.getByText("Test Node")).toBeInTheDocument();
    });

    it("should render children content", () => {
      render(
        <TestWrapper>
          <BaseNode {...defaultProps} />
        </TestWrapper>
      );

      expect(screen.getByTestId("test-children")).toBeInTheDocument();
      expect(screen.getByText("Test Content")).toBeInTheDocument();
    });

    it("should apply custom className", () => {
      const { container } = render(
        <TestWrapper>
          <BaseNode {...defaultProps} className="custom-class" />
        </TestWrapper>
      );

      const nodeDiv = container.querySelector(".custom-class");
      expect(nodeDiv).toBeInTheDocument();
    });

    it("should render custom title with default title", () => {
      render(
        <TestWrapper>
          <BaseNode {...defaultProps} customTitle="My Custom Name" />
        </TestWrapper>
      );

      expect(screen.getByText("My Custom Name - Test Node")).toBeInTheDocument();
    });

    it("should render titlePrefix when provided", () => {
      render(
        <TestWrapper>
          <BaseNode
            {...defaultProps}
            titlePrefix={<span data-testid="title-prefix">PREFIX</span>}
          />
        </TestWrapper>
      );

      expect(screen.getByTestId("title-prefix")).toBeInTheDocument();
    });

    it("should render headerAction when provided", () => {
      render(
        <TestWrapper>
          <BaseNode
            {...defaultProps}
            headerAction={<button data-testid="header-action">Action</button>}
          />
        </TestWrapper>
      );

      expect(screen.getByTestId("header-action")).toBeInTheDocument();
    });
  });

  describe("Title Editing", () => {
    it("should enter edit mode when title is clicked", () => {
      render(
        <TestWrapper>
          <BaseNode {...defaultProps} onCustomTitleChange={vi.fn()} />
        </TestWrapper>
      );

      const title = screen.getByText("Test Node");
      fireEvent.click(title);

      const input = screen.getByPlaceholderText("Custom title...");
      expect(input).toBeInTheDocument();
    });

    it("should update title value when typing", () => {
      render(
        <TestWrapper>
          <BaseNode {...defaultProps} onCustomTitleChange={vi.fn()} />
        </TestWrapper>
      );

      const title = screen.getByText("Test Node");
      fireEvent.click(title);

      const input = screen.getByPlaceholderText("Custom title...");
      fireEvent.change(input, { target: { value: "New Title" } });

      expect(input).toHaveValue("New Title");
    });

    it("should submit title on Enter and call onCustomTitleChange", () => {
      const mockOnCustomTitleChange = vi.fn();
      render(
        <TestWrapper>
          <BaseNode {...defaultProps} onCustomTitleChange={mockOnCustomTitleChange} />
        </TestWrapper>
      );

      const title = screen.getByText("Test Node");
      fireEvent.click(title);

      const input = screen.getByPlaceholderText("Custom title...");
      fireEvent.change(input, { target: { value: "New Title" } });
      fireEvent.keyDown(input, { key: "Enter" });

      expect(mockOnCustomTitleChange).toHaveBeenCalledWith("New Title");
    });

    it("should cancel editing on Escape and revert to original value", () => {
      render(
        <TestWrapper>
          <BaseNode {...defaultProps} customTitle="Original" onCustomTitleChange={vi.fn()} />
        </TestWrapper>
      );

      const title = screen.getByText("Original - Test Node");
      fireEvent.click(title);

      const input = screen.getByPlaceholderText("Custom title...");
      fireEvent.change(input, { target: { value: "Changed" } });
      fireEvent.keyDown(input, { key: "Escape" });

      // Should exit edit mode and show original title
      expect(screen.getByText("Original - Test Node")).toBeInTheDocument();
    });

    it("should submit title on blur", () => {
      const mockOnCustomTitleChange = vi.fn();
      render(
        <TestWrapper>
          <BaseNode {...defaultProps} onCustomTitleChange={mockOnCustomTitleChange} />
        </TestWrapper>
      );

      const title = screen.getByText("Test Node");
      fireEvent.click(title);

      const input = screen.getByPlaceholderText("Custom title...");
      fireEvent.change(input, { target: { value: "Blurred Title" } });
      fireEvent.blur(input);

      expect(mockOnCustomTitleChange).toHaveBeenCalledWith("Blurred Title");
    });
  });

  describe("Comment Functionality", () => {
    it("should render empty comment button when no comment exists", () => {
      render(
        <TestWrapper>
          <BaseNode {...defaultProps} onCommentChange={vi.fn()} />
        </TestWrapper>
      );

      const commentButton = screen.getByTitle("Add comment");
      expect(commentButton).toBeInTheDocument();
    });

    it("should render filled comment button when comment exists", () => {
      render(
        <TestWrapper>
          <BaseNode {...defaultProps} comment="Test comment" onCommentChange={vi.fn()} />
        </TestWrapper>
      );

      const commentButton = screen.getByTitle("Edit comment");
      expect(commentButton).toBeInTheDocument();
    });

    it("should open comment popover when button is clicked", () => {
      render(
        <TestWrapper>
          <BaseNode {...defaultProps} onCommentChange={vi.fn()} />
        </TestWrapper>
      );

      const commentButton = screen.getByTitle("Add comment");
      fireEvent.click(commentButton);

      const textarea = screen.getByPlaceholderText("Add a comment...");
      expect(textarea).toBeInTheDocument();
    });

    it("should show existing comment in textarea", () => {
      render(
        <TestWrapper>
          <BaseNode {...defaultProps} comment="Existing comment" onCommentChange={vi.fn()} />
        </TestWrapper>
      );

      const commentButton = screen.getByTitle("Edit comment");
      fireEvent.click(commentButton);

      const textarea = screen.getByPlaceholderText("Add a comment...");
      expect(textarea).toHaveValue("Existing comment");
    });

    it("should call onCommentChange when Save is clicked", () => {
      const mockOnCommentChange = vi.fn();
      render(
        <TestWrapper>
          <BaseNode {...defaultProps} onCommentChange={mockOnCommentChange} />
        </TestWrapper>
      );

      const commentButton = screen.getByTitle("Add comment");
      fireEvent.click(commentButton);

      const textarea = screen.getByPlaceholderText("Add a comment...");
      fireEvent.change(textarea, { target: { value: "New comment" } });

      const saveButton = screen.getByText("Save");
      fireEvent.click(saveButton);

      expect(mockOnCommentChange).toHaveBeenCalledWith("New comment");
    });

    it("should close popover and revert on Cancel", () => {
      const mockOnCommentChange = vi.fn();
      render(
        <TestWrapper>
          <BaseNode {...defaultProps} comment="Original" onCommentChange={mockOnCommentChange} />
        </TestWrapper>
      );

      const commentButton = screen.getByTitle("Edit comment");
      fireEvent.click(commentButton);

      const textarea = screen.getByPlaceholderText("Add a comment...");
      fireEvent.change(textarea, { target: { value: "Changed" } });

      const cancelButton = screen.getByText("Cancel");
      fireEvent.click(cancelButton);

      // Popover should be closed
      expect(screen.queryByPlaceholderText("Add a comment...")).not.toBeInTheDocument();
      expect(mockOnCommentChange).not.toHaveBeenCalled();
    });

    it("should close popover on Escape key", () => {
      render(
        <TestWrapper>
          <BaseNode {...defaultProps} onCommentChange={vi.fn()} />
        </TestWrapper>
      );

      const commentButton = screen.getByTitle("Add comment");
      fireEvent.click(commentButton);

      const textarea = screen.getByPlaceholderText("Add a comment...");
      fireEvent.keyDown(textarea, { key: "Escape" });

      expect(screen.queryByPlaceholderText("Add a comment...")).not.toBeInTheDocument();
    });
  });

  describe("Expand Button", () => {
    it("should not render expand button when onExpand is not provided", () => {
      render(
        <TestWrapper>
          <BaseNode {...defaultProps} />
        </TestWrapper>
      );

      expect(screen.queryByTitle("Expand editor")).not.toBeInTheDocument();
    });

    it("should render expand button when onExpand is provided", () => {
      render(
        <TestWrapper>
          <BaseNode {...defaultProps} onExpand={vi.fn()} />
        </TestWrapper>
      );

      expect(screen.getByTitle("Expand editor")).toBeInTheDocument();
    });

    it("should call onExpand when expand button is clicked", () => {
      const mockOnExpand = vi.fn();
      render(
        <TestWrapper>
          <BaseNode {...defaultProps} onExpand={mockOnExpand} />
        </TestWrapper>
      );

      const expandButton = screen.getByTitle("Expand editor");
      fireEvent.click(expandButton);

      expect(mockOnExpand).toHaveBeenCalled();
    });
  });

  describe("Run Button", () => {
    it("should not render run button when onRun is not provided", () => {
      render(
        <TestWrapper>
          <BaseNode {...defaultProps} />
        </TestWrapper>
      );

      expect(screen.queryByTitle("Run this node")).not.toBeInTheDocument();
    });

    it("should render run button when onRun is provided", () => {
      render(
        <TestWrapper>
          <BaseNode {...defaultProps} onRun={vi.fn()} />
        </TestWrapper>
      );

      expect(screen.getByTitle("Run this node")).toBeInTheDocument();
    });

    it("should call onRun when run button is clicked", () => {
      const mockOnRun = vi.fn();
      render(
        <TestWrapper>
          <BaseNode {...defaultProps} onRun={mockOnRun} />
        </TestWrapper>
      );

      const runButton = screen.getByTitle("Run this node");
      fireEvent.click(runButton);

      expect(mockOnRun).toHaveBeenCalled();
    });

    it("should disable run button when isExecuting is true", () => {
      render(
        <TestWrapper>
          <BaseNode {...defaultProps} onRun={vi.fn()} isExecuting={true} />
        </TestWrapper>
      );

      const runButton = screen.getByTitle("Run this node");
      expect(runButton).toBeDisabled();
    });

    it("should not call onRun when button is disabled", () => {
      const mockOnRun = vi.fn();
      render(
        <TestWrapper>
          <BaseNode {...defaultProps} onRun={mockOnRun} isExecuting={true} />
        </TestWrapper>
      );

      const runButton = screen.getByTitle("Run this node");
      fireEvent.click(runButton);

      expect(mockOnRun).not.toHaveBeenCalled();
    });
  });

  describe("Lock Badge", () => {
    it("should not render lock badge when node is not in a locked group", () => {
      mockUseWorkflowStore.mockImplementation((selector) => {
        const state = {
          currentNodeId: null,
          groups: { "group-1": { id: "group-1", locked: false } },
          nodes: [{ id: "test-node-1", groupId: "group-1" }],
        };
        return selector(state);
      });

      render(
        <TestWrapper>
          <BaseNode {...defaultProps} />
        </TestWrapper>
      );

      expect(
        screen.queryByTitle("This node is in a locked group and will be skipped during execution")
      ).not.toBeInTheDocument();
    });

    it("should render lock badge when node is in a locked group", () => {
      mockUseWorkflowStore.mockImplementation((selector) => {
        const state = {
          currentNodeId: null,
          groups: { "group-1": { id: "group-1", locked: true } },
          nodes: [{ id: "test-node-1", groupId: "group-1" }],
        };
        return selector(state);
      });

      render(
        <TestWrapper>
          <BaseNode {...defaultProps} />
        </TestWrapper>
      );

      expect(
        screen.getByTitle("This node is in a locked group and will be skipped during execution")
      ).toBeInTheDocument();
    });

    it("should not render lock badge when node has no groupId", () => {
      mockUseWorkflowStore.mockImplementation((selector) => {
        const state = {
          currentNodeId: null,
          groups: { "group-1": { id: "group-1", locked: true } },
          nodes: [{ id: "test-node-1" }], // No groupId
        };
        return selector(state);
      });

      render(
        <TestWrapper>
          <BaseNode {...defaultProps} />
        </TestWrapper>
      );

      expect(
        screen.queryByTitle("This node is in a locked group and will be skipped during execution")
      ).not.toBeInTheDocument();
    });
  });

  describe("Visual States", () => {
    it("should apply selected styling when selected is true", () => {
      const { container } = render(
        <TestWrapper>
          <BaseNode {...defaultProps} selected={true} />
        </TestWrapper>
      );

      const nodeDiv = container.querySelector(".ring-2.ring-blue-500\\/40");
      expect(nodeDiv).toBeInTheDocument();
    });

    it("should apply executing styling when isExecuting is true", () => {
      const { container } = render(
        <TestWrapper>
          <BaseNode {...defaultProps} isExecuting={true} />
        </TestWrapper>
      );

      const nodeDiv = container.querySelector(".border-blue-500.ring-1");
      expect(nodeDiv).toBeInTheDocument();
    });

    it("should apply executing styling when currentNodeId matches", () => {
      mockUseWorkflowStore.mockImplementation((selector) => {
        const state = {
          currentNodeId: "test-node-1",
          groups: {},
          nodes: [],
        };
        return selector(state);
      });

      const { container } = render(
        <TestWrapper>
          <BaseNode {...defaultProps} />
        </TestWrapper>
      );

      const nodeDiv = container.querySelector(".border-blue-500.ring-1");
      expect(nodeDiv).toBeInTheDocument();
    });

    it("should apply error styling when hasError is true", () => {
      const { container } = render(
        <TestWrapper>
          <BaseNode {...defaultProps} hasError={true} />
        </TestWrapper>
      );

      const nodeDiv = container.querySelector(".border-red-500");
      expect(nodeDiv).toBeInTheDocument();
    });
  });

  describe("Node Resizer", () => {
    it("should make resizer visible when selected", () => {
      const { container } = render(
        <TestWrapper>
          <BaseNode {...defaultProps} selected={true} />
        </TestWrapper>
      );

      // NodeResizer component renders with isVisible prop
      // We can check that the component was rendered by looking for resizer handles
      const resizer = container.querySelector(".react-flow__resize-control");
      // NodeResizer is visible when selected, component structure varies
      expect(container.firstChild).toBeTruthy();
    });

    it("should use custom minWidth and minHeight", () => {
      // This test verifies the props are passed, but the actual resizer behavior
      // is managed by React Flow's NodeResizer component
      render(
        <TestWrapper>
          <BaseNode {...defaultProps} minWidth={200} minHeight={150} selected={true} />
        </TestWrapper>
      );

      // Component renders without error with custom dimensions
      expect(screen.getByText("Test Node")).toBeInTheDocument();
    });
  });
});
