import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { KnowledgeGraphViewer } from "./KnowledgeGraphViewer";
import * as knowledgeApi from "@/lib/api/knowledge";

// Mock the knowledge API
vi.mock("@/lib/api/knowledge");

// Mock MosaicSpinner to expose a test ID
vi.mock("@/components/ui/MosaicSpinner", () => ({
  MosaicSpinner: ({ label }: { label?: string }): React.JSX.Element => (
    <div data-testid="loading-spinner">{label ?? "Loading..."}</div>
  ),
}));

// Mock elkjs since it requires APIs not available in test environment
vi.mock("elkjs/lib/elk.bundled.js", () => ({
  default: class ELK {
    layout(graph: {
      children?: { id: string }[];
    }): Promise<{ children: { id: string; x: number; y: number }[] }> {
      return Promise.resolve({
        children: (graph.children ?? []).map((child: { id: string }, i: number) => ({
          id: child.id,
          x: i * 100,
          y: i * 100,
        })),
      });
    }
  },
}));

// Mock Next.js router
const mockPush = vi.fn();
vi.mock("next/navigation", () => ({
  useRouter: (): {
    push: typeof mockPush;
    replace: () => void;
    prefetch: () => void;
    back: () => void;
    forward: () => void;
    refresh: () => void;
  } => ({
    push: mockPush,
    replace: vi.fn(),
    prefetch: vi.fn(),
    back: vi.fn(),
    forward: vi.fn(),
    refresh: vi.fn(),
  }),
}));

// Mock ReactFlow since it requires DOM APIs not available in test environment
vi.mock("@xyflow/react", () => ({
  ReactFlow: ({
    nodes,
    edges,
    children,
  }: {
    nodes: unknown[];
    edges: unknown[];
    children: React.ReactNode;
  }): React.JSX.Element => (
    <div data-testid="react-flow">
      <div data-testid="node-count">{nodes.length}</div>
      <div data-testid="edge-count">{edges.length}</div>
      {children}
    </div>
  ),
  Background: (): React.JSX.Element => <div data-testid="background" />,
  Controls: (): React.JSX.Element => <div data-testid="controls" />,
  MiniMap: (): React.JSX.Element => <div data-testid="minimap" />,
  Panel: ({ children }: { children: React.ReactNode }): React.JSX.Element => (
    <div data-testid="panel">{children}</div>
  ),
  useNodesState: (initial: unknown[]): [unknown[], () => void, () => void] => [
    initial,
    vi.fn(),
    vi.fn(),
  ],
  useEdgesState: (initial: unknown[]): [unknown[], () => void, () => void] => [
    initial,
    vi.fn(),
    vi.fn(),
  ],
  MarkerType: { ArrowClosed: "arrowclosed" },
  BackgroundVariant: { Dots: "dots" },
}));

const mockGraphData = {
  nodes: [
    {
      id: "node-1",
      slug: "test-entry-1",
      title: "Test Entry 1",
      summary: "Test summary 1",
      status: "PUBLISHED",
      tags: [{ id: "tag-1", name: "Tag 1", slug: "tag-1", color: "#3B82F6" }],
      depth: 0,
      isOrphan: false,
    },
    {
      id: "node-2",
      slug: "test-entry-2",
      title: "Test Entry 2",
      summary: "Test summary 2",
      status: "DRAFT",
      tags: [],
      depth: 1,
      isOrphan: false,
    },
    {
      id: "node-3",
      slug: "test-entry-3",
      title: "Orphan Entry",
      summary: "No connections",
      status: "PUBLISHED",
      tags: [],
      depth: 0,
      isOrphan: true,
    },
  ],
  edges: [
    {
      id: "edge-1",
      sourceId: "node-1",
      targetId: "node-2",
      linkText: "link text",
    },
  ],
  stats: {
    totalNodes: 3,
    totalEdges: 1,
    orphanCount: 1,
  },
};

describe("KnowledgeGraphViewer", (): void => {
  beforeEach((): void => {
    vi.clearAllMocks();
  });

  describe("rendering", (): void => {
    it("should render loading state initially", (): void => {
      vi.mocked(knowledgeApi.fetchKnowledgeGraph).mockImplementation(
        // eslint-disable-next-line @typescript-eslint/no-empty-function
        () => new Promise(() => {}) // Never resolves
      );

      render(<KnowledgeGraphViewer />);

      expect(screen.getByTestId("loading-spinner")).toBeInTheDocument();
    });

    it("should render error state when fetch fails", async (): Promise<void> => {
      vi.mocked(knowledgeApi.fetchKnowledgeGraph).mockRejectedValue(
        new Error("Failed to fetch graph")
      );

      render(<KnowledgeGraphViewer />);

      await waitFor(() => {
        expect(screen.getByText(/error loading graph/i)).toBeInTheDocument();
        expect(screen.getByText(/failed to fetch graph/i)).toBeInTheDocument();
      });
    });

    it("should render graph when data loads successfully", async (): Promise<void> => {
      vi.mocked(knowledgeApi.fetchKnowledgeGraph).mockResolvedValue(mockGraphData);

      render(<KnowledgeGraphViewer />);

      await waitFor(() => {
        expect(screen.getByTestId("react-flow")).toBeInTheDocument();
        expect(screen.getByTestId("node-count")).toHaveTextContent("3");
        expect(screen.getByTestId("edge-count")).toHaveTextContent("1");
      });
    });

    it("should render graph controls", async (): Promise<void> => {
      vi.mocked(knowledgeApi.fetchKnowledgeGraph).mockResolvedValue(mockGraphData);

      render(<KnowledgeGraphViewer />);

      await waitFor(() => {
        expect(screen.getByTestId("controls")).toBeInTheDocument();
        expect(screen.getByTestId("minimap")).toBeInTheDocument();
      });
    });

    it("should display statistics in panel", async (): Promise<void> => {
      vi.mocked(knowledgeApi.fetchKnowledgeGraph).mockResolvedValue(mockGraphData);

      render(<KnowledgeGraphViewer />);

      await waitFor(() => {
        expect(screen.getByText(/3 entries/i)).toBeInTheDocument();
        expect(screen.getByText(/1 connection/i)).toBeInTheDocument();
      });
    });
  });

  describe("node sizing", (): void => {
    it("should size nodes based on number of connections", async (): Promise<void> => {
      vi.mocked(knowledgeApi.fetchKnowledgeGraph).mockResolvedValue(mockGraphData);

      render(<KnowledgeGraphViewer />);

      await waitFor(() => {
        const reactFlow = screen.getByTestId("react-flow");
        expect(reactFlow).toBeInTheDocument();
      });

      // Node sizes should be calculated based on connection count
      // node-1 has 1 connection, node-2 has 1 connection, node-3 has 0 connections
    });
  });

  describe("node coloring", (): void => {
    it("should color nodes based on entry status", async (): Promise<void> => {
      vi.mocked(knowledgeApi.fetchKnowledgeGraph).mockResolvedValue(mockGraphData);

      render(<KnowledgeGraphViewer />);

      await waitFor(() => {
        expect(screen.getByTestId("react-flow")).toBeInTheDocument();
      });

      // Colors should follow PDA-friendly guidelines:
      // PUBLISHED -> green (active)
      // DRAFT -> blue (scheduled/upcoming)
      // ARCHIVED -> gray (dormant)
    });

    it("should highlight orphan nodes differently", async (): Promise<void> => {
      vi.mocked(knowledgeApi.fetchKnowledgeGraph).mockResolvedValue(mockGraphData);

      render(<KnowledgeGraphViewer />);

      await waitFor(() => {
        expect(screen.getByTestId("react-flow")).toBeInTheDocument();
      });

      // Orphan nodes should have visual indication
    });
  });

  describe("layout controls", (): void => {
    it("should render layout toggle buttons", async (): Promise<void> => {
      vi.mocked(knowledgeApi.fetchKnowledgeGraph).mockResolvedValue(mockGraphData);

      render(<KnowledgeGraphViewer />);

      await waitFor(() => {
        expect(screen.getByRole("button", { name: /force/i })).toBeInTheDocument();
        expect(screen.getByRole("button", { name: /hierarchical/i })).toBeInTheDocument();
        expect(screen.getByRole("button", { name: /circular/i })).toBeInTheDocument();
      });
    });

    it("should switch layout when button is clicked", async (): Promise<void> => {
      const user = userEvent.setup();
      vi.mocked(knowledgeApi.fetchKnowledgeGraph).mockResolvedValue(mockGraphData);

      render(<KnowledgeGraphViewer />);

      await waitFor(() => {
        expect(screen.getByRole("button", { name: /force/i })).toBeInTheDocument();
      });

      const hierarchicalButton = screen.getByRole("button", { name: /hierarchical/i });
      await user.click(hierarchicalButton);

      // Should apply hierarchical layout
      expect(hierarchicalButton).toHaveClass("bg-blue-500");
    });
  });

  describe("filtering", (): void => {
    it("should allow filtering by tags", async (): Promise<void> => {
      const user = userEvent.setup();
      vi.mocked(knowledgeApi.fetchKnowledgeGraph).mockResolvedValue(mockGraphData);

      render(<KnowledgeGraphViewer />);

      await waitFor(() => {
        expect(screen.getByTestId("react-flow")).toBeInTheDocument();
      });

      const filterInput = screen.getByPlaceholderText(/filter by tags/i);
      await user.type(filterInput, "Tag 1");

      // Should filter nodes
    });

    it("should allow filtering by status", async (): Promise<void> => {
      const user = userEvent.setup();
      vi.mocked(knowledgeApi.fetchKnowledgeGraph).mockResolvedValue(mockGraphData);

      render(<KnowledgeGraphViewer />);

      await waitFor(() => {
        expect(screen.getByTestId("react-flow")).toBeInTheDocument();
      });

      const statusFilter = screen.getByRole("combobox", { name: /status/i });
      await user.selectOptions(statusFilter, "PUBLISHED");

      // Should filter nodes by status
    });

    it("should show orphan nodes when filter is enabled", async (): Promise<void> => {
      const user = userEvent.setup();
      vi.mocked(knowledgeApi.fetchKnowledgeGraph).mockResolvedValue(mockGraphData);

      render(<KnowledgeGraphViewer />);

      await waitFor(() => {
        expect(screen.getByTestId("react-flow")).toBeInTheDocument();
      });

      const orphanToggle = screen.getByRole("switch", { name: /show orphans/i });
      await user.click(orphanToggle);

      // Should toggle orphan visibility
    });
  });

  describe("node interaction", (): void => {
    it("should navigate to entry when node is clicked", async (): Promise<void> => {
      vi.mocked(knowledgeApi.fetchKnowledgeGraph).mockResolvedValue(mockGraphData);

      render(<KnowledgeGraphViewer />);

      await waitFor(() => {
        expect(screen.getByTestId("react-flow")).toBeInTheDocument();
      });

      // Click functionality will be tested in integration tests
      // Unit test verifies the handler is set up correctly
    });

    it("should show node details on hover", async (): Promise<void> => {
      vi.mocked(knowledgeApi.fetchKnowledgeGraph).mockResolvedValue(mockGraphData);

      render(<KnowledgeGraphViewer />);

      await waitFor(() => {
        expect(screen.getByTestId("react-flow")).toBeInTheDocument();
      });

      // Hover behavior will be verified in the actual component
    });
  });

  describe("performance", (): void => {
    it("should handle large graphs with 500+ nodes", async (): Promise<void> => {
      const largeGraph = {
        nodes: Array.from({ length: 500 }, (_, i) => ({
          id: `node-${String(i)}`,
          slug: `entry-${String(i)}`,
          title: `Entry ${String(i)}`,
          summary: `Summary ${String(i)}`,
          status: "PUBLISHED",
          tags: [],
          depth: 0,
          isOrphan: false,
        })),
        edges: Array.from({ length: 600 }, (_, i) => ({
          id: `edge-${String(i)}`,
          sourceId: `node-${String(i % 500)}`,
          targetId: `node-${String((i + 1) % 500)}`,
          linkText: "link",
        })),
        stats: {
          totalNodes: 500,
          totalEdges: 600,
          orphanCount: 0,
        },
      };

      vi.mocked(knowledgeApi.fetchKnowledgeGraph).mockResolvedValue(largeGraph);

      const startTime = performance.now();
      render(<KnowledgeGraphViewer />);

      await waitFor(() => {
        expect(screen.getByTestId("react-flow")).toBeInTheDocument();
      });

      const endTime = performance.now();
      const renderTime = endTime - startTime;

      // Should render in reasonable time (< 3 seconds)
      expect(renderTime).toBeLessThan(3000);
    });
  });
});
