/**
 * @file AgentTerminal.test.tsx
 * @description Unit tests for the AgentTerminal component
 *
 * Tests cover:
 * - Output rendering
 * - Status display (status indicator + badge)
 * - ANSI stripping
 * - Agent header information (type, duration, jobId)
 * - Auto-scroll behavior
 * - Copy-to-clipboard
 * - Error message display
 * - Empty state rendering
 */

import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render, screen, fireEvent, act } from "@testing-library/react";
import type { ReactElement } from "react";
import { AgentTerminal } from "./AgentTerminal";
import type { AgentSession } from "@/hooks/useAgentStream";

// ==========================================
// Mock navigator.clipboard
// ==========================================

const mockWriteText = vi.fn(() => Promise.resolve());
Object.defineProperty(navigator, "clipboard", {
  value: { writeText: mockWriteText },
  writable: true,
  configurable: true,
});

// ==========================================
// Factory helpers
// ==========================================

function makeAgent(overrides: Partial<AgentSession> = {}): AgentSession {
  return {
    agentId: "test-agent-1",
    agentType: "worker",
    status: "running",
    outputLines: [],
    startedAt: Date.now() - 5000, // 5s ago
    ...overrides,
  };
}

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

describe("AgentTerminal", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.useFakeTimers({ shouldAdvanceTime: false });
  });

  afterEach(() => {
    vi.useRealTimers();
  });

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

  describe("rendering", () => {
    it("renders the agent terminal container", () => {
      render((<AgentTerminal agent={makeAgent()} />) as ReactElement);
      expect(screen.getByTestId("agent-terminal")).toBeInTheDocument();
    });

    it("has region role with agent type label", () => {
      render((<AgentTerminal agent={makeAgent({ agentType: "planner" })} />) as ReactElement);
      expect(screen.getByRole("region", { name: "Agent output: planner" })).toBeInTheDocument();
    });

    it("sets data-agent-id attribute", () => {
      render((<AgentTerminal agent={makeAgent({ agentId: "my-agent-123" })} />) as ReactElement);
      const container = screen.getByTestId("agent-terminal");
      expect(container).toHaveAttribute("data-agent-id", "my-agent-123");
    });

    it("applies className prop to the outer container", () => {
      render((<AgentTerminal agent={makeAgent()} className="custom-cls" />) as ReactElement);
      expect(screen.getByTestId("agent-terminal")).toHaveClass("custom-cls");
    });
  });

  // ==========================================
  // Header
  // ==========================================

  describe("header", () => {
    it("renders the agent type label", () => {
      render((<AgentTerminal agent={makeAgent({ agentType: "coordinator" })} />) as ReactElement);
      expect(screen.getByTestId("agent-type-label")).toHaveTextContent("coordinator");
    });

    it("includes jobId in the label when provided", () => {
      render(
        (
          <AgentTerminal agent={makeAgent({ agentType: "worker", jobId: "job-42" })} />
        ) as ReactElement
      );
      expect(screen.getByTestId("agent-type-label")).toHaveTextContent("worker · job-42");
    });

    it("does not show jobId separator when jobId is absent", () => {
      render((<AgentTerminal agent={makeAgent({ agentType: "worker" })} />) as ReactElement);
      expect(screen.getByTestId("agent-type-label")).not.toHaveTextContent("·");
    });

    it("renders the duration element", () => {
      render((<AgentTerminal agent={makeAgent()} />) as ReactElement);
      expect(screen.getByTestId("agent-duration")).toBeInTheDocument();
    });

    it("shows seconds for short-running agents", () => {
      const agent = makeAgent({ startedAt: Date.now() - 8000 });
      render((<AgentTerminal agent={agent} />) as ReactElement);
      expect(screen.getByTestId("agent-duration")).toHaveTextContent("s");
    });

    it("shows minutes for long-running agents", () => {
      const agent = makeAgent({ startedAt: Date.now() - 125000 }); // 2m 5s
      render((<AgentTerminal agent={agent} />) as ReactElement);
      expect(screen.getByTestId("agent-duration")).toHaveTextContent("m");
    });
  });

  // ==========================================
  // Status indicator
  // ==========================================

  describe("status indicator", () => {
    it("shows a running indicator for running status", () => {
      render((<AgentTerminal agent={makeAgent({ status: "running" })} />) as ReactElement);
      const indicator = screen.getByTestId("status-indicator");
      expect(indicator).toHaveAttribute("data-status", "running");
    });

    it("shows a spawning indicator for spawning status", () => {
      render((<AgentTerminal agent={makeAgent({ status: "spawning" })} />) as ReactElement);
      const indicator = screen.getByTestId("status-indicator");
      expect(indicator).toHaveAttribute("data-status", "spawning");
    });

    it("shows completed indicator for completed status", () => {
      render(
        (
          <AgentTerminal agent={makeAgent({ status: "completed", endedAt: Date.now() })} />
        ) as ReactElement
      );
      const indicator = screen.getByTestId("status-indicator");
      expect(indicator).toHaveAttribute("data-status", "completed");
    });

    it("shows error indicator for error status", () => {
      render(
        (
          <AgentTerminal agent={makeAgent({ status: "error", endedAt: Date.now() })} />
        ) as ReactElement
      );
      const indicator = screen.getByTestId("status-indicator");
      expect(indicator).toHaveAttribute("data-status", "error");
    });
  });

  // ==========================================
  // Status badge
  // ==========================================

  describe("status badge", () => {
    it("renders the status badge", () => {
      render((<AgentTerminal agent={makeAgent({ status: "running" })} />) as ReactElement);
      expect(screen.getByTestId("status-badge")).toHaveTextContent("running");
    });

    it("shows 'spawning' badge for spawning status", () => {
      render((<AgentTerminal agent={makeAgent({ status: "spawning" })} />) as ReactElement);
      expect(screen.getByTestId("status-badge")).toHaveTextContent("spawning");
    });

    it("shows 'completed' badge for completed status", () => {
      render(
        (
          <AgentTerminal agent={makeAgent({ status: "completed", endedAt: Date.now() })} />
        ) as ReactElement
      );
      expect(screen.getByTestId("status-badge")).toHaveTextContent("completed");
    });

    it("shows 'error' badge for error status", () => {
      render(
        (
          <AgentTerminal agent={makeAgent({ status: "error", endedAt: Date.now() })} />
        ) as ReactElement
      );
      expect(screen.getByTestId("status-badge")).toHaveTextContent("error");
    });
  });

  // ==========================================
  // Output rendering
  // ==========================================

  describe("output area", () => {
    it("renders the output pre element", () => {
      render((<AgentTerminal agent={makeAgent()} />) as ReactElement);
      expect(screen.getByTestId("agent-output")).toBeInTheDocument();
    });

    it("shows 'Waiting for output...' when outputLines is empty and status is running", () => {
      render(
        (
          <AgentTerminal agent={makeAgent({ status: "running", outputLines: [] })} />
        ) as ReactElement
      );
      expect(screen.getByTestId("agent-output")).toHaveTextContent("Waiting for output...");
    });

    it("shows 'Spawning agent...' when status is spawning and no output", () => {
      render(
        (
          <AgentTerminal agent={makeAgent({ status: "spawning", outputLines: [] })} />
        ) as ReactElement
      );
      expect(screen.getByTestId("agent-output")).toHaveTextContent("Spawning agent...");
    });

    it("renders output lines as text content", () => {
      const agent = makeAgent({
        outputLines: ["Hello world\n", "Second line\n"],
      });
      render((<AgentTerminal agent={agent} />) as ReactElement);
      const output = screen.getByTestId("agent-output");
      expect(output).toHaveTextContent("Hello world");
      expect(output).toHaveTextContent("Second line");
    });

    it("strips ANSI escape codes from output", () => {
      const agent = makeAgent({
        outputLines: ["\x1b[32mGreen text\x1b[0m\n"],
      });
      render((<AgentTerminal agent={agent} />) as ReactElement);
      const output = screen.getByTestId("agent-output");
      expect(output).toHaveTextContent("Green text");
      expect(output.textContent).not.toContain("\x1b");
    });

    it("has aria-live=polite for screen reader announcements", () => {
      render((<AgentTerminal agent={makeAgent()} />) as ReactElement);
      expect(screen.getByTestId("agent-output")).toHaveAttribute("aria-live", "polite");
    });
  });

  // ==========================================
  // Error message
  // ==========================================

  describe("error message", () => {
    it("shows error message when status is error and errorMessage is set", () => {
      const agent = makeAgent({
        status: "error",
        endedAt: Date.now(),
        errorMessage: "Process crashed",
      });
      render((<AgentTerminal agent={agent} />) as ReactElement);
      expect(screen.getByTestId("agent-error-message")).toHaveTextContent("Process crashed");
    });

    it("renders alert role for error message", () => {
      const agent = makeAgent({
        status: "error",
        endedAt: Date.now(),
        errorMessage: "OOM killed",
      });
      render((<AgentTerminal agent={agent} />) as ReactElement);
      expect(screen.getByRole("alert")).toBeInTheDocument();
    });

    it("does not show error message when status is running", () => {
      render((<AgentTerminal agent={makeAgent({ status: "running" })} />) as ReactElement);
      expect(screen.queryByTestId("agent-error-message")).not.toBeInTheDocument();
    });

    it("does not show error message when status is error but errorMessage is absent", () => {
      const agent = makeAgent({ status: "error", endedAt: Date.now() });
      render((<AgentTerminal agent={agent} />) as ReactElement);
      expect(screen.queryByTestId("agent-error-message")).not.toBeInTheDocument();
    });
  });

  // ==========================================
  // Copy to clipboard
  // ==========================================

  describe("copy to clipboard", () => {
    it("renders the copy button", () => {
      render((<AgentTerminal agent={makeAgent()} />) as ReactElement);
      expect(screen.getByTestId("copy-button")).toBeInTheDocument();
    });

    it("copy button has aria-label='Copy agent output'", () => {
      render((<AgentTerminal agent={makeAgent()} />) as ReactElement);
      expect(screen.getByRole("button", { name: "Copy agent output" })).toBeInTheDocument();
    });

    it("calls clipboard.writeText with stripped output on click", async () => {
      const agent = makeAgent({
        outputLines: ["\x1b[32mLine 1\x1b[0m\n", "Line 2\n"],
      });
      render((<AgentTerminal agent={agent} />) as ReactElement);

      await act(async () => {
        fireEvent.click(screen.getByTestId("copy-button"));
        await Promise.resolve();
      });

      expect(mockWriteText).toHaveBeenCalledWith("Line 1\nLine 2\n");
    });

    it("shows 'copied' text briefly after clicking copy", async () => {
      render((<AgentTerminal agent={makeAgent()} />) as ReactElement);

      await act(async () => {
        fireEvent.click(screen.getByTestId("copy-button"));
        await Promise.resolve();
      });

      expect(screen.getByTestId("copy-button")).toHaveTextContent("copied");
    });

    it("reverts copy button text after timeout", async () => {
      render((<AgentTerminal agent={makeAgent()} />) as ReactElement);

      await act(async () => {
        fireEvent.click(screen.getByTestId("copy-button"));
        await Promise.resolve();
      });

      act(() => {
        vi.advanceTimersByTime(2500);
      });

      expect(screen.getByTestId("copy-button")).toHaveTextContent("copy");
    });
  });

  // ==========================================
  // Auto-scroll
  // ==========================================

  describe("auto-scroll", () => {
    it("does not throw when outputLines changes", () => {
      const agent = makeAgent({ outputLines: ["Line 1\n"] });
      const { rerender } = render((<AgentTerminal agent={agent} />) as ReactElement);

      expect(() => {
        rerender(
          (
            <AgentTerminal agent={{ ...agent, outputLines: ["Line 1\n", "Line 2\n"] }} />
          ) as ReactElement
        );
      }).not.toThrow();
    });
  });
});
