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

import { DIProvider } from "@/app/di";
import type { AgentProfile } from "@/domain";
import type { Gateways } from "@/ports";
import { CustomAgentChatView } from "./CustomAgentChatView";

const profile: AgentProfile = {
  id: "structured",
  name: "Structured Codex",
  command: "codex",
  args: [],
  contextInjection: { strategy: "conventionFile", target: "AGENTS.md" },
  detect: null,
  cwdTemplate: "{projectRoot}",
  structuredAdapter: "codex",
};

describe("CustomAgentChatView", () => {
  it("cancels only the current turn and keeps the structured session alive", async () => {
    const agent = {
      launchAgentChat: vi.fn(),
      reattachAgentChat: vi.fn(async (sessionId: string) => ({
        sessionId,
        scrollback: [],
      })),
      sendAgentChat: vi.fn(() => new Promise<void>(() => {})),
      cancelAgentChat: vi.fn(async () => {}),
      closeAgentChat: vi.fn(async () => {}),
    };
    const onSessionId = vi.fn();

    render(
      <DIProvider
        gateways={{
          agent,
          system: { pickFile: vi.fn(async () => null) },
        } as unknown as Gateways}
      >
        <CustomAgentChatView
          projectId="project-1"
          agentId="agent-1"
          agentName="Worker"
          profile={profile}
          cwd="/repo"
          nodeId="node-1"
          sessionId="chat-session-1"
          conversationId="conversation-1"
          onSessionId={onSessionId}
          onConversationId={vi.fn()}
        />
      </DIProvider>,
    );

    await waitFor(() =>
      expect(agent.reattachAgentChat).toHaveBeenCalledWith(
        "chat-session-1",
        expect.any(Function),
      ),
    );

    fireEvent.change(screen.getByLabelText(/message CLI custom/), {
      target: { value: "first turn" },
    });
    fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));

    await waitFor(() =>
      expect(agent.sendAgentChat).toHaveBeenCalledWith(
        "chat-session-1",
        "first turn",
        expect.any(Function),
      ),
    );

    fireEvent.click(await screen.findByRole("button", { name: "Cancel" }));

    await waitFor(() =>
      expect(agent.cancelAgentChat).toHaveBeenCalledWith("chat-session-1"),
    );
    expect(agent.closeAgentChat).not.toHaveBeenCalled();
    expect(onSessionId).not.toHaveBeenCalledWith(null);
    expect(screen.getByText("Tour interrompu.")).toBeTruthy();

    fireEvent.change(screen.getByLabelText(/message CLI custom/), {
      target: { value: "second turn" },
    });
    fireEvent.click(screen.getByRole("button", { name: "Envoyer" }));

    await waitFor(() => expect(agent.sendAgentChat).toHaveBeenCalledTimes(2));
    expect(agent.sendAgentChat).toHaveBeenLastCalledWith(
      "chat-session-1",
      "second turn",
      expect.any(Function),
    );
  });

  it("falls back to a fresh launch when reattach reports NOT_FOUND", async () => {
    const agent = {
      launchAgentChat: vi.fn(async () => ({
        sessionId: "chat-session-2",
        assignedConversationId: "conversation-2",
      })),
      reattachAgentChat: vi
        .fn()
        .mockRejectedValueOnce({ code: "NOT_FOUND", message: "structured session gone" })
        .mockImplementationOnce(async (sessionId: string) => ({
          sessionId,
          scrollback: [],
        })),
      sendAgentChat: vi.fn(() => new Promise<void>(() => {})),
      cancelAgentChat: vi.fn(async () => {}),
      closeAgentChat: vi.fn(async () => {}),
    };
    const onSessionId = vi.fn();

    render(
      <DIProvider
        gateways={{
          agent,
          system: { pickFile: vi.fn(async () => null) },
        } as unknown as Gateways}
      >
        <CustomAgentChatView
          projectId="project-1"
          agentId="agent-1"
          agentName="Worker"
          profile={profile}
          cwd="/repo"
          nodeId="node-1"
          sessionId="stale-session"
          conversationId="conversation-1"
          onSessionId={onSessionId}
          onConversationId={vi.fn()}
        />
      </DIProvider>,
    );

    await waitFor(() =>
      expect(agent.launchAgentChat).toHaveBeenCalledWith("project-1", "agent-1", {
        cwd: "/repo",
        rows: 24,
        cols: 80,
        conversationId: "conversation-1",
        nodeId: "node-1",
      }),
    );
    await waitFor(() =>
      expect(onSessionId).toHaveBeenCalledWith("chat-session-2"),
    );
    expect(screen.queryByText(/structured session gone/)).toBeNull();
  });

  it("surfaces non-NOT_FOUND reattach errors instead of launching a new session", async () => {
    const agent = {
      launchAgentChat: vi.fn(),
      reattachAgentChat: vi.fn().mockRejectedValueOnce({
        code: "PROCESS",
        message: "structured session failed to attach",
      }),
      sendAgentChat: vi.fn(() => new Promise<void>(() => {})),
      cancelAgentChat: vi.fn(async () => {}),
      closeAgentChat: vi.fn(async () => {}),
    };

    render(
      <DIProvider
        gateways={{
          agent,
          system: { pickFile: vi.fn(async () => null) },
        } as unknown as Gateways}
      >
        <CustomAgentChatView
          projectId="project-1"
          agentId="agent-1"
          agentName="Worker"
          profile={profile}
          cwd="/repo"
          nodeId="node-1"
          sessionId="existing-session"
          conversationId="conversation-1"
          onSessionId={vi.fn()}
          onConversationId={vi.fn()}
        />
      </DIProvider>,
    );

    expect((await screen.findByRole("alert")).textContent).toContain(
      "structured session failed to attach",
    );
    expect(agent.launchAgentChat).not.toHaveBeenCalled();
  });
});
