import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import type { ButtonHTMLAttributes, HTMLAttributes, ReactNode } from "react";

interface MockButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  children: ReactNode;
}

interface MockContainerProps extends HTMLAttributes<HTMLElement> {
  children: ReactNode;
}

interface MockSession {
  id: string;
  providerId: string;
  providerType: string;
  status: "active" | "paused" | "killed";
  createdAt: string;
  updatedAt: string;
  metadata?: Record<string, unknown>;
}

const mockApiGet = vi.fn<(endpoint: string) => Promise<MockSession[]>>();
const mockApiPost = vi.fn<(endpoint: string, body?: unknown) => Promise<{ message: string }>>();
const mockKillAllDialog = vi.fn<() => React.JSX.Element>();

vi.mock("@/lib/api/client", () => ({
  apiGet: (endpoint: string): Promise<MockSession[]> => mockApiGet(endpoint),
  apiPost: (endpoint: string, body?: unknown): Promise<{ message: string }> =>
    mockApiPost(endpoint, body),
}));

vi.mock("@/components/mission-control/KillAllDialog", () => ({
  KillAllDialog: (): React.JSX.Element => mockKillAllDialog(),
}));

vi.mock("@/components/ui/button", () => ({
  Button: ({ children, ...props }: MockButtonProps): React.JSX.Element => (
    <button {...props}>{children}</button>
  ),
}));

vi.mock("@/components/ui/badge", () => ({
  Badge: ({ children, ...props }: MockContainerProps): React.JSX.Element => (
    <span {...props}>{children}</span>
  ),
}));

vi.mock("@/components/ui/card", () => ({
  Card: ({ children, ...props }: MockContainerProps): React.JSX.Element => (
    <section {...props}>{children}</section>
  ),
  CardHeader: ({ children, ...props }: MockContainerProps): React.JSX.Element => (
    <header {...props}>{children}</header>
  ),
  CardContent: ({ children, ...props }: MockContainerProps): React.JSX.Element => (
    <div {...props}>{children}</div>
  ),
  CardTitle: ({ children, ...props }: MockContainerProps): React.JSX.Element => (
    <h2 {...props}>{children}</h2>
  ),
}));

import { GlobalAgentRoster } from "./GlobalAgentRoster";

function renderWithQueryClient(ui: React.JSX.Element): ReturnType<typeof render> {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: { retry: false },
      mutations: { retry: false },
    },
  });

  return render(<QueryClientProvider client={queryClient}>{ui}</QueryClientProvider>);
}

function makeSession(overrides: Partial<MockSession>): MockSession {
  return {
    id: "session-12345678",
    providerId: "internal",
    providerType: "internal",
    status: "active",
    createdAt: "2026-03-07T10:00:00.000Z",
    updatedAt: "2026-03-07T10:01:00.000Z",
    ...overrides,
  };
}

function getRowForSessionLabel(label: string): HTMLElement {
  const sessionLabel = screen.getByText(label);
  const row = sessionLabel.closest('[role="button"]');

  if (!(row instanceof HTMLElement)) {
    throw new Error(`Expected a row element for session label ${label}`);
  }

  return row;
}

describe("GlobalAgentRoster", (): void => {
  beforeEach((): void => {
    vi.clearAllMocks();
    vi.stubGlobal("fetch", vi.fn());

    mockApiGet.mockResolvedValue([]);
    mockApiPost.mockResolvedValue({ message: "ok" });

    mockKillAllDialog.mockImplementation(
      (): React.JSX.Element => <div data-testid="kill-all-dialog">kill-all-dialog</div>
    );
  });

  afterEach((): void => {
    vi.unstubAllGlobals();
  });

  it("renders the empty state when no active sessions are returned", async (): Promise<void> => {
    renderWithQueryClient(<GlobalAgentRoster />);

    await waitFor((): void => {
      expect(screen.getByText("No active agents")).toBeInTheDocument();
    });

    expect(screen.queryByTestId("kill-all-dialog")).not.toBeInTheDocument();
  });

  it("groups sessions by provider and shows kill-all control when sessions exist", async (): Promise<void> => {
    mockApiGet.mockResolvedValue([
      makeSession({ id: "alpha123456", providerId: "internal", providerType: "internal" }),
      makeSession({ id: "bravo123456", providerId: "codex", providerType: "openai" }),
    ]);

    renderWithQueryClient(<GlobalAgentRoster />);

    await waitFor((): void => {
      expect(screen.getByText("internal")).toBeInTheDocument();
      expect(screen.getByText("codex (openai)")).toBeInTheDocument();
    });

    expect(screen.getByText("alpha123")).toBeInTheDocument();
    expect(screen.getByText("bravo123")).toBeInTheDocument();
    expect(screen.getByTestId("kill-all-dialog")).toBeInTheDocument();
  });

  it("calls onSelectSession on row click and keyboard activation", async (): Promise<void> => {
    const onSelectSession = vi.fn<(sessionId: string) => void>();

    mockApiGet.mockResolvedValue([makeSession({ id: "target123456" })]);

    renderWithQueryClient(<GlobalAgentRoster onSelectSession={onSelectSession} />);

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

    const row = getRowForSessionLabel("target12");

    fireEvent.click(row);

    fireEvent.keyDown(row, { key: "Enter" });

    expect(onSelectSession).toHaveBeenCalledTimes(2);
    expect(onSelectSession).toHaveBeenNthCalledWith(1, "target123456");
    expect(onSelectSession).toHaveBeenNthCalledWith(2, "target123456");
  });

  it("kills a session from the roster", async (): Promise<void> => {
    mockApiGet.mockResolvedValue([makeSession({ id: "killme123456" })]);

    renderWithQueryClient(<GlobalAgentRoster />);

    await waitFor((): void => {
      expect(screen.getByRole("button", { name: "Kill session killme12" })).toBeInTheDocument();
    });

    fireEvent.click(screen.getByRole("button", { name: "Kill session killme12" }));

    await waitFor((): void => {
      expect(mockApiPost).toHaveBeenCalledWith(
        "/api/orchestrator/api/mission-control/sessions/killme123456/kill",
        {
          force: false,
        }
      );
    });
  });

  it("collapses and reopens provider groups", async (): Promise<void> => {
    mockApiGet.mockResolvedValue([makeSession({ id: "grouped12345" })]);

    renderWithQueryClient(<GlobalAgentRoster />);

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

    fireEvent.click(screen.getByRole("button", { name: /internal/i }));

    expect(screen.queryByText("grouped1")).not.toBeInTheDocument();

    fireEvent.click(screen.getByRole("button", { name: /internal/i }));

    expect(screen.getByText("grouped1")).toBeInTheDocument();
  });
});
