import React from "react";
import { render, screen, waitFor, fireEvent } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { LinkAutocomplete } from "../LinkAutocomplete";
import * as apiClient from "@/lib/api/client";

// Mock the API client
vi.mock("@/lib/api/client", () => ({
  apiGet: vi.fn(),
}));

const mockApiGet = apiClient.apiGet as ReturnType<typeof vi.fn>;

describe("LinkAutocomplete", (): void => {
  let textareaRef: React.RefObject<HTMLTextAreaElement>;
  let onInsertMock: ReturnType<typeof vi.fn>;

  beforeEach((): void => {
    // Create a real textarea element
    const textarea = document.createElement("textarea");
    textarea.style.width = "500px";
    textarea.style.height = "300px";
    document.body.appendChild(textarea);

    textareaRef = { current: textarea };
    onInsertMock = vi.fn();

    // Reset mocks
    vi.clearAllMocks();
    mockApiGet.mockResolvedValue({
      data: [],
      meta: { total: 0, page: 1, limit: 10, totalPages: 0 },
    });
  });

  afterEach((): void => {
    // Clean up
    if (textareaRef.current) {
      document.body.removeChild(textareaRef.current);
    }
    vi.clearAllTimers();
  });

  it("should not show dropdown initially", (): void => {
    render(<LinkAutocomplete textareaRef={textareaRef} onInsert={onInsertMock} />);

    expect(screen.queryByText(/Start typing to search/)).not.toBeInTheDocument();
  });

  it("should show dropdown when typing [[", async (): Promise<void> => {
    const user = userEvent.setup();
    render(<LinkAutocomplete textareaRef={textareaRef} onInsert={onInsertMock} />);

    const textarea = textareaRef.current;
    textarea.focus();

    await user.type(textarea, "[[");

    await waitFor(() => {
      expect(screen.getByText(/Start typing to search/)).toBeInTheDocument();
    });
  });

  it("should perform debounced search when typing query", async (): Promise<void> => {
    vi.useFakeTimers();
    const user = userEvent.setup({ delay: null });

    const mockResults = {
      data: [
        {
          id: "1",
          slug: "test-entry",
          title: "Test Entry",
          summary: "A test entry",
          workspaceId: "workspace-1",
          content: "Content",
          contentHtml: "<p>Content</p>",
          status: "PUBLISHED" as const,
          visibility: "PUBLIC" as const,
          createdBy: "user-1",
          updatedBy: "user-1",
          createdAt: new Date(),
          updatedAt: new Date(),
          tags: [],
        },
      ],
      meta: { total: 1, page: 1, limit: 10, totalPages: 1 },
    };

    mockApiGet.mockResolvedValue(mockResults);

    render(<LinkAutocomplete textareaRef={textareaRef} onInsert={onInsertMock} />);

    const textarea = textareaRef.current;
    textarea.focus();

    await user.type(textarea, "[[test");

    // Should not call API immediately
    expect(mockApiGet).not.toHaveBeenCalled();

    // Fast-forward 300ms
    vi.advanceTimersByTime(300);

    await waitFor(() => {
      expect(mockApiGet).toHaveBeenCalledWith("/api/knowledge/search?q=test&limit=10");
    });

    await waitFor(() => {
      expect(screen.getByText("Test Entry")).toBeInTheDocument();
    });

    vi.useRealTimers();
  });

  it("should navigate results with arrow keys", async (): Promise<void> => {
    vi.useFakeTimers();
    const user = userEvent.setup({ delay: null });

    const mockResults = {
      data: [
        {
          id: "1",
          slug: "entry-one",
          title: "Entry One",
          summary: "First entry",
          workspaceId: "workspace-1",
          content: "Content",
          contentHtml: "<p>Content</p>",
          status: "PUBLISHED" as const,
          visibility: "PUBLIC" as const,
          createdBy: "user-1",
          updatedBy: "user-1",
          createdAt: new Date(),
          updatedAt: new Date(),
          tags: [],
        },
        {
          id: "2",
          slug: "entry-two",
          title: "Entry Two",
          summary: "Second entry",
          workspaceId: "workspace-1",
          content: "Content",
          contentHtml: "<p>Content</p>",
          status: "PUBLISHED" as const,
          visibility: "PUBLIC" as const,
          createdBy: "user-1",
          updatedBy: "user-1",
          createdAt: new Date(),
          updatedAt: new Date(),
          tags: [],
        },
      ],
      meta: { total: 2, page: 1, limit: 10, totalPages: 1 },
    };

    mockApiGet.mockResolvedValue(mockResults);

    render(<LinkAutocomplete textareaRef={textareaRef} onInsert={onInsertMock} />);

    const textarea = textareaRef.current;
    textarea.focus();

    await user.type(textarea, "[[test");
    vi.advanceTimersByTime(300);

    await waitFor(() => {
      expect(screen.getByText("Entry One")).toBeInTheDocument();
    });

    // First item should be selected (highlighted)
    const firstItem = screen.getByText("Entry One").closest("li");
    expect(firstItem).toHaveClass("bg-blue-50");

    // Press ArrowDown
    fireEvent.keyDown(textarea, { key: "ArrowDown" });

    // Second item should now be selected
    await waitFor(() => {
      const secondItem = screen.getByText("Entry Two").closest("li");
      expect(secondItem).toHaveClass("bg-blue-50");
    });

    // Press ArrowUp
    fireEvent.keyDown(textarea, { key: "ArrowUp" });

    // First item should be selected again
    await waitFor(() => {
      const firstItem = screen.getByText("Entry One").closest("li");
      expect(firstItem).toHaveClass("bg-blue-50");
    });

    vi.useRealTimers();
  });

  it("should insert link on Enter key", async (): Promise<void> => {
    vi.useFakeTimers();
    const user = userEvent.setup({ delay: null });

    const mockResults = {
      data: [
        {
          id: "1",
          slug: "test-entry",
          title: "Test Entry",
          summary: "A test entry",
          workspaceId: "workspace-1",
          content: "Content",
          contentHtml: "<p>Content</p>",
          status: "PUBLISHED" as const,
          visibility: "PUBLIC" as const,
          createdBy: "user-1",
          updatedBy: "user-1",
          createdAt: new Date(),
          updatedAt: new Date(),
          tags: [],
        },
      ],
      meta: { total: 1, page: 1, limit: 10, totalPages: 1 },
    };

    mockApiGet.mockResolvedValue(mockResults);

    render(<LinkAutocomplete textareaRef={textareaRef} onInsert={onInsertMock} />);

    const textarea = textareaRef.current;
    textarea.focus();

    await user.type(textarea, "[[test");
    vi.advanceTimersByTime(300);

    await waitFor(() => {
      expect(screen.getByText("Test Entry")).toBeInTheDocument();
    });

    // Press Enter to select
    fireEvent.keyDown(textarea, { key: "Enter" });

    await waitFor(() => {
      expect(onInsertMock).toHaveBeenCalledWith("[[test-entry|Test Entry]]");
    });

    vi.useRealTimers();
  });

  it("should insert link on click", async (): Promise<void> => {
    vi.useFakeTimers();
    const user = userEvent.setup({ delay: null });

    const mockResults = {
      data: [
        {
          id: "1",
          slug: "test-entry",
          title: "Test Entry",
          summary: "A test entry",
          workspaceId: "workspace-1",
          content: "Content",
          contentHtml: "<p>Content</p>",
          status: "PUBLISHED" as const,
          visibility: "PUBLIC" as const,
          createdBy: "user-1",
          updatedBy: "user-1",
          createdAt: new Date(),
          updatedAt: new Date(),
          tags: [],
        },
      ],
      meta: { total: 1, page: 1, limit: 10, totalPages: 1 },
    };

    mockApiGet.mockResolvedValue(mockResults);

    render(<LinkAutocomplete textareaRef={textareaRef} onInsert={onInsertMock} />);

    const textarea = textareaRef.current;
    textarea.focus();

    await user.type(textarea, "[[test");
    vi.advanceTimersByTime(300);

    await waitFor(() => {
      expect(screen.getByText("Test Entry")).toBeInTheDocument();
    });

    // Click on the result
    fireEvent.click(screen.getByText("Test Entry"));

    await waitFor(() => {
      expect(onInsertMock).toHaveBeenCalledWith("[[test-entry|Test Entry]]");
    });

    vi.useRealTimers();
  });

  it("should close dropdown on Escape key", async (): Promise<void> => {
    vi.useFakeTimers();
    const user = userEvent.setup({ delay: null });

    render(<LinkAutocomplete textareaRef={textareaRef} onInsert={onInsertMock} />);

    const textarea = textareaRef.current;
    textarea.focus();

    await user.type(textarea, "[[test");
    vi.advanceTimersByTime(300);

    await waitFor(() => {
      expect(screen.getByText(/Start typing to search/)).toBeInTheDocument();
    });

    // Press Escape
    fireEvent.keyDown(textarea, { key: "Escape" });

    await waitFor(() => {
      expect(screen.queryByText(/Start typing to search/)).not.toBeInTheDocument();
    });

    vi.useRealTimers();
  });

  it("should close dropdown when closing brackets are typed", async (): Promise<void> => {
    vi.useFakeTimers();
    const user = userEvent.setup({ delay: null });

    render(<LinkAutocomplete textareaRef={textareaRef} onInsert={onInsertMock} />);

    const textarea = textareaRef.current;
    textarea.focus();

    await user.type(textarea, "[[test");
    vi.advanceTimersByTime(300);

    await waitFor(() => {
      expect(screen.getByText(/Start typing to search/)).toBeInTheDocument();
    });

    // Type closing brackets
    await user.type(textarea, "]]");

    await waitFor(() => {
      expect(screen.queryByText(/Start typing to search/)).not.toBeInTheDocument();
    });

    vi.useRealTimers();
  });

  it("should show 'No entries found' when search returns no results", async (): Promise<void> => {
    vi.useFakeTimers();
    const user = userEvent.setup({ delay: null });

    mockApiGet.mockResolvedValue({
      data: [],
      meta: { total: 0, page: 1, limit: 10, totalPages: 0 },
    });

    render(<LinkAutocomplete textareaRef={textareaRef} onInsert={onInsertMock} />);

    const textarea = textareaRef.current;
    textarea.focus();

    await user.type(textarea, "[[nonexistent");
    vi.advanceTimersByTime(300);

    await waitFor(() => {
      expect(screen.getByText("No entries found")).toBeInTheDocument();
    });

    vi.useRealTimers();
  });

  it("should show loading state while searching", async (): Promise<void> => {
    vi.useFakeTimers();
    const user = userEvent.setup({ delay: null });

    // Mock a slow API response
    let resolveSearch: (value: unknown) => void;
    const searchPromise = new Promise((resolve) => {
      resolveSearch = resolve;
    });
    mockApiGet.mockReturnValue(searchPromise as Promise<any>);

    render(<LinkAutocomplete textareaRef={textareaRef} onInsert={onInsertMock} />);

    const textarea = textareaRef.current;
    textarea.focus();

    await user.type(textarea, "[[test");
    vi.advanceTimersByTime(300);

    await waitFor(() => {
      expect(screen.getByText("Searching...")).toBeInTheDocument();
    });

    // Resolve the search
    resolveSearch!({
      data: [],
      meta: { total: 0, page: 1, limit: 10, totalPages: 0 },
    });

    await waitFor(() => {
      expect(screen.queryByText("Searching...")).not.toBeInTheDocument();
    });

    vi.useRealTimers();
  });

  it("should display summary preview for entries", async (): Promise<void> => {
    vi.useFakeTimers();
    const user = userEvent.setup({ delay: null });

    const mockResults = {
      data: [
        {
          id: "1",
          slug: "test-entry",
          title: "Test Entry",
          summary: "This is a helpful summary",
          workspaceId: "workspace-1",
          content: "Content",
          contentHtml: "<p>Content</p>",
          status: "PUBLISHED" as const,
          visibility: "PUBLIC" as const,
          createdBy: "user-1",
          updatedBy: "user-1",
          createdAt: new Date(),
          updatedAt: new Date(),
          tags: [],
        },
      ],
      meta: { total: 1, page: 1, limit: 10, totalPages: 1 },
    };

    mockApiGet.mockResolvedValue(mockResults);

    render(<LinkAutocomplete textareaRef={textareaRef} onInsert={onInsertMock} />);

    const textarea = textareaRef.current;
    textarea.focus();

    await user.type(textarea, "[[test");
    vi.advanceTimersByTime(300);

    await waitFor(() => {
      expect(screen.getByText("This is a helpful summary")).toBeInTheDocument();
    });

    vi.useRealTimers();
  });
});
