/**
 * #3 — Agent dropdown per cell: setCellAgent persists in the layout, and the
 * terminal re-mounts with the agent opener; "Plain" reverts to a plain terminal.
 */
import { describe, it, expect, vi } from "vitest";
import { render, screen, waitFor, fireEvent } from "@testing-library/react";

import type { Gateways } from "@/ports";
import {
  MockLayoutGateway,
  MockAgentGateway,
  MockTerminalGateway,
} from "@/adapters/mock";
import { DIProvider } from "@/app/di";
import { LayoutGrid } from "./LayoutGrid";
import { leaves } from "./layout";

function renderGrid(
  layout: MockLayoutGateway,
  agent: MockAgentGateway,
  projectId = "p1",
  terminal: MockTerminalGateway = new MockTerminalGateway(),
) {
  const gateways = {
    layout,
    terminal,
    agent,
  } as unknown as Gateways;
  return render(
    <DIProvider gateways={gateways}>
      <LayoutGrid projectId={projectId} cwd="/home/me/proj" />
    </DIProvider>,
  );
}

async function waitForAgentOption(name = "MyAgent") {
  await screen.findByRole("option", { name });
}

describe("setCellAgent (agent dropdown per cell)", () => {
  it("renders an agent selector on each leaf cell", async () => {
    const layout = new MockLayoutGateway();
    const agent = new MockAgentGateway();
    renderGrid(layout, agent);

    await waitFor(() => {
      expect(screen.getAllByRole("combobox")).toHaveLength(1);
    });
  });

  it("selecting an agent calls setCellAgent via the layout gateway", async () => {
    const layout = new MockLayoutGateway();
    const agent = new MockAgentGateway();

    // Create an agent in the project.
    const a = await agent.createAgent("p1", {
      name: "MyAgent",
      profileId: "prof-1",
    });

    renderGrid(layout, agent);

    await waitFor(() => {
      expect(screen.getByRole("combobox")).toBeTruthy();
    });
    await waitForAgentOption();

    const select = screen.getByRole("combobox") as HTMLSelectElement;
    fireEvent.change(select, { target: { value: a.id } });

    // The layout should persist the agent on the leaf.
    const initial = await layout.loadLayout("p1");
    const leafId = leaves(initial)[0].id;

    await waitFor(async () => {
      const tree = await layout.loadLayout("p1");
      const leaf = leaves(tree)[0];
      // The mutation should have persisted.
      expect(leaf.id).toBe(leafId);
      expect(leaf.agent).toBe(a.id);
      // Since mutateLayout is async, give it a tick.
    });
  });

  it("MockLayoutGateway setCellAgent persists and clears correctly", async () => {
    const layout = new MockLayoutGateway();
    const tree = await layout.loadLayout("p1");
    const leafId = leaves(tree)[0].id;

    // Set agent.
    const after = await layout.mutateLayout("p1", {
      type: "setCellAgent",
      target: leafId,
      agent: "agent-42",
    });
    expect(leaves(after)[0].agent).toBe("agent-42");

    // Clear agent (null).
    const cleared = await layout.mutateLayout("p1", {
      type: "setCellAgent",
      target: leafId,
      agent: null,
    });
    expect(leaves(cleared)[0].agent).toBeUndefined();
  });

  it("selecting Plain (empty) clears the agent via mutateLayout", async () => {
    const layout = new MockLayoutGateway();
    const agent = new MockAgentGateway();
    const a = await agent.createAgent("p1", {
      name: "MyAgent",
      profileId: "prof-1",
    });

    // Pre-set agent on the leaf.
    const tree = await layout.loadLayout("p1");
    const leafId = leaves(tree)[0].id;
    await layout.mutateLayout("p1", {
      type: "setCellAgent",
      target: leafId,
      agent: a.id,
    });

    renderGrid(layout, agent);

    await waitFor(() => {
      const sel = screen.getByRole("combobox") as HTMLSelectElement;
      // The select should show the agent (or Plain if the component loaded before the mutation).
      expect(sel).toBeTruthy();
    });

    const select = screen.getByRole("combobox") as HTMLSelectElement;
    // Switch back to Plain.
    fireEvent.change(select, { target: { value: "" } });

    // After clearing, the layout should have no agent on the leaf.
    await waitFor(async () => {
      const fresh = await layout.loadLayout("p1");
      const l = leaves(fresh)[0];
      expect(l.agent === undefined || l.agent === null).toBe(true);
    });
  });

  it("changing the agent resets the cell session to null (Bug #3)", async () => {
    const layout = new MockLayoutGateway();
    const agent = new MockAgentGateway();
    const a = await agent.createAgent("p1", { name: "MyAgent", profileId: "p" });

    // Pre-set a running session on the leaf (as if a plain terminal had opened).
    const tree = await layout.loadLayout("p1");
    const leafId = leaves(tree)[0].id;
    await layout.mutateLayout("p1", {
      type: "setSession",
      target: leafId,
      session: "old-session",
    });

    renderGrid(layout, agent);
    await waitFor(() => expect(screen.getByRole("combobox")).toBeTruthy());
    await waitForAgentOption();

    fireEvent.change(screen.getByRole("combobox"), { target: { value: a.id } });

    await waitFor(async () => {
      const fresh = await layout.loadLayout("p1");
      const l = leaves(fresh)[0];
      expect(l.agent).toBe(a.id);
      // The stale session must be cleared so the new agent opens fresh.
      expect(l.session === null || l.session === undefined).toBe(true);
    });
  });

  it("changing the agent kills the previous PTY (Bug #3)", async () => {
    const layout = new MockLayoutGateway();
    const agent = new MockAgentGateway();
    const terminal = new MockTerminalGateway();
    const closeSpy = vi.spyOn(terminal, "closeTerminal");
    const a = await agent.createAgent("p1", { name: "MyAgent", profileId: "p" });

    const tree = await layout.loadLayout("p1");
    const leafId = leaves(tree)[0].id;
    await layout.mutateLayout("p1", {
      type: "setSession",
      target: leafId,
      session: "old-session",
    });

    renderGrid(layout, agent, "p1", terminal);
    await waitFor(() => expect(screen.getByRole("combobox")).toBeTruthy());
    await waitForAgentOption();

    fireEvent.change(screen.getByRole("combobox"), { target: { value: a.id } });

    await waitFor(() => {
      expect(closeSpy).toHaveBeenCalledWith("old-session");
    });
  });

  it("does not kill any PTY when the cell had no session (Bug #3)", async () => {
    const layout = new MockLayoutGateway();
    const agent = new MockAgentGateway();
    const terminal = new MockTerminalGateway();
    const closeSpy = vi.spyOn(terminal, "closeTerminal");
    const a = await agent.createAgent("p1", { name: "MyAgent", profileId: "p" });

    renderGrid(layout, agent, "p1", terminal);
    await waitFor(() => expect(screen.getByRole("combobox")).toBeTruthy());
    await waitForAgentOption();

    fireEvent.change(screen.getByRole("combobox"), { target: { value: a.id } });

    await waitFor(async () => {
      const fresh = await layout.loadLayout("p1");
      expect(leaves(fresh)[0].agent).toBe(a.id);
    });
    expect(closeSpy).not.toHaveBeenCalled();
  });

  it("closing a cell kills the dropped cell's PTY (no orphan)", async () => {
    const layout = new MockLayoutGateway();
    const agent = new MockAgentGateway();
    const terminal = new MockTerminalGateway();
    const closeSpy = vi.spyOn(terminal, "closeTerminal");

    // Split the root into two cells, then give the second a running session.
    const tree = await layout.loadLayout("p1");
    const rootId = leaves(tree)[0].id;
    const afterSplit = await layout.mutateLayout("p1", {
      type: "split",
      target: rootId,
      direction: "row",
      newLeaf: "leaf-b",
      container: "split-c",
    });
    const droppedId = leaves(afterSplit)[1].id;
    await layout.mutateLayout("p1", {
      type: "setSession",
      target: droppedId,
      session: "dropped-session",
    });

    renderGrid(layout, agent, "p1", terminal);

    // Close the SECOND cell: keep the first, drop the second. Closing a cell is
    // now a detach-only view operation; it must not kill the PTY.
    await waitFor(() => {
      expect(screen.getByLabelText(`close ${droppedId}`)).toBeTruthy();
    });
    fireEvent.click(screen.getByLabelText(`close ${droppedId}`));

    await waitFor(() => {
      expect(screen.getAllByTestId("layout-leaf")).toHaveLength(1);
    });
    expect(closeSpy).not.toHaveBeenCalled();
  });

  it("agent selector shows project agents as options", async () => {
    const layout = new MockLayoutGateway();
    const agent = new MockAgentGateway();
    await agent.createAgent("p1", { name: "Agent Alpha", profileId: "p" });
    await agent.createAgent("p1", { name: "Agent Beta", profileId: "p" });

    renderGrid(layout, agent);

    await waitFor(() => {
      expect(screen.getByText("Agent Alpha")).toBeTruthy();
      expect(screen.getByText("Agent Beta")).toBeTruthy();
    });
  });
});
