import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import type { Task } from "@mosaic/shared";
import { TaskPriority, TaskStatus } from "@mosaic/shared";
import KanbanPage from "./page";

const mockReplace = vi.fn();
let mockSearchParams = new URLSearchParams();

vi.mock("next/navigation", () => ({
  useRouter: (): { replace: typeof mockReplace } => ({ replace: mockReplace }),
  useSearchParams: (): URLSearchParams => mockSearchParams,
}));

vi.mock("@hello-pangea/dnd", () => ({
  DragDropContext: ({ children }: { children: React.ReactNode }): React.JSX.Element => (
    <div data-testid="mock-dnd-context">{children}</div>
  ),
  Droppable: ({
    children,
    droppableId,
  }: {
    children: (provided: {
      innerRef: (el: HTMLElement | null) => void;
      droppableProps: Record<string, never>;
      placeholder: React.ReactNode;
    }) => React.ReactNode;
    droppableId: string;
  }): React.JSX.Element => (
    <div data-testid={`mock-droppable-${droppableId}`}>
      {children({
        innerRef: () => {
          /* noop */
        },
        droppableProps: {},
        placeholder: null,
      })}
    </div>
  ),
  Draggable: ({
    children,
    draggableId,
  }: {
    children: (
      provided: {
        innerRef: (el: HTMLElement | null) => void;
        draggableProps: { style: Record<string, string> };
        dragHandleProps: Record<string, string>;
      },
      snapshot: { isDragging: boolean }
    ) => React.ReactNode;
    draggableId: string;
    index: number;
  }): React.JSX.Element => (
    <div data-testid={`mock-draggable-${draggableId}`}>
      {children(
        {
          innerRef: () => {
            /* noop */
          },
          draggableProps: { style: {} },
          dragHandleProps: {},
        },
        { isDragging: false }
      )}
    </div>
  ),
}));

vi.mock("@/components/ui/MosaicSpinner", () => ({
  MosaicSpinner: ({ label }: { label?: string }): React.JSX.Element => (
    <div data-testid="mosaic-spinner">{label ?? "Loading..."}</div>
  ),
}));

const mockUseWorkspaceId = vi.fn<() => string | null>();
vi.mock("@/lib/hooks", () => ({
  useWorkspaceId: (): string | null => mockUseWorkspaceId(),
}));

const mockFetchTasks = vi.fn<() => Promise<Task[]>>();
const mockUpdateTask = vi.fn<() => Promise<unknown>>();
const mockCreateTask = vi.fn<() => Promise<Task>>();
vi.mock("@/lib/api/tasks", () => ({
  fetchTasks: (...args: unknown[]): Promise<Task[]> => mockFetchTasks(...(args as [])),
  updateTask: (...args: unknown[]): Promise<unknown> => mockUpdateTask(...(args as [])),
  createTask: (...args: unknown[]): Promise<Task> => mockCreateTask(...(args as [])),
}));

const mockFetchProjects = vi.fn<() => Promise<unknown[]>>();
vi.mock("@/lib/api/projects", () => ({
  fetchProjects: (...args: unknown[]): Promise<unknown[]> => mockFetchProjects(...(args as [])),
}));

const createdTask: Task = {
  id: "task-new-1",
  title: "Ship Kanban add task flow",
  description: null,
  status: TaskStatus.NOT_STARTED,
  priority: TaskPriority.MEDIUM,
  dueDate: null,
  creatorId: "user-1",
  assigneeId: null,
  workspaceId: "ws-1",
  projectId: "project-42",
  parentId: null,
  sortOrder: 0,
  metadata: {},
  completedAt: null,
  createdAt: new Date("2026-03-01"),
  updatedAt: new Date("2026-03-01"),
};

describe("KanbanPage add task flow", (): void => {
  beforeEach((): void => {
    vi.clearAllMocks();
    mockSearchParams = new URLSearchParams("project=project-42");
    mockUseWorkspaceId.mockReturnValue("ws-1");
    mockFetchTasks.mockResolvedValue([]);
    mockFetchProjects.mockResolvedValue([]);
    mockCreateTask.mockResolvedValue(createdTask);
  });

  it("opens add-task form in a column and creates a task via API", async (): Promise<void> => {
    const user = userEvent.setup();

    render(<KanbanPage />);

    await waitFor((): void => {
      expect(screen.getByText("Kanban Board")).toBeInTheDocument();
    });

    // Click the "+ Add task" button in the To Do column
    const addTaskButtons = screen.getAllByRole("button", { name: /\+ Add task/i });
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    await user.click(addTaskButtons[0]!); // First column is "To Do"

    // Type in the title input
    const titleInput = screen.getByPlaceholderText("Task title...");
    await user.type(titleInput, createdTask.title);

    // Click the Add button
    await user.click(screen.getByRole("button", { name: /✓ Add/i }));

    await waitFor((): void => {
      expect(mockCreateTask).toHaveBeenCalledWith(
        expect.objectContaining({
          title: createdTask.title,
          status: TaskStatus.NOT_STARTED,
          projectId: "project-42",
        }),
        "ws-1"
      );
    });
  });

  it("cancels add-task form when pressing Escape", async (): Promise<void> => {
    const user = userEvent.setup();

    render(<KanbanPage />);

    await waitFor((): void => {
      expect(screen.getByText("Kanban Board")).toBeInTheDocument();
    });

    // Click the "+ Add task" button
    const addTaskButtons = screen.getAllByRole("button", { name: /\+ Add task/i });
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    await user.click(addTaskButtons[0]!);

    // Type in the title input
    const titleInput = screen.getByPlaceholderText("Task title...");
    await user.type(titleInput, "Test task");

    // Press Escape to cancel
    await user.keyboard("{Escape}");

    // Form should be closed, back to "+ Add task" button
    await waitFor((): void => {
      const buttons = screen.getAllByRole("button", { name: /\+ Add task/i });
      expect(buttons.length).toBe(5); // One per column
    });

    // Should not have called createTask
    expect(mockCreateTask).not.toHaveBeenCalled();
  });
});
