import { useState, type FormEvent } from "react";
import { useInfiniteQuery } from "@tanstack/react-query";
import * as stylex from "@stylexjs/stylex";
import * as api from "@/lib/api";
import { formatMicros, formatTime } from "@/lib/format";
import { queriesInfiniteQuery } from "@/lib/queries";
import type { QueriesFilter, QueryRow } from "@/lib/types";
import { ClientName, useClientNames, type ClientNames } from "@/features/clients/clientNames";
import { qtypeName } from "./qtype";
import Select from "@/ui/Select";
import { styles as shared } from "@/ui/styles";
import { colors } from "@/ui/tokens.stylex";

const DARK = "@media (prefers-color-scheme: dark)";

const STATUS_OPTIONS = [
	{ value: "any", label: "All" },
	{ value: "blocked", label: "Blocked only" },
	{ value: "allowed", label: "Allowed only" },
];

const styles = stylex.create({
	heading: {
		fontSize: "1.5rem",
		lineHeight: "2rem",
		fontWeight: 600,
	},
	/** One column on a phone, two from `sm`, five from `lg`, as before. */
	filterGrid: {
		marginTop: "1rem",
		display: "grid",
		gap: "0.75rem",
		gridTemplateColumns: {
			default: "repeat(1, minmax(0, 1fr))",
			"@media (min-width: 640px)": "repeat(2, minmax(0, 1fr))",
			"@media (min-width: 1024px)": "repeat(5, minmax(0, 1fr))",
		},
	},
	filterLabel: {
		display: "block",
		fontSize: "0.875rem",
		lineHeight: "1.25rem",
	},
	filterInput: {
		marginTop: "0.25rem",
		width: "100%",
	},
	buttonRow: {
		display: "flex",
		alignItems: "flex-end",
		gap: "0.5rem",
		gridColumn: {
			default: null,
			"@media (min-width: 640px)": "span 2 / span 2",
			"@media (min-width: 1024px)": "span 5 / span 5",
		},
	},
	toolbarButton: {
		fontWeight: 500,
	},
	note: {
		fontSize: "0.875rem",
		lineHeight: "1.25rem",
		color: colors.textMuted,
	},
	empty: {
		marginTop: "1.5rem",
		color: colors.textMuted,
	},
	tableWrap: {
		marginTop: "1rem",
		overflowX: "auto",
		borderRadius: "0.25rem",
		borderWidth: 1,
		borderStyle: "solid",
		borderColor: colors.border,
	},
	table: {
		width: "100%",
		fontSize: "0.875rem",
		lineHeight: "1.25rem",
	},
	/** The header tint is a shade off the ground in each scheme, not a token role. */
	head: {
		backgroundColor: { default: "oklch(98.5% 0 none)", [DARK]: "oklch(21% 0.006 285.885)" },
		textAlign: "left",
	},
	th: {
		paddingInline: "0.75rem",
		paddingBlock: "0.5rem",
		fontWeight: 500,
		color: colors.textSecondary,
	},
	/** `divide-y`: a hairline between rows, so the first row carries none. */
	row: {
		borderTopWidth: { default: 1, ":first-child": 0 },
		borderTopStyle: "solid",
		borderTopColor: colors.border,
	},
	cell: {
		paddingInline: "0.75rem",
		paddingBlock: "0.5rem",
	},
	nowrap: {
		whiteSpace: "nowrap",
	},
	breakAll: {
		wordBreak: "break-all",
	},
	small: {
		fontSize: "0.75rem",
		lineHeight: "1rem",
	},
	muted: {
		color: colors.textMuted,
	},
	blockedWrap: {
		display: "inline-flex",
		alignItems: "center",
		gap: "0.375rem",
	},
	blockedBadge: {
		borderRadius: "0.25rem",
		paddingInline: "0.375rem",
		paddingBlock: "0.125rem",
		fontSize: "0.75rem",
		lineHeight: "1rem",
		fontWeight: 500,
		backgroundColor: { default: "oklch(93.6% 0.032 17.717)", [DARK]: "oklch(39.6% 0.141 25.723)" },
		color: { default: "oklch(44.4% 0.177 26.899)", [DARK]: "oklch(88.5% 0.062 18.334)" },
	},
	footer: {
		marginTop: "0.75rem",
		display: "flex",
		alignItems: "center",
		gap: "0.75rem",
	},
	moreError: {
		marginTop: "0.5rem",
		fontSize: "0.875rem",
		lineHeight: "1.25rem",
		color: colors.dangerText,
	},
});

function errorMessage(error: unknown): string {
	return error instanceof Error ? error.message : String(error);
}

function datetimeLocalToUnix(value: string): number | undefined {
	if (value === "") return undefined;
	const ms = new Date(value).getTime();
	return Number.isFinite(ms) ? Math.floor(ms / 1000) : undefined;
}

export function BlockedCell({ row }: { row: Pick<QueryRow, "blocked" | "block_reason"> }) {
	if (!row.blocked) return <span {...stylex.props(styles.muted)}>—</span>;
	return (
		<span {...stylex.props(styles.blockedWrap)}>
			<span {...stylex.props(styles.blockedBadge)}>Blocked</span>
			{row.block_reason !== "" && <span {...stylex.props(styles.small, styles.muted)}>{row.block_reason}</span>}
		</span>
	);
}

export function QueryCells({ row, clientNames }: { row: Omit<QueryRow, "id">; clientNames: ClientNames }) {
	return (
		<>
			<td {...stylex.props(styles.cell, styles.nowrap, styles.muted)}>{formatTime(row.ts)}</td>
			<td {...stylex.props(styles.cell, styles.small, styles.breakAll, shared.mono)}>{row.domain}</td>
			<td {...stylex.props(styles.cell, styles.small, styles.nowrap)}>
				<ClientName ip={row.client_ip} names={clientNames} />
			</td>
			<td {...stylex.props(styles.cell, styles.nowrap)}>{qtypeName(row.qtype)}</td>
			<td {...stylex.props(styles.cell)}>
				<BlockedCell row={row} />
			</td>
			<td {...stylex.props(styles.cell, styles.nowrap, shared.tabularNums)}>
				{row.response_time_us === null ? "—" : formatMicros(row.response_time_us)}
			</td>
			<td {...stylex.props(styles.cell, styles.nowrap)}>
				{row.cache_hit === null ? "—" : row.cache_hit ? "hit" : "miss"}
			</td>
			<td {...stylex.props(styles.cell, styles.small, styles.breakAll, shared.mono)}>
				{row.upstream === "" ? "—" : row.upstream}
			</td>
		</>
	);
}

export function QueryTableHead() {
	return (
		<thead {...stylex.props(styles.head)}>
			<tr>
				<th {...stylex.props(styles.th)}>Time</th>
				<th {...stylex.props(styles.th)}>Domain</th>
				<th {...stylex.props(styles.th)}>Client</th>
				<th {...stylex.props(styles.th)}>Type</th>
				<th {...stylex.props(styles.th)}>Status</th>
				<th {...stylex.props(styles.th)}>Response</th>
				<th {...stylex.props(styles.th)}>Cache</th>
				<th {...stylex.props(styles.th)}>Upstream</th>
			</tr>
		</thead>
	);
}

export default function QueryLogPage() {
	const [domain, setDomain] = useState("");
	const [client, setClient] = useState("");
	const [blocked, setBlocked] = useState("any");
	const [since, setSince] = useState("");
	const [until, setUntil] = useState("");

	const [applied, setApplied] = useState<QueriesFilter>({});

	const base = useInfiniteQuery(queriesInfiniteQuery(applied));
	const clientNames = useClientNames();

	const pages = base.data?.pages ?? [];
	const rows: QueryRow[] = pages.flatMap((page) => page.queries);
	const filterActive = Object.keys(applied).length > 0;
	// `base.hasNextPage` reads the query state, which is empty while placeholder
	// data stands in for a filter change; derive the cursor from what is on
	// screen so the button keeps its place instead of flashing "end of log".
	const lastPage = pages[pages.length - 1];
	const hasMore = lastPage !== undefined && lastPage.next_before !== null;
	// A 401 is already redirecting via the cache-level handleUnauthorized.
	const isUnauthorized = base.error instanceof api.ApiError && base.error.status === 401;
	const moreError = base.isFetchNextPageError && !isUnauthorized ? errorMessage(base.error) : null;

	function applyFilters(event: FormEvent) {
		event.preventDefault();
		const filter: QueriesFilter = {};
		if (domain.trim() !== "") filter.domain = domain.trim();
		if (client.trim() !== "") filter.client = client.trim();
		if (blocked === "blocked") filter.blocked = true;
		if (blocked === "allowed") filter.blocked = false;
		const sinceTs = datetimeLocalToUnix(since);
		if (sinceTs !== undefined) filter.since = sinceTs;
		const untilTs = datetimeLocalToUnix(until);
		if (untilTs !== undefined) filter.until = untilTs;
		setApplied(filter);
	}

	function clearFilters() {
		setDomain("");
		setClient("");
		setBlocked("any");
		setSince("");
		setUntil("");
		setApplied({});
	}

	function loadMore() {
		if (!hasMore || base.isFetchingNextPage || base.isPlaceholderData) return;
		void base.fetchNextPage();
	}

	return (
		<section>
			<h1 {...stylex.props(styles.heading)}>Query Log</h1>

			<form onSubmit={applyFilters} {...stylex.props(styles.filterGrid)}>
				<label {...stylex.props(styles.filterLabel)}>
					Domain contains
					<input
						type="text"
						value={domain}
						onChange={(event) => setDomain(event.target.value)}
						{...stylex.props(shared.smallInput, styles.filterInput, shared.focusRing)}
					/>
				</label>
				<label {...stylex.props(styles.filterLabel)}>
					Client (exact)
					<input
						type="text"
						value={client}
						onChange={(event) => setClient(event.target.value)}
						{...stylex.props(shared.smallInput, styles.filterInput, shared.focusRing)}
					/>
				</label>
				<Select
					variant="compactField"
					label="Status"
					value={blocked}
					onChange={setBlocked}
					options={STATUS_OPTIONS}
				/>
				<label {...stylex.props(styles.filterLabel)}>
					Since
					<input
						type="datetime-local"
						value={since}
						onChange={(event) => setSince(event.target.value)}
						{...stylex.props(shared.smallInput, styles.filterInput, shared.focusRing)}
					/>
				</label>
				<label {...stylex.props(styles.filterLabel)}>
					Until
					<input
						type="datetime-local"
						value={until}
						onChange={(event) => setUntil(event.target.value)}
						{...stylex.props(shared.smallInput, styles.filterInput, shared.focusRing)}
					/>
				</label>
				<div {...stylex.props(styles.buttonRow)}>
					<button type="submit" {...stylex.props(shared.button, styles.toolbarButton, shared.focusRing)}>
						Apply filters
					</button>
					<button
						type="button"
						onClick={clearFilters}
						{...stylex.props(shared.button, styles.toolbarButton, shared.focusRing)}
					>
						Clear
					</button>
					{base.isFetching && (
						<span {...stylex.props(styles.note)} role="status">
							Loading…
						</span>
					)}
				</div>
			</form>

			{base.data === undefined ? (
				<p {...stylex.props(styles.empty, shared.pulse)} role="status">
					Loading query log…
				</p>
			) : rows.length === 0 ? (
				<p {...stylex.props(styles.empty)}>
					{filterActive ? "No queries match the current filters." : "No queries logged yet."}
				</p>
			) : (
				<>
					<div {...stylex.props(styles.tableWrap)}>
						<table {...stylex.props(styles.table)}>
							<QueryTableHead />
							<tbody>
								{rows.map((row) => (
									<tr key={row.id} {...stylex.props(styles.row)}>
										<QueryCells row={row} clientNames={clientNames} />
									</tr>
								))}
							</tbody>
						</table>
					</div>
					<div {...stylex.props(styles.footer)}>
						<p {...stylex.props(styles.note)}>
							Showing {rows.length} {rows.length === 1 ? "query" : "queries"}
							{hasMore ? "" : " — end of log"}
						</p>
						{hasMore && (
							<button
								type="button"
								onClick={loadMore}
								disabled={base.isFetchingNextPage || base.isPlaceholderData}
								{...stylex.props(shared.button, styles.toolbarButton, shared.focusRing)}
							>
								{base.isFetchingNextPage ? "Loading…" : "Load more"}
							</button>
						)}
					</div>
					{moreError !== null && (
						<p role="alert" {...stylex.props(styles.moreError)}>
							Failed to load more: {moreError}
						</p>
					)}
				</>
			)}
		</section>
	);
}
