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

vi.mock("../api/client", async () => {
	const actual =
		await vi.importActual<typeof import("../api/client")>("../api/client");
	return {
		...actual,
		api: {
			...actual.api,
			getWorkItemContent: vi.fn(),
			getTestResults: vi.fn(),
			getTokenCost: vi.fn(),
		},
	};
});

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

import { agentsApi, subscribeAgentStream } from "../api/agents";
import { api } from "../api/client";

const { WorkItemDetailPanel } = await import("./WorkItemDetailPanel");

const mockedGetWorkItemContent = vi.mocked(api.getWorkItemContent);
const mockedGetTestResults = vi.mocked(api.getTestResults);
const mockedGetTokenCost = vi.mocked(api.getTokenCost);
const mockedListAgents = vi.mocked(agentsApi.listAgents);
const mockedGetAgentConfig = vi.mocked(agentsApi.getAgentConfig);
const mockedSubscribeAgentStream = vi.mocked(subscribeAgentStream);

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

const sampleTestResults: TestResultsResponse = {
	unit: [
		{ name: "test_add", status: "pass", details: null },
		{ name: "test_subtract", status: "fail", details: "expected 3, got 4" },
	],
	integration: [{ name: "test_api_endpoint", status: "pass", details: null }],
};

beforeEach(() => {
	vi.clearAllMocks();
	mockedGetWorkItemContent.mockResolvedValue(DEFAULT_CONTENT);
	mockedGetTestResults.mockResolvedValue(null);
	mockedGetTokenCost.mockResolvedValue({ total_cost_usd: 0, agents: [] });
	mockedListAgents.mockResolvedValue([]);
	mockedGetAgentConfig.mockResolvedValue([]);
	mockedSubscribeAgentStream.mockReturnValue(() => {});
});

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

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

	it("shows loading state initially", () => {
		render(
			<WorkItemDetailPanel
				storyId="237_bug_test"
				pipelineVersion={0}
				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"
				pipelineVersion={0}
				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"
				pipelineVersion={0}
				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", () => {
	it("shows placeholder when no agent is assigned to the story", async () => {
		render(
			<WorkItemDetailPanel
				storyId="42_story_test"
				pipelineVersion={0}
				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"
				pipelineVersion={0}
				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"
				pipelineVersion={0}
				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"
				pipelineVersion={0}
				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"
				pipelineVersion={0}
				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"
				pipelineVersion={0}
				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"
				pipelineVersion={0}
				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"
				pipelineVersion={0}
				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"
				pipelineVersion={0}
				onClose={() => {}}
			/>,
		);

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

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

describe("WorkItemDetailPanel - Assigned Agent", () => {
	it("shows assigned agent name when agent front matter field is set", async () => {
		mockedGetWorkItemContent.mockResolvedValue({
			...DEFAULT_CONTENT,
			agent: "coder-opus",
		});

		render(
			<WorkItemDetailPanel
				storyId="271_story_test"
				pipelineVersion={0}
				onClose={() => {}}
			/>,
		);

		const agentEl = await screen.findByTestId("detail-panel-assigned-agent");
		expect(agentEl).toHaveTextContent("coder-opus");
	});

	it("omits assigned agent field when no agent is set in front matter", async () => {
		render(
			<WorkItemDetailPanel
				storyId="271_story_test"
				pipelineVersion={0}
				onClose={() => {}}
			/>,
		);

		await screen.findByTestId("detail-panel-content");
		expect(
			screen.queryByTestId("detail-panel-assigned-agent"),
		).not.toBeInTheDocument();
	});

	it("shows the specific agent name not just 'assigned'", async () => {
		mockedGetWorkItemContent.mockResolvedValue({
			...DEFAULT_CONTENT,
			agent: "coder-haiku",
		});

		render(
			<WorkItemDetailPanel
				storyId="271_story_test"
				pipelineVersion={0}
				onClose={() => {}}
			/>,
		);

		const agentEl = await screen.findByTestId("detail-panel-assigned-agent");
		expect(agentEl).toHaveTextContent("coder-haiku");
		expect(agentEl).not.toHaveTextContent("assigned");
	});
});

describe("WorkItemDetailPanel - Test Results", () => {
	it("shows empty test results message when no results exist", async () => {
		mockedGetTestResults.mockResolvedValue(null);

		render(
			<WorkItemDetailPanel
				storyId="42_story_foo"
				pipelineVersion={0}
				onClose={() => {}}
			/>,
		);

		await waitFor(() => {
			expect(screen.getByTestId("test-results-empty")).toBeInTheDocument();
		});
		expect(screen.getByText("No test results recorded")).toBeInTheDocument();
	});

	it("shows unit and integration test results when available", async () => {
		mockedGetTestResults.mockResolvedValue(sampleTestResults);

		render(
			<WorkItemDetailPanel
				storyId="42_story_foo"
				pipelineVersion={0}
				onClose={() => {}}
			/>,
		);

		await waitFor(() => {
			expect(screen.getByTestId("test-results-content")).toBeInTheDocument();
		});

		// Unit test section
		expect(screen.getByTestId("test-section-unit")).toBeInTheDocument();
		expect(
			screen.getByText("Unit Tests (1 passed, 1 failed)"),
		).toBeInTheDocument();

		// Integration test section
		expect(screen.getByTestId("test-section-integration")).toBeInTheDocument();
		expect(
			screen.getByText("Integration Tests (1 passed, 0 failed)"),
		).toBeInTheDocument();
	});

	it("shows pass/fail status and details for each test", async () => {
		mockedGetTestResults.mockResolvedValue(sampleTestResults);

		render(
			<WorkItemDetailPanel
				storyId="42_story_foo"
				pipelineVersion={0}
				onClose={() => {}}
			/>,
		);

		await waitFor(() => {
			expect(screen.getByTestId("test-case-test_add")).toBeInTheDocument();
		});

		// Passing test
		expect(screen.getByTestId("test-status-test_add")).toHaveTextContent(
			"PASS",
		);
		expect(screen.getByText("test_add")).toBeInTheDocument();

		// Failing test with details
		expect(screen.getByTestId("test-status-test_subtract")).toHaveTextContent(
			"FAIL",
		);
		expect(screen.getByText("test_subtract")).toBeInTheDocument();
		expect(screen.getByTestId("test-details-test_subtract")).toHaveTextContent(
			"expected 3, got 4",
		);

		// Integration test
		expect(
			screen.getByTestId("test-status-test_api_endpoint"),
		).toHaveTextContent("PASS");
	});

	it("re-fetches test results when pipelineVersion changes", async () => {
		mockedGetTestResults.mockResolvedValue(null);

		const { rerender } = render(
			<WorkItemDetailPanel
				storyId="42_story_foo"
				pipelineVersion={0}
				onClose={() => {}}
			/>,
		);

		await waitFor(() => {
			expect(mockedGetTestResults).toHaveBeenCalledTimes(1);
		});

		// Update with new results and bump pipelineVersion.
		mockedGetTestResults.mockResolvedValue(sampleTestResults);

		rerender(
			<WorkItemDetailPanel
				storyId="42_story_foo"
				pipelineVersion={1}
				onClose={() => {}}
			/>,
		);

		await waitFor(() => {
			expect(mockedGetTestResults).toHaveBeenCalledTimes(2);
		});

		await waitFor(() => {
			expect(screen.getByTestId("test-results-content")).toBeInTheDocument();
		});
	});
});

describe("WorkItemDetailPanel - Token Cost", () => {
	const sampleTokenCost: TokenCostResponse = {
		total_cost_usd: 0.012345,
		agents: [
			{
				agent_name: "coder-1",
				model: "claude-sonnet-4-6",
				input_tokens: 1000,
				output_tokens: 500,
				cache_creation_input_tokens: 200,
				cache_read_input_tokens: 100,
				total_cost_usd: 0.009,
			},
			{
				agent_name: "coder-2",
				model: null,
				input_tokens: 800,
				output_tokens: 300,
				cache_creation_input_tokens: 0,
				cache_read_input_tokens: 0,
				total_cost_usd: 0.003345,
			},
		],
	};

	it("shows empty state when no token data exists", async () => {
		mockedGetTokenCost.mockResolvedValue({ total_cost_usd: 0, agents: [] });

		render(
			<WorkItemDetailPanel
				storyId="42_story_foo"
				pipelineVersion={0}
				onClose={() => {}}
			/>,
		);

		await waitFor(() => {
			expect(screen.getByTestId("token-cost-empty")).toBeInTheDocument();
		});
		expect(screen.getByText("No token data recorded")).toBeInTheDocument();
	});

	it("shows per-agent breakdown and total cost when data exists", async () => {
		mockedGetTokenCost.mockResolvedValue(sampleTokenCost);

		render(
			<WorkItemDetailPanel
				storyId="42_story_foo"
				pipelineVersion={0}
				onClose={() => {}}
			/>,
		);

		await waitFor(() => {
			expect(screen.getByTestId("token-cost-content")).toBeInTheDocument();
		});

		expect(screen.getByTestId("token-cost-total")).toHaveTextContent(
			"$0.012345",
		);
		expect(screen.getByTestId("token-cost-agent-coder-1")).toBeInTheDocument();
		expect(screen.getByTestId("token-cost-agent-coder-2")).toBeInTheDocument();
	});

	it("shows agent name and model when model is present", async () => {
		mockedGetTokenCost.mockResolvedValue(sampleTokenCost);

		render(
			<WorkItemDetailPanel
				storyId="42_story_foo"
				pipelineVersion={0}
				onClose={() => {}}
			/>,
		);

		await waitFor(() => {
			expect(
				screen.getByTestId("token-cost-agent-coder-1"),
			).toBeInTheDocument();
		});

		const agentRow = screen.getByTestId("token-cost-agent-coder-1");
		expect(agentRow).toHaveTextContent("coder-1");
		expect(agentRow).toHaveTextContent("claude-sonnet-4-6");
	});

	it("shows agent name without model when model is null", async () => {
		mockedGetTokenCost.mockResolvedValue(sampleTokenCost);

		render(
			<WorkItemDetailPanel
				storyId="42_story_foo"
				pipelineVersion={0}
				onClose={() => {}}
			/>,
		);

		await waitFor(() => {
			expect(
				screen.getByTestId("token-cost-agent-coder-2"),
			).toBeInTheDocument();
		});

		const agentRow = screen.getByTestId("token-cost-agent-coder-2");
		expect(agentRow).toHaveTextContent("coder-2");
		expect(agentRow).not.toHaveTextContent("null");
	});

	it("re-fetches token cost when pipelineVersion changes", async () => {
		mockedGetTokenCost.mockResolvedValue({ total_cost_usd: 0, agents: [] });

		const { rerender } = render(
			<WorkItemDetailPanel
				storyId="42_story_foo"
				pipelineVersion={0}
				onClose={() => {}}
			/>,
		);

		await waitFor(() => {
			expect(mockedGetTokenCost).toHaveBeenCalledTimes(1);
		});

		mockedGetTokenCost.mockResolvedValue(sampleTokenCost);

		rerender(
			<WorkItemDetailPanel
				storyId="42_story_foo"
				pipelineVersion={1}
				onClose={() => {}}
			/>,
		);

		await waitFor(() => {
			expect(mockedGetTokenCost).toHaveBeenCalledTimes(2);
		});

		await waitFor(() => {
			expect(screen.getByTestId("token-cost-content")).toBeInTheDocument();
		});
	});
});
