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

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,
	origin: 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 with type and ID prefix", async () => {
		render(
			<WorkItemDetailPanel
				storyId="237_bug_test"
				pipelineVersion={0}
				onClose={() => {}}
			/>,
		);
		await waitFor(() => {
			expect(screen.getByTestId("detail-panel-title")).toHaveTextContent(
				"Bug 237: 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 () => {
		mockedGetWorkItemContent.mockResolvedValue({
			...DEFAULT_CONTENT,
			content: "# Title Heading\n\n## Section Heading\n\nSome content.",
		});
		render(
			<WorkItemDetailPanel
				storyId="237_bug_test"
				pipelineVersion={0}
				onClose={() => {}}
			/>,
		);
		await waitFor(() => {
			const content = screen.getByTestId("detail-panel-content");
			// H1 is stripped by stripDisplayContent; h2 should be constrained
			const h2 = content.querySelector("h2");
			expect(h2).not.toBeNull();
			expect(h2?.style.fontSize).toBeTruthy();
		});
	});

	it("strips YAML front matter so 'name' is not shown as a prefix in content", async () => {
		mockedGetWorkItemContent.mockResolvedValue({
			content:
				'---\nname: "My Story Name"\n---\n\n# Story 42: My Story Name\n\n## User Story\n\nAs a user...',
			stage: "current",
			name: "My Story Name",
			agent: null,
			origin: null,
		});
		render(
			<WorkItemDetailPanel
				storyId="42_story_test"
				pipelineVersion={0}
				onClose={() => {}}
			/>,
		);
		const content = await screen.findByTestId("detail-panel-content");
		expect(content.textContent).not.toMatch(/name:/i);
	});

	it("strips the first H1 heading so the story title is not shown twice", async () => {
		mockedGetWorkItemContent.mockResolvedValue({
			content:
				'---\nname: "My Story Name"\n---\n\n# Story 42: My Story Name\n\n## User Story\n\nAs a user...',
			stage: "current",
			name: "My Story Name",
			agent: null,
			origin: null,
		});
		render(
			<WorkItemDetailPanel
				storyId="42_story_test"
				pipelineVersion={0}
				onClose={() => {}}
			/>,
		);
		const content = await screen.findByTestId("detail-panel-content");
		expect(content.querySelector("h1")).toBeNull();
	});

	it("shows 'Type N: Name' format in the panel header title (story ID/title left-justified)", async () => {
		mockedGetWorkItemContent.mockResolvedValue({
			content: "## User Story\n\nAs a user...",
			stage: "current",
			name: "My Story Name",
			agent: null,
			origin: null,
		});
		render(
			<WorkItemDetailPanel
				storyId="42_story_test"
				pipelineVersion={0}
				onClose={() => {}}
			/>,
		);
		await waitFor(() => {
			expect(screen.getByTestId("detail-panel-title")).toHaveTextContent(
				"Story 42: My Story Name",
			);
		});
	});

	it("does not show the work item type label twice when front matter and H1 are stripped", async () => {
		mockedGetWorkItemContent.mockResolvedValue({
			content:
				'---\nname: "My Story Name"\n---\n\n# Story 42: My Story Name\n\n## User Story\n\nContent.',
			stage: "current",
			name: "My Story Name",
			agent: null,
			origin: null,
		});
		render(
			<WorkItemDetailPanel
				storyId="42_story_test"
				pipelineVersion={0}
				onClose={() => {}}
			/>,
		);
		await screen.findByTestId("detail-panel-content");
		// "Story" type label appears exactly once — in the panel header title
		const title = screen.getByTestId("detail-panel-title");
		expect(title.textContent).toContain("Story 42:");
		// The content body should not contain an H1 repeating the type + title
		const content = screen.getByTestId("detail-panel-content");
		expect(content.querySelector("h1")).toBeNull();
	});
});

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");
	});
});
