/**
 * @file TerminalPanel.test.tsx
 * @description Unit tests for the TerminalPanel component — multi-tab scenarios
 */

import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import type { ReactElement } from "react";

// ==========================================
// Mocks
// ==========================================

// Mock XTerminal to avoid xterm.js DOM dependencies in panel tests
vi.mock("./XTerminal", () => ({
  XTerminal: vi.fn(
    ({
      sessionId,
      isVisible,
      sessionStatus,
    }: {
      sessionId: string;
      isVisible: boolean;
      sessionStatus: string;
    }) => (
      <div
        data-testid="mock-xterminal"
        data-session-id={sessionId}
        data-visible={isVisible ? "true" : "false"}
        data-status={sessionStatus}
      />
    )
  ),
}));

// Mock AgentTerminal to avoid complexity in panel tests
vi.mock("./AgentTerminal", () => ({
  AgentTerminal: vi.fn(
    ({ agent }: { agent: { agentId: string; agentType: string; status: string } }) => (
      <div
        data-testid="mock-agent-terminal"
        data-agent-id={agent.agentId}
        data-agent-type={agent.agentType}
        data-status={agent.status}
      />
    )
  ),
}));

// Mock useTerminalSessions
const mockCreateSession = vi.fn();
const mockCloseSession = vi.fn();
const mockRenameSession = vi.fn();
const mockSetActiveSession = vi.fn();
const mockSendInput = vi.fn();
const mockResize = vi.fn();
const mockRegisterOutputCallback = vi.fn(() => vi.fn());

// Mutable state for the mock — tests update these
let mockSessions = new Map<
  string,
  {
    sessionId: string;
    name: string;
    status: "active" | "exited";
    exitCode?: number;
  }
>();
let mockActiveSessionId: string | null = null;
let mockIsConnected = false;
let mockConnectionError: string | null = null;

vi.mock("@/hooks/useTerminalSessions", () => ({
  useTerminalSessions: vi.fn(() => ({
    sessions: mockSessions,
    activeSessionId: mockActiveSessionId,
    isConnected: mockIsConnected,
    connectionError: mockConnectionError,
    createSession: mockCreateSession,
    closeSession: mockCloseSession,
    renameSession: mockRenameSession,
    setActiveSession: mockSetActiveSession,
    sendInput: mockSendInput,
    resize: mockResize,
    registerOutputCallback: mockRegisterOutputCallback,
  })),
}));

// Mock useAgentStream
const mockDismissAgent = vi.fn();
let mockAgents = new Map<
  string,
  {
    agentId: string;
    agentType: string;
    status: "spawning" | "running" | "completed" | "error";
    outputLines: string[];
    startedAt: number;
  }
>();
let mockAgentStreamConnected = false;

vi.mock("@/hooks/useAgentStream", () => ({
  useAgentStream: vi.fn(() => ({
    agents: mockAgents,
    isConnected: mockAgentStreamConnected,
    connectionError: null,
    dismissAgent: mockDismissAgent,
  })),
}));

import { TerminalPanel } from "./TerminalPanel";

// ==========================================
// Helpers
// ==========================================

function setTwoSessions(): void {
  mockSessions = new Map([
    ["session-1", { sessionId: "session-1", name: "Terminal 1", status: "active" }],
    ["session-2", { sessionId: "session-2", name: "Terminal 2", status: "active" }],
  ]);
  mockActiveSessionId = "session-1";
}

// ==========================================
// Tests
// ==========================================

describe("TerminalPanel", () => {
  const onClose = vi.fn();

  beforeEach(() => {
    vi.clearAllMocks();
    mockSessions = new Map();
    mockActiveSessionId = null;
    mockIsConnected = false;
    mockConnectionError = null;
    mockRegisterOutputCallback.mockReturnValue(vi.fn());
    mockAgents = new Map();
    mockAgentStreamConnected = false;
  });

  // ==========================================
  // Rendering
  // ==========================================

  describe("rendering", () => {
    it("renders the terminal panel", () => {
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      expect(screen.getByRole("region", { name: "Terminal panel" })).toBeInTheDocument();
    });

    it("renders with height 280 when open", () => {
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      const panel = screen.getByRole("region", { name: "Terminal panel" });
      expect(panel).toHaveStyle({ height: "280px" });
    });

    it("renders with height 0 when closed", () => {
      const { container } = render(
        (<TerminalPanel open={false} onClose={onClose} token="test-token" />) as ReactElement
      );
      const panel = container.querySelector('[role="region"][aria-label="Terminal panel"]');
      expect(panel).toHaveStyle({ height: "0px" });
    });

    it("renders empty state when no sessions exist", () => {
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      // No XTerminal instances should be mounted
      expect(screen.queryByTestId("mock-xterminal")).not.toBeInTheDocument();
    });

    it("shows connecting message in empty state when not connected", () => {
      mockIsConnected = false;
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      expect(screen.getByText("Connecting...")).toBeInTheDocument();
    });

    it("shows creating message in empty state when connected", () => {
      mockIsConnected = true;
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      expect(screen.getByText("Creating terminal...")).toBeInTheDocument();
    });
  });

  // ==========================================
  // Tab bar from sessions
  // ==========================================

  describe("tab bar", () => {
    it("renders a tab for each session", () => {
      setTwoSessions();
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      expect(screen.getByRole("tab", { name: "Terminal 1" })).toBeInTheDocument();
      expect(screen.getByRole("tab", { name: "Terminal 2" })).toBeInTheDocument();
    });

    it("marks the active session tab as selected", () => {
      setTwoSessions();
      mockActiveSessionId = "session-2";
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      expect(screen.getByRole("tab", { name: "Terminal 2" })).toHaveAttribute(
        "aria-selected",
        "true"
      );
      expect(screen.getByRole("tab", { name: "Terminal 1" })).toHaveAttribute(
        "aria-selected",
        "false"
      );
    });

    it("calls setActiveSession when a tab is clicked", () => {
      setTwoSessions();
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      fireEvent.click(screen.getByRole("tab", { name: "Terminal 2" }));
      expect(mockSetActiveSession).toHaveBeenCalledWith("session-2");
    });

    it("has tablist role on the tab bar", () => {
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      expect(screen.getByRole("tablist")).toBeInTheDocument();
    });
  });

  // ==========================================
  // New tab button
  // ==========================================

  describe("new tab button", () => {
    it("renders the new tab button", () => {
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      expect(screen.getByRole("button", { name: "New terminal tab" })).toBeInTheDocument();
    });

    it("calls createSession when new tab button is clicked", () => {
      setTwoSessions();
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      fireEvent.click(screen.getByRole("button", { name: "New terminal tab" }));
      expect(mockCreateSession).toHaveBeenCalledWith(
        // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
        expect.objectContaining({ name: expect.any(String) })
      );
    });
  });

  // ==========================================
  // Per-tab close button
  // ==========================================

  describe("per-tab close button", () => {
    it("renders a close button for each tab", () => {
      setTwoSessions();
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      expect(screen.getByRole("button", { name: "Close Terminal 1" })).toBeInTheDocument();
      expect(screen.getByRole("button", { name: "Close Terminal 2" })).toBeInTheDocument();
    });

    it("calls closeSession with the correct sessionId when tab close is clicked", () => {
      setTwoSessions();
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      fireEvent.click(screen.getByRole("button", { name: "Close Terminal 1" }));
      expect(mockCloseSession).toHaveBeenCalledWith("session-1");
    });
  });

  // ==========================================
  // Panel close button
  // ==========================================

  describe("panel close button", () => {
    it("renders the close panel button", () => {
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      expect(screen.getByRole("button", { name: "Close terminal" })).toBeInTheDocument();
    });

    it("calls onClose when close panel button is clicked", () => {
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      fireEvent.click(screen.getByRole("button", { name: "Close terminal" }));
      expect(onClose).toHaveBeenCalledTimes(1);
    });
  });

  // ==========================================
  // Multi-tab XTerminal rendering
  // ==========================================

  describe("multi-tab terminal rendering", () => {
    it("renders an XTerminal for each session", () => {
      setTwoSessions();
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      const terminals = screen.getAllByTestId("mock-xterminal");
      expect(terminals).toHaveLength(2);
    });

    it("shows the active session terminal as visible", () => {
      setTwoSessions();
      mockActiveSessionId = "session-1";
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      const terminal1 = screen
        .getAllByTestId("mock-xterminal")
        .find((el) => el.getAttribute("data-session-id") === "session-1");
      expect(terminal1).toHaveAttribute("data-visible", "true");
    });

    it("hides inactive session terminals", () => {
      setTwoSessions();
      mockActiveSessionId = "session-1";
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      const terminal2 = screen
        .getAllByTestId("mock-xterminal")
        .find((el) => el.getAttribute("data-session-id") === "session-2");
      expect(terminal2).toHaveAttribute("data-visible", "false");
    });

    it("passes sessionStatus to XTerminal", () => {
      mockSessions = new Map([
        [
          "session-1",
          { sessionId: "session-1", name: "Terminal 1", status: "exited", exitCode: 0 },
        ],
      ]);
      mockActiveSessionId = "session-1";
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      const terminal = screen.getByTestId("mock-xterminal");
      expect(terminal).toHaveAttribute("data-status", "exited");
    });

    it("passes isVisible=false to all terminals when panel is closed", () => {
      setTwoSessions();
      const { container } = render(
        (<TerminalPanel open={false} onClose={onClose} token="test-token" />) as ReactElement
      );
      const terminals = container.querySelectorAll('[data-testid="mock-xterminal"]');
      terminals.forEach((terminal) => {
        expect(terminal).toHaveAttribute("data-visible", "false");
      });
    });
  });

  // ==========================================
  // Inline tab rename
  // ==========================================

  describe("tab rename", () => {
    it("shows a rename input when a tab is double-clicked", () => {
      setTwoSessions();
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      fireEvent.dblClick(screen.getByRole("tab", { name: "Terminal 1" }));
      expect(screen.getByTestId("tab-rename-input")).toBeInTheDocument();
    });

    it("calls renameSession when rename input loses focus", () => {
      setTwoSessions();
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      fireEvent.dblClick(screen.getByRole("tab", { name: "Terminal 1" }));

      const input = screen.getByTestId("tab-rename-input");
      fireEvent.change(input, { target: { value: "Custom Shell" } });
      fireEvent.blur(input);

      expect(mockRenameSession).toHaveBeenCalledWith("session-1", "Custom Shell");
    });

    it("calls renameSession when Enter is pressed in the rename input", () => {
      setTwoSessions();
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      fireEvent.dblClick(screen.getByRole("tab", { name: "Terminal 1" }));

      const input = screen.getByTestId("tab-rename-input");
      fireEvent.change(input, { target: { value: "New Name" } });
      fireEvent.keyDown(input, { key: "Enter" });

      expect(mockRenameSession).toHaveBeenCalledWith("session-1", "New Name");
    });

    it("cancels rename when Escape is pressed", () => {
      setTwoSessions();
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      fireEvent.dblClick(screen.getByRole("tab", { name: "Terminal 1" }));

      const input = screen.getByTestId("tab-rename-input");
      fireEvent.change(input, { target: { value: "Abandoned Name" } });
      fireEvent.keyDown(input, { key: "Escape" });

      expect(mockRenameSession).not.toHaveBeenCalled();
      expect(screen.queryByTestId("tab-rename-input")).not.toBeInTheDocument();
    });
  });

  // ==========================================
  // Connection error banner
  // ==========================================

  describe("connection error", () => {
    it("shows a connection error banner when connectionError is set", () => {
      mockConnectionError = "WebSocket connection failed";
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      const alert = screen.getByRole("alert");
      expect(alert).toBeInTheDocument();
      expect(alert).toHaveTextContent(/WebSocket connection failed/);
    });

    it("does not show the error banner when connectionError is null", () => {
      mockConnectionError = null;
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      expect(screen.queryByRole("alert")).not.toBeInTheDocument();
    });
  });

  // ==========================================
  // Accessibility
  // ==========================================

  describe("accessibility", () => {
    it("has aria-hidden=true when closed", () => {
      const { container } = render(
        (<TerminalPanel open={false} onClose={onClose} token="test-token" />) as ReactElement
      );
      const panel = container.querySelector('[role="region"][aria-label="Terminal panel"]');
      expect(panel).toHaveAttribute("aria-hidden", "true");
    });

    it("has aria-hidden=false when open", () => {
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      const panel = screen.getByRole("region", { name: "Terminal panel" });
      expect(panel).toHaveAttribute("aria-hidden", "false");
    });
  });

  // ==========================================
  // Auto-create session
  // ==========================================

  describe("auto-create first session", () => {
    it("calls createSession when connected and no sessions exist", () => {
      mockIsConnected = true;
      mockSessions = new Map();
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      expect(mockCreateSession).toHaveBeenCalledWith(
        // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
        expect.objectContaining({ name: expect.any(String) })
      );
    });

    it("does not call createSession when sessions already exist", () => {
      mockIsConnected = true;
      setTwoSessions();
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      expect(mockCreateSession).not.toHaveBeenCalled();
    });

    it("does not call createSession when not connected", () => {
      mockIsConnected = false;
      mockSessions = new Map();
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      expect(mockCreateSession).not.toHaveBeenCalled();
    });

    it("does not call createSession when panel is closed", () => {
      mockIsConnected = true;
      mockSessions = new Map();
      render((<TerminalPanel open={false} onClose={onClose} token="test-token" />) as ReactElement);
      expect(mockCreateSession).not.toHaveBeenCalled();
    });
  });

  // ==========================================
  // Agent tab integration
  // ==========================================

  describe("agent tab integration", () => {
    function setOneAgent(status: "spawning" | "running" | "completed" | "error" = "running"): void {
      mockAgents = new Map([
        [
          "agent-1",
          {
            agentId: "agent-1",
            agentType: "worker",
            status,
            outputLines: ["Hello from agent\n"],
            startedAt: Date.now() - 3000,
          },
        ],
      ]);
    }

    it("renders an agent tab when an agent is active", () => {
      setOneAgent("running");
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      expect(screen.getAllByTestId("agent-tab")).toHaveLength(1);
    });

    it("renders no agent tabs when agents map is empty", () => {
      mockAgents = new Map();
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      expect(screen.queryByTestId("agent-tab")).not.toBeInTheDocument();
    });

    it("agent tab button has the agent type as label", () => {
      setOneAgent("running");
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      expect(screen.getByRole("tab", { name: "Agent: worker" })).toBeInTheDocument();
    });

    it("agent tab has role=tab", () => {
      setOneAgent("running");
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      expect(screen.getByRole("tab", { name: "Agent: worker" })).toBeInTheDocument();
    });

    it("shows dismiss button for completed agents", () => {
      setOneAgent("completed");
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      expect(screen.getByRole("button", { name: "Dismiss worker agent" })).toBeInTheDocument();
    });

    it("shows dismiss button for error agents", () => {
      setOneAgent("error");
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      expect(screen.getByRole("button", { name: "Dismiss worker agent" })).toBeInTheDocument();
    });

    it("does not show dismiss button for running agents", () => {
      setOneAgent("running");
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      expect(
        screen.queryByRole("button", { name: "Dismiss worker agent" })
      ).not.toBeInTheDocument();
    });

    it("does not show dismiss button for spawning agents", () => {
      setOneAgent("spawning");
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      expect(
        screen.queryByRole("button", { name: "Dismiss worker agent" })
      ).not.toBeInTheDocument();
    });

    it("calls dismissAgent when dismiss button is clicked", () => {
      setOneAgent("completed");
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      fireEvent.click(screen.getByRole("button", { name: "Dismiss worker agent" }));
      expect(mockDismissAgent).toHaveBeenCalledWith("agent-1");
    });

    it("renders AgentTerminal when agent tab is active", () => {
      setOneAgent("running");
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      // Click the agent tab to make it active
      fireEvent.click(screen.getByRole("tab", { name: "Agent: worker" }));
      // AgentTerminal should be rendered (mock shows mock-agent-terminal)
      expect(screen.getByTestId("mock-agent-terminal")).toBeInTheDocument();
    });

    it("shows a divider between terminal and agent tabs", () => {
      setTwoSessions();
      setOneAgent("running");
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      // The divider div is aria-hidden; check it's present in the DOM
      const tablist = screen.getByRole("tablist");
      const divider = tablist.querySelector('[aria-hidden="true"][style*="width: 1"]');
      expect(divider).toBeInTheDocument();
    });

    it("agent tabs show correct data-agent-status", () => {
      setOneAgent("running");
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      const tab = screen.getByTestId("agent-tab");
      expect(tab).toHaveAttribute("data-agent-status", "running");
    });

    it("empty state not shown when agents exist but no terminal sessions", () => {
      mockSessions = new Map();
      setOneAgent("running");
      mockIsConnected = false;
      render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
      expect(screen.queryByText("Connecting...")).not.toBeInTheDocument();
    });
  });
});
