import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import type { WsConnectivity } from "../hooks/useChatWebSocket";
import { ChatHeader } from "./ChatHeader";

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

interface ChatHeaderProps {
	projectPath: string;
	onCloseProject: () => void;
	contextUsage: { used: number; total: number; percentage: number };
	onClearSession: () => void;
	model: string;
	availableModels: string[];
	claudeModels: string[];
	hasAnthropicKey: boolean;
	onModelChange: (model: string) => void;
	enableTools: boolean;
	onToggleTools: (enabled: boolean) => void;
	wsConnected: boolean;
	wsConnectivity?: WsConnectivity;
	wsDisconnectedAt?: Date | null;
}

function makeProps(overrides: Partial<ChatHeaderProps> = {}): ChatHeaderProps {
	return {
		projectPath: "/test/project",
		onCloseProject: vi.fn(),
		contextUsage: { used: 1000, total: 10000, percentage: 10 },
		onClearSession: vi.fn(),
		model: "claude-sonnet",
		availableModels: ["llama3"],
		claudeModels: ["claude-sonnet"],
		hasAnthropicKey: true,
		onModelChange: vi.fn(),
		enableTools: true,
		onToggleTools: vi.fn(),
		wsConnected: false,
		...overrides,
	};
}

describe("ChatHeader", () => {
	it("renders project path", () => {
		render(<ChatHeader {...makeProps()} />);
		expect(screen.getByText("/test/project")).toBeInTheDocument();
	});

	it("calls onCloseProject when close button is clicked", () => {
		const onCloseProject = vi.fn();
		render(<ChatHeader {...makeProps({ onCloseProject })} />);
		fireEvent.click(screen.getByText("\u2715"));
		expect(onCloseProject).toHaveBeenCalled();
	});

	it("displays context percentage with green emoji when low", () => {
		render(
			<ChatHeader
				{...makeProps({
					contextUsage: { used: 1000, total: 10000, percentage: 10 },
				})}
			/>,
		);
		expect(screen.getByText(/10%/)).toBeInTheDocument();
	});

	it("displays yellow emoji when context is 75-89%", () => {
		render(
			<ChatHeader
				{...makeProps({
					contextUsage: { used: 8000, total: 10000, percentage: 80 },
				})}
			/>,
		);
		expect(screen.getByText(/80%/)).toBeInTheDocument();
	});

	it("displays red emoji when context is 90%+", () => {
		render(
			<ChatHeader
				{...makeProps({
					contextUsage: { used: 9500, total: 10000, percentage: 95 },
				})}
			/>,
		);
		expect(screen.getByText(/95%/)).toBeInTheDocument();
	});

	it("calls onClearSession when New Session button is clicked", () => {
		const onClearSession = vi.fn();
		render(<ChatHeader {...makeProps({ onClearSession })} />);
		fireEvent.click(screen.getByText(/New Session/));
		expect(onClearSession).toHaveBeenCalled();
	});

	it("renders select dropdown when model options are available", () => {
		render(<ChatHeader {...makeProps()} />);
		const select = screen.getByRole("combobox");
		expect(select).toBeInTheDocument();
	});

	it("renders text input when no model options are available", () => {
		render(
			<ChatHeader {...makeProps({ availableModels: [], claudeModels: [] })} />,
		);
		expect(screen.getByPlaceholderText("Model")).toBeInTheDocument();
	});

	it("calls onModelChange when model is selected from dropdown", () => {
		const onModelChange = vi.fn();
		render(<ChatHeader {...makeProps({ onModelChange })} />);
		const select = screen.getByRole("combobox");
		fireEvent.change(select, { target: { value: "llama3" } });
		expect(onModelChange).toHaveBeenCalledWith("llama3");
	});

	it("calls onModelChange when text is typed in model input", () => {
		const onModelChange = vi.fn();
		render(
			<ChatHeader
				{...makeProps({
					availableModels: [],
					claudeModels: [],
					onModelChange,
				})}
			/>,
		);
		const input = screen.getByPlaceholderText("Model");
		fireEvent.change(input, { target: { value: "custom-model" } });
		expect(onModelChange).toHaveBeenCalledWith("custom-model");
	});

	it("calls onToggleTools when checkbox is toggled", () => {
		const onToggleTools = vi.fn();
		render(<ChatHeader {...makeProps({ onToggleTools })} />);
		const checkbox = screen.getByRole("checkbox");
		fireEvent.click(checkbox);
		expect(onToggleTools).toHaveBeenCalled();
	});

	it("displays the build timestamp in human-readable format", () => {
		render(<ChatHeader {...makeProps()} />);
		expect(screen.getByText("Built: 2026-01-01 00:00")).toBeInTheDocument();
	});

	it("displays Huskies branding in the header", () => {
		render(<ChatHeader {...makeProps()} />);
		expect(screen.getByText("Huskies")).toBeInTheDocument();
	});

	it("labels the claude-pty optgroup as 'Claude Code'", () => {
		render(<ChatHeader {...makeProps()} />);
		const optgroup = document.querySelector('optgroup[label="Claude Code"]');
		expect(optgroup).toBeInTheDocument();
	});

	it("labels the Anthropic API optgroup as 'Anthropic API'", () => {
		render(<ChatHeader {...makeProps()} />);
		const optgroup = document.querySelector('optgroup[label="Anthropic API"]');
		expect(optgroup).toBeInTheDocument();
	});

	it("shows disabled placeholder when claudeModels is empty and no API key", () => {
		render(
			<ChatHeader
				{...makeProps({
					claudeModels: [],
					hasAnthropicKey: false,
					availableModels: ["llama3"],
				})}
			/>,
		);
		expect(
			screen.getByText("Add Anthropic API key to load models"),
		).toBeInTheDocument();
	});

	// ── Close button hover/focus handlers ─────────────────────────────────────

	it("close button changes background on mouseOver and resets on mouseOut", () => {
		render(<ChatHeader {...makeProps()} />);
		const closeBtn = screen.getByText("\u2715");
		fireEvent.mouseOver(closeBtn);
		expect(closeBtn.style.background).toBe("rgb(51, 51, 51)");
		fireEvent.mouseOut(closeBtn);
		expect(closeBtn.style.background).toBe("transparent");
	});

	it("close button changes background on focus and resets on blur", () => {
		render(<ChatHeader {...makeProps()} />);
		const closeBtn = screen.getByText("\u2715");
		fireEvent.focus(closeBtn);
		expect(closeBtn.style.background).toBe("rgb(51, 51, 51)");
		fireEvent.blur(closeBtn);
		expect(closeBtn.style.background).toBe("transparent");
	});

	// ── New Session button hover/focus handlers ───────────────────────────────

	it("New Session button changes style on mouseOver and resets on mouseOut", () => {
		render(<ChatHeader {...makeProps()} />);
		const sessionBtn = screen.getByText(/New Session/);
		fireEvent.mouseOver(sessionBtn);
		expect(sessionBtn.style.backgroundColor).toBe("rgb(63, 63, 63)");
		expect(sessionBtn.style.color).toBe("rgb(204, 204, 204)");
		fireEvent.mouseOut(sessionBtn);
		expect(sessionBtn.style.backgroundColor).toBe("rgb(47, 47, 47)");
		expect(sessionBtn.style.color).toBe("rgb(136, 136, 136)");
	});

	it("New Session button changes style on focus and resets on blur", () => {
		render(<ChatHeader {...makeProps()} />);
		const sessionBtn = screen.getByText(/New Session/);
		fireEvent.focus(sessionBtn);
		expect(sessionBtn.style.backgroundColor).toBe("rgb(63, 63, 63)");
		expect(sessionBtn.style.color).toBe("rgb(204, 204, 204)");
		fireEvent.blur(sessionBtn);
		expect(sessionBtn.style.backgroundColor).toBe("rgb(47, 47, 47)");
		expect(sessionBtn.style.color).toBe("rgb(136, 136, 136)");
	});

	// ── Rebuild button ────────────────────────────────────────────────────────

	it("renders rebuild button", () => {
		render(<ChatHeader {...makeProps()} />);
		expect(
			screen.getByTitle("Rebuild and restart the server"),
		).toBeInTheDocument();
	});

	it("shows confirmation dialog when rebuild button is clicked", () => {
		render(<ChatHeader {...makeProps()} />);
		fireEvent.click(screen.getByTitle("Rebuild and restart the server"));
		expect(screen.getByText("Rebuild and restart?")).toBeInTheDocument();
	});

	it("hides confirmation dialog when cancel is clicked", () => {
		render(<ChatHeader {...makeProps()} />);
		fireEvent.click(screen.getByTitle("Rebuild and restart the server"));
		fireEvent.click(screen.getByText("Cancel"));
		expect(screen.queryByText("Rebuild and restart?")).not.toBeInTheDocument();
	});

	it("calls api.rebuildAndRestart and shows Building... when confirmed", async () => {
		const { api } = await import("../api/client");
		vi.mocked(api.rebuildAndRestart).mockReturnValue(new Promise(() => {}));

		render(<ChatHeader {...makeProps()} />);
		fireEvent.click(screen.getByTitle("Rebuild and restart the server"));
		fireEvent.click(screen.getByText("Rebuild"));

		await waitFor(() => {
			expect(screen.getByText("Building...")).toBeInTheDocument();
		});
		expect(api.rebuildAndRestart).toHaveBeenCalled();
	});

	it("shows Reconnecting... when rebuild triggers a network error", async () => {
		const { api } = await import("../api/client");
		vi.mocked(api.rebuildAndRestart).mockRejectedValue(
			new TypeError("Failed to fetch"),
		);

		render(<ChatHeader {...makeProps()} />);
		fireEvent.click(screen.getByTitle("Rebuild and restart the server"));
		fireEvent.click(screen.getByText("Rebuild"));

		await waitFor(() => {
			expect(screen.getByText("Reconnecting...")).toBeInTheDocument();
		});
	});

	it("shows error when rebuild returns a failure message", async () => {
		const { api } = await import("../api/client");
		vi.mocked(api.rebuildAndRestart).mockResolvedValue(
			"error[E0308]: mismatched types",
		);

		render(<ChatHeader {...makeProps()} />);
		fireEvent.click(screen.getByTitle("Rebuild and restart the server"));
		fireEvent.click(screen.getByText("Rebuild"));

		await waitFor(() => {
			expect(screen.getByText("⚠ Rebuild failed")).toBeInTheDocument();
			expect(
				screen.getByText("error[E0308]: mismatched types"),
			).toBeInTheDocument();
		});
	});

	// ── Connectivity indicator ────────────────────────────────────────────────

	it("does not render connectivity dot when wsConnectivity is not provided", () => {
		render(<ChatHeader {...makeProps()} />);
		expect(screen.queryByTestId("ws-connectivity-dot")).not.toBeInTheDocument();
	});

	it("renders green dot with title 'Connected' when connected", () => {
		render(<ChatHeader {...makeProps({ wsConnectivity: "connected" })} />);
		const dot = screen.getByTestId("ws-connectivity-dot");
		expect(dot).toBeInTheDocument();
		expect(dot).toHaveAttribute("title", "Connected");
		expect(dot.style.backgroundColor).toBe("rgb(76, 175, 80)");
	});

	it("renders amber dot with title 'Reconnecting…' when reconnecting", () => {
		render(<ChatHeader {...makeProps({ wsConnectivity: "reconnecting" })} />);
		const dot = screen.getByTestId("ws-connectivity-dot");
		expect(dot).toHaveAttribute("title", "Reconnecting…");
		expect(dot.style.backgroundColor).toBe("rgb(245, 166, 35)");
	});

	it("renders amber dot with title 'Connecting…' when connecting", () => {
		render(<ChatHeader {...makeProps({ wsConnectivity: "connecting" })} />);
		const dot = screen.getByTestId("ws-connectivity-dot");
		expect(dot).toHaveAttribute("title", "Connecting…");
		expect(dot.style.backgroundColor).toBe("rgb(245, 166, 35)");
	});

	it("renders red dot with title 'Disconnected' when failed with no timestamp", () => {
		render(<ChatHeader {...makeProps({ wsConnectivity: "failed" })} />);
		const dot = screen.getByTestId("ws-connectivity-dot");
		expect(dot).toHaveAttribute("title", "Disconnected");
		expect(dot.style.backgroundColor).toBe("rgb(229, 57, 53)");
	});

	it("renders red dot with 'Disconnected since HH:MM' when failed with timestamp", () => {
		const disconnectedAt = new Date("2026-05-14T14:30:00");
		render(
			<ChatHeader
				{...makeProps({ wsConnectivity: "failed", wsDisconnectedAt: disconnectedAt })}
			/>,
		);
		const dot = screen.getByTestId("ws-connectivity-dot");
		expect(dot.getAttribute("title")).toMatch(/Disconnected since/);
	});

	it("clears reconnecting state when wsConnected transitions to true", async () => {
		const { api } = await import("../api/client");
		vi.mocked(api.rebuildAndRestart).mockRejectedValue(
			new TypeError("Failed to fetch"),
		);

		const { rerender } = render(
			<ChatHeader {...makeProps({ wsConnected: false })} />,
		);
		fireEvent.click(screen.getByTitle("Rebuild and restart the server"));
		fireEvent.click(screen.getByText("Rebuild"));

		await waitFor(() => {
			expect(screen.getByText("Reconnecting...")).toBeInTheDocument();
		});

		rerender(<ChatHeader {...makeProps({ wsConnected: true })} />);

		await waitFor(() => {
			expect(screen.getByText("↺ Rebuild")).toBeInTheDocument();
		});
	});
});
