/**
 * @file TerminalView.test.tsx
 * @description Tests for the xterm.js-backed terminal view: verifies it opens
 * a WS connection to the right URL, writes incoming binary frames to the
 * mocked terminal, and forwards typed input as outgoing binary frames.
 * @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
 */
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render, cleanup } from "@testing-library/react";
import { TerminalView } from "../TerminalView";

const writeMock = vi.fn();
const onDataHandlers: Array<(d: string) => void> = [];
const openMock = vi.fn();
const disposeMock = vi.fn();

vi.mock("@xterm/xterm", () => ({
  Terminal: vi.fn().mockImplementation(() => ({
    open: openMock,
    write: writeMock,
    onData: (fn: (d: string) => void) => {
      onDataHandlers.push(fn);
      return { dispose: vi.fn() };
    },
    dispose: disposeMock,
    loadAddon: vi.fn(),
  })),
}));
vi.mock("@xterm/addon-fit", () => ({
  FitAddon: vi.fn().mockImplementation(() => ({ fit: vi.fn() })),
}));

class MockWebSocket {
  static instances: MockWebSocket[] = [];
  url: string;
  sent: unknown[] = [];
  onopen: (() => void) | null = null;
  onmessage: ((e: { data: unknown }) => void) | null = null;
  onclose: (() => void) | null = null;
  constructor(url: string) {
    this.url = url;
    MockWebSocket.instances.push(this);
  }
  send(data: unknown) {
    this.sent.push(data);
  }
  close() {
    this.onclose?.();
  }
}
// @ts-expect-error test override
global.WebSocket = MockWebSocket;

describe("TerminalView", () => {
  beforeEach(() => {
    MockWebSocket.instances = [];
    onDataHandlers.length = 0;
    writeMock.mockClear();
    openMock.mockClear();
  });

  afterEach(cleanup);

  it("opens a WS connection to the run's ws-pty path", () => {
    render(<TerminalView runId="ccam-lane-1" wsBaseUrl="ws://localhost:4820" />);
    expect(MockWebSocket.instances).toHaveLength(1);
    expect(MockWebSocket.instances[0]!.url).toBe("ws://localhost:4820/ws-pty/ccam-lane-1");
  });

  it("writes incoming WS data to the terminal", () => {
    render(<TerminalView runId="ccam-lane-1" wsBaseUrl="ws://localhost:4820" />);
    const ws = MockWebSocket.instances[0]!;
    ws.onopen?.();
    ws.onmessage?.({ data: "hello" });
    expect(writeMock).toHaveBeenCalledWith("hello");
  });

  it("decodes binary ArrayBuffer frames (server sends PTY output as binary)", () => {
    render(<TerminalView runId="ccam-lane-1" wsBaseUrl="ws://localhost:4820" />);
    const ws = MockWebSocket.instances[0]!;
    ws.onopen?.();
    const bytes = new TextEncoder().encode("hello-binary").buffer;
    ws.onmessage?.({ data: bytes });
    expect(writeMock).toHaveBeenCalledWith("hello-binary");
  });

  it("forwards terminal keystrokes as outgoing WS sends", () => {
    render(<TerminalView runId="ccam-lane-1" wsBaseUrl="ws://localhost:4820" />);
    const ws = MockWebSocket.instances[0]!;
    onDataHandlers[0]!("ls -la\r");
    expect(ws.sent).toEqual(["ls -la\r"]);
  });
});
