import { render, screen } from "@testing-library/react";
import type { QueryRow } from "@/lib/types";
import type { ClientNames } from "@/features/clients/clientNames";
import { queryRow } from "@/features/provenance/provenanceFixture";
import { summarizeRow, type QuerySummary } from "@/features/provenance/querySummary";
import { ACTIVITY_COLUMNS, ActivityCells, ActivityTableHead, resultLabel, routeLabel } from "./cells";

const noNames: ClientNames = new Map();

function renderRow(overrides: Partial<QueryRow> = {}): HTMLTableRowElement {
	const row: QuerySummary = summarizeRow(queryRow(1, overrides));
	render(
		<table>
			<ActivityTableHead />
			<tbody>
				<tr data-testid="row">
					<ActivityCells
						row={row}
						clientNames={noNames}
						renderDomain={(id, children) => <a href={`/activity/queries/${id}`}>{children}</a>}
					/>
				</tr>
			</tbody>
		</table>,
	);
	return screen.getByTestId("row") as HTMLTableRowElement;
}

/** The cell under a header, read by its column name rather than its index. */
function cell(row: HTMLTableRowElement, column: (typeof ACTIVITY_COLUMNS)[number]): string {
	const index = ACTIVITY_COLUMNS.indexOf(column);
	return row.cells[index]?.textContent ?? "";
}

test("the head names the seven columns in order", () => {
	render(
		<table>
			<ActivityTableHead />
		</table>,
	);
	const headers = screen.getAllByRole("columnheader").map((header) => header.textContent);
	expect(headers).toEqual(["Time", "Domain", "Client", "Type", "Result", "Route", "Duration"]);
});

test("an allowed NOERROR row reads as the answer it got, with no badge", () => {
	const row = renderRow({ blocked: false, rcode: 0, route_kind: "upstream", response_time_us: 1234 });
	expect(cell(row, "Result")).toBe("NOERROR");
	expect(cell(row, "Route")).toBe("Upstream");
	expect(cell(row, "Duration")).toBe("1.2 ms");
	expect(cell(row, "Type")).toBe("A");
});

test("a blocked row reads Blocked even though the client got NOERROR", () => {
	const row = renderRow({ blocked: true, rcode: 0, route_kind: "blocked", policy_reason: "blocklist_domain" });
	expect(cell(row, "Result")).toBe("Blocked");
	expect(cell(row, "Route")).toBe("Blocked");
});

test("a SERVFAIL row names the code", () => {
	const row = renderRow({ blocked: false, rcode: 2, route_kind: "upstream", response_time_us: null });
	expect(cell(row, "Result")).toBe("SERVFAIL");
	expect(cell(row, "Duration")).toBe("—");
});

test("a cache hit names the cache as the route", () => {
	const row = renderRow({ blocked: false, rcode: 0, route_kind: "cache", cache_hit: true, upstream: "" });
	expect(cell(row, "Route")).toBe("Cache");
	expect(cell(row, "Result")).toBe("NOERROR");
});

test("an unassigned extended rcode keeps the numeric fallback, without the long form's parentheses", () => {
	const row = renderRow({ blocked: false, rcode: 3841 });
	expect(cell(row, "Result")).toBe("RCODE 3841");
});

test("a persisted row links its domain to the detail the caller chose", () => {
	renderRow({ domain: "ads.example" });
	expect(screen.getByRole("link", { name: "ads.example" }).getAttribute("href")).toBe("/activity/queries/1");
});

test("a streamed row reaches the renderer with a null id, and can render as plain text", () => {
	render(
		<table>
			<tbody>
				<tr data-testid="row">
					<ActivityCells
						row={{ ...summarizeRow(queryRow(1)), id: null }}
						clientNames={noNames}
						renderDomain={(id, children) => {
							expect(id).toBeNull();
							return children;
						}}
					/>
				</tr>
			</tbody>
		</table>,
	);
	expect(screen.queryByRole("link")).toBeNull();
	expect(screen.getByTestId("row").textContent).toContain("example.com");
});

test("every route kind has a compact label", () => {
	expect(routeLabel("blocked")).toBe("Blocked");
	expect(routeLabel("local")).toBe("Local");
	expect(routeLabel("forward_zone")).toBe("Forward zone");
	expect(routeLabel("upstream")).toBe("Upstream");
	expect(routeLabel("cache")).toBe("Cache");
	expect(routeLabel("rejected")).toBe("Rejected");
});

test("resultLabel is the pure form of the Result cell", () => {
	expect(resultLabel({ blocked: true, rcode: 2 })).toBe("Blocked");
	expect(resultLabel({ blocked: false, rcode: 5 })).toBe("REFUSED");
});
