import { act, render, screen, waitFor } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { AgentEvent, AgentInfo } from "../api/agents";
import { agentsApi, subscribeAgentStream } from "../api/agents";
import { api } from "../api/client";

vi.mock("../api/client", () => ({
	api: {
		getWorkItemContent: vi.fn(),
	},
}));

vi.mock("../api/agents", () => ({
	agentsApi: {
		listAgents: vi.fn(),
	},
	subscribeAgentStream: vi.fn(() => () => {}),
}));

// Dynamic import so mocks are in place before the module loads
const { WorkItemDetailPanel } = await import("./WorkItemDetailPanel");

const mockedGetWorkItemContent = vi.mocked(api.getWorkItemContent);
const mockedListAgents = vi.mocked(agentsApi.listAgents);
const mockedSubscribeAgentStream = vi.mocked(subscribeAgentStream);

const DEFAULT_CONTENT = {
	content: "# Big Title\n\nSome content here.",
	stage: "current",
	name: "Big Title Story",
};

describe("WorkItemDetailPanel", () => {
	beforeEach(() => {
		vi.clearAllMocks();
		mockedGetWorkItemContent.mockResolvedValue(DEFAULT_CONTENT);
		mockedListAgents.mockResolvedValue([]);
		mockedSubscribeAgentStream.mockReturnValue(() => {});
	});

	it("renders the story name in the header", async () => {
		render(<WorkItemDetailPanel storyId="237_bug_test" onClose={() => {}} />);
		await waitFor(() => {
			expect(screen.getByTestId("detail-panel-title")).toHaveTextContent(
				"Big Title Story",
			);
		});
	});

	it("shows loading state initially", () => {
		render(<WorkItemDetailPanel storyId="237_bug_test" onClose={() => {}} />);
		expect(screen.getByTestId("detail-panel-loading")).toBeInTheDocument();
	});

	it("calls onClose when close button is clicked", async () => {
		const onClose = vi.fn();
		render(<WorkItemDetailPanel storyId="237_bug_test" onClose={onClose} />);
		const closeButton = screen.getByTestId("detail-panel-close");
		closeButton.click();
		expect(onClose).toHaveBeenCalledTimes(1);
	});

	it("renders markdown headings with constrained inline font size", async () => {
		render(<WorkItemDetailPanel storyId="237_bug_test" onClose={() => {}} />);
		await waitFor(() => {
			const content = screen.getByTestId("detail-panel-content");
			const h1 = content.querySelector("h1");
			expect(h1).not.toBeNull();
			expect(h1?.style.fontSize).toBeTruthy();
		});
	});
});

describe("WorkItemDetailPanel - Agent Logs", () => {
	beforeEach(() => {
		vi.clearAllMocks();
		mockedGetWorkItemContent.mockResolvedValue(DEFAULT_CONTENT);
		mockedListAgents.mockResolvedValue([]);
		mockedSubscribeAgentStream.mockReturnValue(() => {});
	});

	it("shows placeholder when no agent is assigned to the story", async () => {
		render(<WorkItemDetailPanel storyId="42_story_test" onClose={() => {}} />);
		await screen.findByTestId("detail-panel-content");
		const placeholder = screen.getByTestId("placeholder-agent-logs");
		expect(placeholder).toBeInTheDocument();
		expect(placeholder).toHaveTextContent("Coming soon");
	});

	it("shows agent name and running status when agent is running", async () => {
		const agentList: AgentInfo[] = [
			{
				story_id: "42_story_test",
				agent_name: "coder-1",
				status: "running",
				session_id: null,
				worktree_path: "/tmp/wt",
				base_branch: "master",
				log_session_id: null,
			},
		];
		mockedListAgents.mockResolvedValue(agentList);

		render(<WorkItemDetailPanel storyId="42_story_test" onClose={() => {}} />);

		const statusBadge = await screen.findByTestId("agent-status-badge");
		expect(statusBadge).toHaveTextContent("coder-1");
		expect(statusBadge).toHaveTextContent("running");
	});

	it("shows log output when agent emits output events", async () => {
		let emitEvent: ((e: AgentEvent) => void) | null = null;
		mockedSubscribeAgentStream.mockImplementation(
			(_storyId, _agentName, onEvent) => {
				emitEvent = onEvent;
				return () => {};
			},
		);

		const agentList: AgentInfo[] = [
			{
				story_id: "42_story_test",
				agent_name: "coder-1",
				status: "running",
				session_id: null,
				worktree_path: "/tmp/wt",
				base_branch: "master",
				log_session_id: null,
			},
		];
		mockedListAgents.mockResolvedValue(agentList);

		render(<WorkItemDetailPanel storyId="42_story_test" onClose={() => {}} />);

		await screen.findByTestId("agent-status-badge");

		await act(async () => {
			emitEvent?.({
				type: "output",
				story_id: "42_story_test",
				agent_name: "coder-1",
				text: "Writing tests...",
			});
		});

		const logOutput = screen.getByTestId("agent-log-output");
		expect(logOutput).toHaveTextContent("Writing tests...");
	});

	it("appends multiple output events to the log", async () => {
		let emitEvent: ((e: AgentEvent) => void) | null = null;
		mockedSubscribeAgentStream.mockImplementation(
			(_storyId, _agentName, onEvent) => {
				emitEvent = onEvent;
				return () => {};
			},
		);

		const agentList: AgentInfo[] = [
			{
				story_id: "42_story_test",
				agent_name: "coder-1",
				status: "running",
				session_id: null,
				worktree_path: "/tmp/wt",
				base_branch: "master",
				log_session_id: null,
			},
		];
		mockedListAgents.mockResolvedValue(agentList);

		render(<WorkItemDetailPanel storyId="42_story_test" onClose={() => {}} />);

		await screen.findByTestId("agent-status-badge");

		await act(async () => {
			emitEvent?.({
				type: "output",
				story_id: "42_story_test",
				agent_name: "coder-1",
				text: "Line one\n",
			});
		});

		await act(async () => {
			emitEvent?.({
				type: "output",
				story_id: "42_story_test",
				agent_name: "coder-1",
				text: "Line two\n",
			});
		});

		const logOutput = screen.getByTestId("agent-log-output");
		expect(logOutput.textContent).toContain("Line one");
		expect(logOutput.textContent).toContain("Line two");
	});

	it("updates status to completed after done event", async () => {
		let emitEvent: ((e: AgentEvent) => void) | null = null;
		mockedSubscribeAgentStream.mockImplementation(
			(_storyId, _agentName, onEvent) => {
				emitEvent = onEvent;
				return () => {};
			},
		);

		const agentList: AgentInfo[] = [
			{
				story_id: "42_story_test",
				agent_name: "coder-1",
				status: "running",
				session_id: null,
				worktree_path: "/tmp/wt",
				base_branch: "master",
				log_session_id: null,
			},
		];
		mockedListAgents.mockResolvedValue(agentList);

		render(<WorkItemDetailPanel storyId="42_story_test" onClose={() => {}} />);

		await screen.findByTestId("agent-status-badge");

		await act(async () => {
			emitEvent?.({
				type: "done",
				story_id: "42_story_test",
				agent_name: "coder-1",
				session_id: "session-123",
			});
		});

		const statusBadge = screen.getByTestId("agent-status-badge");
		expect(statusBadge).toHaveTextContent("completed");
	});

	it("shows failed status after error event", async () => {
		let emitEvent: ((e: AgentEvent) => void) | null = null;
		mockedSubscribeAgentStream.mockImplementation(
			(_storyId, _agentName, onEvent) => {
				emitEvent = onEvent;
				return () => {};
			},
		);

		const agentList: AgentInfo[] = [
			{
				story_id: "42_story_test",
				agent_name: "coder-1",
				status: "running",
				session_id: null,
				worktree_path: "/tmp/wt",
				base_branch: "master",
				log_session_id: null,
			},
		];
		mockedListAgents.mockResolvedValue(agentList);

		render(<WorkItemDetailPanel storyId="42_story_test" onClose={() => {}} />);

		await screen.findByTestId("agent-status-badge");

		await act(async () => {
			emitEvent?.({
				type: "error",
				story_id: "42_story_test",
				agent_name: "coder-1",
				message: "Process failed",
			});
		});

		const statusBadge = screen.getByTestId("agent-status-badge");
		expect(statusBadge).toHaveTextContent("failed");

		const logOutput = screen.getByTestId("agent-log-output");
		expect(logOutput.textContent).toContain("[ERROR] Process failed");
	});

	it("shows completed agent status without subscribing to stream", async () => {
		const agentList: AgentInfo[] = [
			{
				story_id: "42_story_test",
				agent_name: "coder-1",
				status: "completed",
				session_id: "session-123",
				worktree_path: "/tmp/wt",
				base_branch: "master",
				log_session_id: null,
			},
		];
		mockedListAgents.mockResolvedValue(agentList);

		render(<WorkItemDetailPanel storyId="42_story_test" onClose={() => {}} />);

		const statusBadge = await screen.findByTestId("agent-status-badge");
		expect(statusBadge).toHaveTextContent("completed");
		expect(mockedSubscribeAgentStream).not.toHaveBeenCalled();
	});

	it("shows failed agent status for a failed agent without subscribing to stream", async () => {
		const agentList: AgentInfo[] = [
			{
				story_id: "42_story_test",
				agent_name: "coder-1",
				status: "failed",
				session_id: null,
				worktree_path: null,
				base_branch: "master",
				log_session_id: null,
			},
		];
		mockedListAgents.mockResolvedValue(agentList);

		render(<WorkItemDetailPanel storyId="42_story_test" onClose={() => {}} />);

		const statusBadge = await screen.findByTestId("agent-status-badge");
		expect(statusBadge).toHaveTextContent("failed");
		expect(mockedSubscribeAgentStream).not.toHaveBeenCalled();
	});

	it("shows agent logs section (not placeholder) when agent is assigned", async () => {
		const agentList: AgentInfo[] = [
			{
				story_id: "42_story_test",
				agent_name: "coder-1",
				status: "running",
				session_id: null,
				worktree_path: "/tmp/wt",
				base_branch: "master",
				log_session_id: null,
			},
		];
		mockedListAgents.mockResolvedValue(agentList);

		render(<WorkItemDetailPanel storyId="42_story_test" onClose={() => {}} />);

		await screen.findByTestId("agent-logs-section");

		expect(
			screen.queryByTestId("placeholder-agent-logs"),
		).not.toBeInTheDocument();
	});
});
