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

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

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

const mockApiPost = vi.fn<(endpoint: string, body?: unknown) => Promise<{ message: string }>>();

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

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

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

import { PanelControls } from "./PanelControls";

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>);
}

describe("PanelControls", (): void => {
  beforeEach((): void => {
    vi.clearAllMocks();
    vi.stubGlobal("fetch", vi.fn());
    mockApiPost.mockResolvedValue({ message: "ok" });
  });

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

  it("renders action buttons with correct disabled state for active sessions", (): void => {
    renderWithQueryClient(<PanelControls sessionId="session-1" status="active" />);

    expect(screen.getByRole("button", { name: "Pause session" })).toBeEnabled();
    expect(screen.getByRole("button", { name: "Resume session" })).toBeDisabled();
    expect(screen.getByRole("button", { name: "Gracefully kill session" })).toBeEnabled();
    expect(screen.getByRole("button", { name: "Force kill session" })).toBeEnabled();
  });

  it("disables all action buttons when session is already killed", (): void => {
    renderWithQueryClient(<PanelControls sessionId="session-2" status="killed" />);

    expect(screen.getByRole("button", { name: "Pause session" })).toBeDisabled();
    expect(screen.getByRole("button", { name: "Resume session" })).toBeDisabled();
    expect(screen.getByRole("button", { name: "Gracefully kill session" })).toBeDisabled();
    expect(screen.getByRole("button", { name: "Force kill session" })).toBeDisabled();
  });

  it("pauses a running session and reports the next status", async (): Promise<void> => {
    const onStatusChange = vi.fn<(status: string) => void>();
    const user = userEvent.setup();

    renderWithQueryClient(
      <PanelControls
        sessionId="session with space"
        status="active"
        onStatusChange={onStatusChange}
      />
    );

    await user.click(screen.getByRole("button", { name: "Pause session" }));

    await waitFor((): void => {
      expect(mockApiPost).toHaveBeenCalledWith(
        "/api/mission-control/sessions/session%20with%20space/pause",
        undefined
      );
    });

    expect(onStatusChange).toHaveBeenCalledWith("paused");
  });

  it("asks for graceful kill confirmation before submitting", async (): Promise<void> => {
    const onStatusChange = vi.fn<(status: string) => void>();
    const user = userEvent.setup();

    renderWithQueryClient(
      <PanelControls sessionId="session-4" status="active" onStatusChange={onStatusChange} />
    );

    await user.click(screen.getByRole("button", { name: "Gracefully kill session" }));

    expect(
      screen.getByText("Gracefully stop this agent after it finishes the current step?")
    ).toBeInTheDocument();

    await user.click(screen.getByRole("button", { name: "Confirm" }));

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

    expect(onStatusChange).toHaveBeenCalledWith("killed");
  });

  it("sends force kill after confirmation", async (): Promise<void> => {
    const onStatusChange = vi.fn<(status: string) => void>();
    const user = userEvent.setup();

    renderWithQueryClient(
      <PanelControls sessionId="session-5" status="paused" onStatusChange={onStatusChange} />
    );

    await user.click(screen.getByRole("button", { name: "Force kill session" }));

    expect(screen.getByText("This will hard-kill the agent immediately.")).toBeInTheDocument();

    await user.click(screen.getByRole("button", { name: "Confirm" }));

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

    expect(onStatusChange).toHaveBeenCalledWith("killed");
  });

  it("shows an error badge when an action fails", async (): Promise<void> => {
    const user = userEvent.setup();

    mockApiPost.mockRejectedValueOnce(new Error("unable to pause"));

    renderWithQueryClient(<PanelControls sessionId="session-6" status="active" />);

    await user.click(screen.getByRole("button", { name: "Pause session" }));

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