import { useState, type FormEvent } from "react";
import { useInfiniteQuery } from "@tanstack/react-query";
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 { qtypeName } from "./qtype";

const inputClass =
	"mt-1 w-full rounded border border-zinc-300 bg-white px-2 py-1.5 text-sm dark:border-zinc-700 dark:bg-zinc-900";
const buttonClass =
	"rounded border border-zinc-300 px-3 py-1.5 text-sm font-medium focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 dark:border-zinc-700";

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 className="text-zinc-400">—</span>;
	return (
		<span className="inline-flex items-center gap-1.5">
			<span className="rounded bg-red-100 px-1.5 py-0.5 text-xs font-medium text-red-800 dark:bg-red-900 dark:text-red-200">
				Blocked
			</span>
			{row.block_reason !== "" && <span className="text-xs text-zinc-500">{row.block_reason}</span>}
		</span>
	);
}

export function QueryCells({ row }: { row: Omit<QueryRow, "id"> }) {
	return (
		<>
			<td className="px-3 py-2 whitespace-nowrap text-zinc-500">{formatTime(row.ts)}</td>
			<td className="px-3 py-2 font-mono text-xs break-all">{row.domain}</td>
			<td className="px-3 py-2 font-mono text-xs whitespace-nowrap">{row.client_ip}</td>
			<td className="px-3 py-2 whitespace-nowrap">{qtypeName(row.qtype)}</td>
			<td className="px-3 py-2">
				<BlockedCell row={row} />
			</td>
			<td className="px-3 py-2 whitespace-nowrap tabular-nums">
				{row.response_time_us === null ? "—" : formatMicros(row.response_time_us)}
			</td>
			<td className="px-3 py-2 whitespace-nowrap">
				{row.cache_hit === null ? "—" : row.cache_hit ? "hit" : "miss"}
			</td>
			<td className="px-3 py-2 font-mono text-xs break-all">{row.upstream === "" ? "—" : row.upstream}</td>
		</>
	);
}

export function QueryTableHead() {
	const th = "px-3 py-2 font-medium text-zinc-600 dark:text-zinc-400";
	return (
		<thead className="bg-zinc-50 text-left dark:bg-zinc-900">
			<tr>
				<th className={th}>Time</th>
				<th className={th}>Domain</th>
				<th className={th}>Client</th>
				<th className={th}>Type</th>
				<th className={th}>Status</th>
				<th className={th}>Response</th>
				<th className={th}>Cache</th>
				<th className={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 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 className="text-2xl font-semibold">Query Log</h1>

			<form onSubmit={applyFilters} className="mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-5">
				<label className="block text-sm">
					Domain contains
					<input
						type="text"
						value={domain}
						onChange={(event) => setDomain(event.target.value)}
						className={inputClass}
					/>
				</label>
				<label className="block text-sm">
					Client (exact)
					<input
						type="text"
						value={client}
						onChange={(event) => setClient(event.target.value)}
						className={inputClass}
					/>
				</label>
				<label className="block text-sm">
					Status
					<select value={blocked} onChange={(event) => setBlocked(event.target.value)} className={inputClass}>
						<option value="any">All</option>
						<option value="blocked">Blocked only</option>
						<option value="allowed">Allowed only</option>
					</select>
				</label>
				<label className="block text-sm">
					Since
					<input
						type="datetime-local"
						value={since}
						onChange={(event) => setSince(event.target.value)}
						className={inputClass}
					/>
				</label>
				<label className="block text-sm">
					Until
					<input
						type="datetime-local"
						value={until}
						onChange={(event) => setUntil(event.target.value)}
						className={inputClass}
					/>
				</label>
				<div className="flex items-end gap-2 sm:col-span-2 lg:col-span-5">
					<button type="submit" className={buttonClass}>
						Apply filters
					</button>
					<button type="button" onClick={clearFilters} className={buttonClass}>
						Clear
					</button>
					{base.isFetching && (
						<span className="text-sm text-zinc-500" role="status">
							Loading…
						</span>
					)}
				</div>
			</form>

			{base.data === undefined ? (
				<p className="mt-6 animate-pulse text-zinc-500" role="status">
					Loading query log…
				</p>
			) : rows.length === 0 ? (
				<p className="mt-6 text-zinc-500">
					{filterActive ? "No queries match the current filters." : "No queries logged yet."}
				</p>
			) : (
				<>
					<div className="mt-4 overflow-x-auto rounded border border-zinc-200 dark:border-zinc-800">
						<table className="w-full text-sm">
							<QueryTableHead />
							<tbody className="divide-y divide-zinc-100 dark:divide-zinc-800">
								{rows.map((row) => (
									<tr key={row.id}>
										<QueryCells row={row} />
									</tr>
								))}
							</tbody>
						</table>
					</div>
					<div className="mt-3 flex items-center gap-3">
						<p className="text-sm text-zinc-500">
							Showing {rows.length} {rows.length === 1 ? "query" : "queries"}
							{hasMore ? "" : " — end of log"}
						</p>
						{hasMore && (
							<button
								type="button"
								onClick={loadMore}
								disabled={base.isFetchingNextPage || base.isPlaceholderData}
								className={buttonClass}
							>
								{base.isFetchingNextPage ? "Loading…" : "Load more"}
							</button>
						)}
					</div>
					{moreError !== null && (
						<p role="alert" className="mt-2 text-sm text-red-700 dark:text-red-300">
							Failed to load more: {moreError}
						</p>
					)}
				</>
			)}
		</section>
	);
}
