import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Media } from "../Media";
import type {
	MediaIndexStatus,
	MediaItem,
	MediaQueryResponse,
	MonitoringMachine,
} from "../../types";

// Shared navigate mock so the row-click test can assert the call. The vi.mock
// factory is hoisted above this const, but it only closes over `navigate`
// lazily (the arrow runs at render time, well after init) — no TDZ access.
const navigate = vi.fn();

function machineFixture(
	overrides: Partial<MonitoringMachine> = {},
): MonitoringMachine {
	return {
		id: "local",
		name: "Local",
		mode: "local",
		enabled: true,
		services: ["jellyfin", "monitoring"],
		host: "",
		port: 22,
		username: "",
		key_directory: "",
		key_name: "",
		ssh_key_id: "",
		ssh_private_key_set: false,
		ssh_private_key_passphrase_set: false,
		password_set: false,
		media_root: "",
		path_prefix: "",
		jellyfin_url: "",
		jellyfin_user_id: "",
		jellyfin_api_key_set: false,
		jellyseerr_url: "",
		jellyseerr_api_key_set: false,
		notes: "",
		...overrides,
	};
}

function statusFixture(
	overrides: Partial<MediaIndexStatus> = {},
): MediaIndexStatus {
	return {
		exists: true,
		item_count: 2,
		updated_at: 1,
		updated_at_label: "now",
		build_duration_seconds: null,
		build_running: false,
		build_stage: "",
		build_message: "",
		build_progress: null,
		build_items_processed: 0,
		build_items_total: 0,
		build_current_library: "",
		build_library_index: 0,
		build_libraries_total: 0,
		build_library_progress: null,
		build_library_items_processed: 0,
		build_library_items_total: 0,
		build_elapsed_seconds: null,
		build_eta_seconds: null,
		build_library_elapsed_seconds: null,
		build_library_eta_seconds: null,
		build_cancel_requested: false,
		build_pid: null,
		build_error: "",
		...overrides,
	};
}

function mediaItem(overrides: Partial<MediaItem> = {}): MediaItem {
	return {
		id: "1",
		title: "Inception",
		series: "",
		season: "",
		episode: null,
		type: "Movie",
		year: 2010,
		runtime_min: 148,
		size: "12.4 GB",
		bitrate: "35.0 Mbps",
		hdr: "HDR10",
		video: "HEVC",
		resolution: "4K",
		date_added: "2024-01-01",
		library: "Movies",
		path: "/media/movies/Inception.mkv",
		...overrides,
	};
}

let status: MediaIndexStatus;
let queryResult: MediaQueryResponse;

vi.mock("react-router-dom", () => ({
	useNavigate: () => navigate,
	useSearchParams: () => [new URLSearchParams("jellyfin_service_id=jfs1"), vi.fn()],
}));

vi.mock("../../hooks/useMedia", () => ({
	useMediaStatus: () => ({ data: status }),
	useMediaQuery: () => ({ data: queryResult, isLoading: false }),
	useBuildIndex: () => ({ isPending: false, mutate: vi.fn() }),
	useStopBuildIndex: () => ({ isPending: false, mutate: vi.fn() }),
	useForceStopBuildIndex: () => ({ isPending: false, mutate: vi.fn() }),
}));

vi.mock("../../hooks/useSettings", () => ({
	useMonitoringSettings: () => ({ data: [machineFixture()] }),
}));

vi.mock("../../hooks/useServices", () => ({
	useServiceInstances: () => ({
		data: [{ id: "jfs1", service_type: "jellyfin", name: "Main", enabled: true }],
	}),
}));

vi.mock("../../hooks/useDashboard", () => ({
	useCounts: () => ({ data: undefined }),
	useLibraries: () => ({ data: undefined }),
}));

// usePersistentState reads/writes localStorage; clear between tests so the
// offset/pageSize/columnVisibility state never leaks across cases.
beforeEach(() => {
	window.localStorage.clear();
	navigate.mockClear();
	status = statusFixture();
	queryResult = {
		items: [
			mediaItem({
				id: "1",
				title: "Inception",
				path: "/media/movies/Inception.mkv",
			}),
			mediaItem({
				id: "2",
				title: "Matrix",
				path: "/media/movies/Matrix.mkv",
			}),
		],
		total: 2,
		limit: 100,
		offset: 0,
	};
});

describe("Media (slice 7b — TanStack DataTable + server-driven pagination)", () => {
	it("exposes exactly the 15 locked toggleable columns", async () => {
		render(<Media />);

		await userEvent.click(screen.getByRole("button", { name: /Columns/ }));

		const toggleable = screen
			.getAllByRole("menuitemcheckbox")
			.map((item) => (item.textContent ?? "").trim());
		expect([...toggleable].sort()).toEqual(
			[
				"title",
				"series",
				"season",
				"episode",
				"type",
				"year",
				"runtime_min",
				"size",
				"bitrate",
				"hdr",
				"video",
				"resolution",
				"date_added",
				"library",
				"path",
			].sort(),
		);
		// The leading selection column is never toggleable (enableHiding=false).
		expect(toggleable).toHaveLength(15);
		expect(toggleable).not.toContain("__select__");
	});

	it("renders the 15 data column headers", () => {
		render(<Media />);
		const headers = screen
			.getAllByRole("columnheader")
			.map((h) => (h.textContent ?? "").trim());
		for (const expected of [
			"Title",
			"Series",
			"Season",
			"Episode",
			"Type",
			"Year",
			"Runtime",
			"Size",
			"Bitrate",
			"HDR",
			"Video codec",
			"Resolution",
			"Date added",
			"Library",
			"Path",
		]) {
			expect(headers).toContain(expected);
		}
	});

	it("navigates to the file browser at the item path on row click", async () => {
		render(<Media />);

		await userEvent.click(screen.getByText("Inception"));

		expect(navigate).toHaveBeenCalledTimes(1);
		expect(navigate).toHaveBeenCalledWith(
			`/files?path=${encodeURIComponent("/media/movies/Inception.mkv")}`,
		);
	});

	it("does NOT navigate when toggling a row selection checkbox", async () => {
		render(<Media />);

		const firstCheckbox = screen.getAllByRole("checkbox", {
			name: "Select row",
		})[0];
		await userEvent.click(firstCheckbox);
		expect(firstCheckbox).toBeChecked();
		expect(navigate).not.toHaveBeenCalled();
	});

	it("renders the server-driven pagination total + page controls", () => {
		render(<Media />);

		// DataTable manual-pagination footer surfaces the server total + pager.
		// ("Page 1 of 1" also appears in the page caption, so match all and assert
		// the pager footer text is present alongside the unique total.)
		expect(screen.getByText("2 rows")).toBeInTheDocument();
		expect(screen.getAllByText(/Page 1 of 1/).length).toBeGreaterThan(0);
		expect(
			screen.getByRole("button", { name: "Previous page" }),
		).toBeDisabled();
	});

	it("disables Build index while a build is running", () => {
		status = statusFixture({ build_running: true });

		render(<Media />);

		expect(screen.getByRole("button", { name: "Building..." })).toBeDisabled();
		// Stop + Force stop surface only while running.
		expect(
			screen.getByRole("button", { name: "Stop build" }),
		).toBeInTheDocument();
		expect(
			screen.getByRole("button", { name: "Force stop" }),
		).toBeInTheDocument();
	});
});
