import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import type { ButtonHTMLAttributes, HTMLAttributes, ReactNode } from "react";

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

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

type MockConnectionStatus = "connected" | "connecting" | "error";
type MockRole = "user" | "assistant" | "tool" | "system";

interface MockMessage {
  id: string;
  role: MockRole;
  content: string;
  timestamp: string;
}

interface MockSession {
  id: string;
  status: string;
}

interface MockSessionStreamResult {
  messages: MockMessage[];
  status: MockConnectionStatus;
  error: string | null;
}

interface MockSessionsResult {
  sessions: MockSession[];
  loading: boolean;
  error: Error | null;
}

interface MockPanelControlsProps {
  sessionId: string;
  status: string;
  onStatusChange?: (nextStatus: string) => void;
}

interface MockBargeInInputProps {
  sessionId: string;
}

const mockUseSessionStream = vi.fn<(sessionId: string) => MockSessionStreamResult>();
const mockUseSessions = vi.fn<() => MockSessionsResult>();
const mockPanelControls = vi.fn<(props: MockPanelControlsProps) => React.JSX.Element>();
const mockBargeInInput = vi.fn<(props: MockBargeInInputProps) => React.JSX.Element>();

vi.mock("date-fns", () => ({
  formatDistanceToNow: (): string => "moments ago",
}));

vi.mock("@/hooks/useMissionControl", () => ({
  useSessionStream: (sessionId: string): MockSessionStreamResult => mockUseSessionStream(sessionId),
  useSessions: (): MockSessionsResult => mockUseSessions(),
}));

vi.mock("@/components/mission-control/PanelControls", () => ({
  PanelControls: (props: MockPanelControlsProps): React.JSX.Element => mockPanelControls(props),
}));

vi.mock("@/components/mission-control/BargeInInput", () => ({
  BargeInInput: (props: MockBargeInInputProps): React.JSX.Element => mockBargeInInput(props),
}));

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

vi.mock("@/components/ui/scroll-area", () => ({
  ScrollArea: ({ children, ...props }: MockContainerProps): React.JSX.Element => (
    <div {...props}>{children}</div>
  ),
}));

import { OrchestratorPanel } from "./OrchestratorPanel";

beforeAll((): void => {
  Object.defineProperty(window.HTMLElement.prototype, "scrollIntoView", {
    configurable: true,
    value: vi.fn(),
  });
});

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

    mockUseSessionStream.mockReturnValue({
      messages: [],
      status: "connecting",
      error: null,
    });

    mockUseSessions.mockReturnValue({
      sessions: [],
      loading: false,
      error: null,
    });

    mockPanelControls.mockImplementation(
      ({ status }: MockPanelControlsProps): React.JSX.Element => (
        <div data-testid="panel-controls">status:{status}</div>
      )
    );

    mockBargeInInput.mockImplementation(
      ({ sessionId }: MockBargeInInputProps): React.JSX.Element => (
        <textarea aria-label="barge-input" data-session-id={sessionId} />
      )
    );
  });

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

  it("renders a selectable empty state when no session is provided", (): void => {
    render(<OrchestratorPanel />);

    expect(screen.getByText("Select an agent to view its stream")).toBeInTheDocument();
    expect(screen.queryByText("Session: session-1")).not.toBeInTheDocument();
  });

  it("renders connection indicator and panel controls for an active session", (): void => {
    mockUseSessionStream.mockReturnValue({
      messages: [],
      status: "connected",
      error: null,
    });

    mockUseSessions.mockReturnValue({
      sessions: [{ id: "session-1", status: "paused" }],
      loading: false,
      error: null,
    });

    render(<OrchestratorPanel sessionId="session-1" />);

    expect(screen.getByText("Connected")).toBeInTheDocument();
    expect(screen.getByText("Session: session-1")).toBeInTheDocument();
    expect(screen.getByText("Waiting for messages...")).toBeInTheDocument();
    expect(screen.getByTestId("panel-controls")).toHaveTextContent("status:paused");
  });

  it("renders stream messages with role and content", (): void => {
    mockUseSessionStream.mockReturnValue({
      status: "connected",
      error: null,
      messages: [
        {
          id: "msg-1",
          role: "assistant",
          content: "Mission accepted.",
          timestamp: "2026-03-07T18:42:00.000Z",
        },
      ],
    });

    render(<OrchestratorPanel sessionId="session-2" />);

    expect(screen.getByText("assistant")).toBeInTheDocument();
    expect(screen.getByText("Mission accepted.")).toBeInTheDocument();
    expect(screen.getByText("moments ago")).toBeInTheDocument();
    expect(screen.getByLabelText("barge-input")).toHaveAttribute("data-session-id", "session-2");
  });

  it("renders stream error text when the session has no messages", (): void => {
    mockUseSessionStream.mockReturnValue({
      messages: [],
      status: "error",
      error: "Mission Control stream disconnected.",
    });

    render(<OrchestratorPanel sessionId="session-3" />);

    expect(screen.getByText("Error")).toBeInTheDocument();
    expect(screen.getByText("Mission Control stream disconnected.")).toBeInTheDocument();
  });

  it("respects close button disabled state in panel actions", (): void => {
    const onClose = vi.fn<() => void>();

    render(<OrchestratorPanel onClose={onClose} closeDisabled />);

    expect(screen.getByRole("button", { name: "Remove panel" })).toBeDisabled();
  });
});
