import { useState } from "react";
import {
	useInfiniteQuery,
	useMutation,
	useQueryClient,
	type InfiniteData,
	type UseInfiniteQueryResult,
} from "@tanstack/react-query";
import { Link, useNavigate, useSearch } from "@tanstack/react-router";
import * as stylex from "@stylexjs/stylex";
import * as api from "@/lib/api";
import InlineError from "@/lib/InlineError";
import { formatDuration, formatTime } from "@/lib/format";
import { diagnosticPurgeMutation, diagnosticsInfiniteQuery, diagnosticsPurgeResolvedMutation } from "@/lib/queries";
import type { DiagnosticEvent, DiagnosticSeverity, DiagnosticState, DiagnosticsPage as Page } from "@/lib/types";
import ConfirmDialog from "@/ui/ConfirmDialog";
import Select from "@/ui/Select";
import { styles as shared } from "@/ui/styles";
import { colors } from "@/ui/tokens.stylex";
import HealthStrip from "./HealthStrip";
import SeverityBadge from "./SeverityBadge";
import { diagnosticsFilterOf } from "./filter";
import { DIAGNOSTIC_COMPONENTS, componentLabel, copyFor } from "./eventCopy";

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

const STATE_OPTIONS = [
	{ value: "all", label: "Active and resolved" },
	{ value: "active", label: "Active only" },
	{ value: "resolved", label: "Resolved only" },
];

const SEVERITY_OPTIONS = [
	{ value: "any", label: "Any severity" },
	{ value: "warning", label: "Warnings" },
	{ value: "error", label: "Errors" },
];

const COMPONENT_OPTIONS = [
	{ value: "any", label: "All components" },
	...DIAGNOSTIC_COMPONENTS.map((component) => ({ value: component, label: componentLabel(component) })),
];

const styles = stylex.create({
	heading: {
		fontSize: "1.5rem",
		lineHeight: "2rem",
		fontWeight: 600,
	},
	intro: {
		marginTop: "0.25rem",
		fontSize: "0.875rem",
		lineHeight: "1.25rem",
		color: colors.textMuted,
		maxWidth: "48rem",
	},
	filterGrid: {
		marginTop: "1rem",
		display: "grid",
		gap: "0.75rem",
		gridTemplateColumns: {
			default: "repeat(1, minmax(0, 1fr))",
			"@media (min-width: 640px)": "repeat(3, minmax(0, 1fr))",
		},
		maxWidth: "48rem",
	},
	sectionHeading: {
		marginTop: "1.5rem",
		fontSize: "1.125rem",
		lineHeight: "1.75rem",
		fontWeight: 600,
	},
	sectionHeadingRow: {
		display: "flex",
		alignItems: "baseline",
		flexWrap: "wrap",
		justifyContent: "space-between",
		gap: "0.75rem",
	},
	/**
	 * Nothing open is the normal state of a working install, so it gets one
	 * quiet muted line — no border, no icon, no alert role. A panel here would
	 * read as a broken page rather than as good news.
	 */
	healthy: {
		marginTop: "0.5rem",
		fontSize: "0.875rem",
		lineHeight: "1.25rem",
		color: colors.textSecondary,
	},
	empty: {
		marginTop: "0.5rem",
		fontSize: "0.875rem",
		lineHeight: "1.25rem",
		color: colors.textMuted,
	},
	cardList: {
		marginTop: "0.75rem",
		display: "flex",
		flexDirection: "column",
		gap: "0.5rem",
		listStyleType: "none",
		padding: 0,
	},
	card: {
		borderRadius: "0.25rem",
		borderWidth: 1,
		borderStyle: "solid",
		borderColor: colors.border,
		backgroundColor: colors.surfaceRaised,
		paddingInline: "0.75rem",
		paddingBlock: "0.625rem",
	},
	cardTop: {
		display: "flex",
		alignItems: "baseline",
		flexWrap: "wrap",
		gap: "0.5rem",
	},
	cardTitle: {
		fontWeight: 500,
		color: colors.primaryOnSurface,
		textDecorationLine: "none",
	},
	subject: {
		fontSize: "0.875rem",
		lineHeight: "1.25rem",
		color: colors.textSecondary,
		wordBreak: "break-all",
	},
	meta: {
		marginTop: "0.25rem",
		fontSize: "0.75rem",
		lineHeight: "1rem",
		color: colors.textMuted,
	},
	rangeNotice: {
		marginTop: "0.75rem",
		borderRadius: "0.25rem",
		borderWidth: 1,
		borderStyle: "solid",
		borderColor: colors.border,
		backgroundColor: colors.surfaceHover,
		paddingInline: "0.75rem",
		paddingBlock: "0.5rem",
		fontSize: "0.875rem",
		lineHeight: "1.25rem",
		color: colors.textSecondary,
	},
	tableWrap: {
		marginTop: "0.75rem",
		overflowX: "auto",
		borderRadius: "0.25rem",
		borderWidth: 1,
		borderStyle: "solid",
		borderColor: colors.border,
	},
	table: {
		width: "100%",
		fontSize: "0.875rem",
		lineHeight: "1.25rem",
	},
	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,
		whiteSpace: "nowrap",
	},
	row: {
		borderTopWidth: { default: 1, ":first-child": 0 },
		borderTopStyle: "solid",
		borderTopColor: colors.border,
	},
	cell: {
		paddingInline: "0.75rem",
		paddingBlock: "0.5rem",
	},
	nowrap: {
		whiteSpace: "nowrap",
	},
	rowLink: {
		color: colors.primaryOnSurface,
		textDecorationLine: "none",
	},
	footer: {
		marginTop: "0.75rem",
		display: "flex",
		alignItems: "center",
		gap: "0.75rem",
	},
	note: {
		fontSize: "0.875rem",
		lineHeight: "1.25rem",
		color: colors.textMuted,
	},
	moreError: {
		marginTop: "0.5rem",
		fontSize: "0.875rem",
		lineHeight: "1.25rem",
		color: colors.dangerText,
	},
});

type Section = UseInfiniteQueryResult<InfiniteData<Page, unknown>, Error>;

/** The two enum filters, narrowed from the picker's string rather than cast. */
function asState(value: string): DiagnosticState | undefined {
	return value === "active" || value === "resolved" ? value : undefined;
}

function asSeverity(value: string): DiagnosticSeverity | undefined {
	return value === "warning" || value === "error" ? value : undefined;
}

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

function occurrenceText(count: number): string {
	return `${count} ${count === 1 ? "occurrence" : "occurrences"}`;
}

function rowsOf(section: Section): DiagnosticEvent[] {
	return (section.data?.pages ?? []).flatMap((page) => page.events);
}

/**
 * The cursor comes from the newest page on screen, not from `hasNextPage`:
 * while placeholder data stands in for a filter change the query state is
 * empty, and the button would flash away and back.
 */
function hasMore(section: Section): boolean {
	const pages = section.data?.pages ?? [];
	const last = pages[pages.length - 1];
	return last !== undefined && last.next_before !== null;
}

function MoreButton({ section }: { section: Section }) {
	const more = hasMore(section);
	// A 401 is already redirecting via the cache-level handleUnauthorized.
	const isUnauthorized = section.error instanceof api.ApiError && section.error.status === 401;
	const failed = section.isFetchNextPageError && !isUnauthorized ? errorMessage(section.error) : null;
	if (!more && failed === null) return null;
	return (
		<>
			<div {...stylex.props(styles.footer)}>
				{more && (
					<button
						type="button"
						onClick={() => {
							if (section.isFetchingNextPage || section.isPlaceholderData) return;
							void section.fetchNextPage();
						}}
						disabled={section.isFetchingNextPage || section.isPlaceholderData}
						{...stylex.props(shared.button, shared.focusRing)}
					>
						{section.isFetchingNextPage ? "Loading…" : "Load more"}
					</button>
				)}
			</div>
			{failed !== null && (
				<p role="alert" {...stylex.props(styles.moreError)}>
					Failed to load more: {failed}
				</p>
			)}
		</>
	);
}

/**
 * The window the page is bounded to, whenever it is bounded.
 *
 * A link from a query detail arrives with an absolute five-minute window, and
 * an empty Active section inside it means something very different from an
 * empty Active section over the whole history. The page has to say which it is
 * showing, and offer the way out of it.
 */
function RangeNotice({ since, until }: { since?: number; until?: number }) {
	const navigate = useNavigate({ from: "/diagnostics" });
	if (since === undefined && until === undefined) return null;
	const from = since === undefined ? "the start of the history" : formatTime(since);
	const to = until === undefined ? "now" : formatTime(until);
	return (
		<p role="status" {...stylex.props(styles.rangeNotice)}>
			Showing events that overlap {from} to {to}.{" "}
			<button
				type="button"
				onClick={() => void navigate({ search: (prev) => ({ ...prev, since: undefined, until: undefined }) })}
				{...stylex.props(shared.linkButton, shared.focusRing)}
			>
				Clear the time range
			</button>
		</p>
	);
}

function ActiveCard({ event, now }: { event: DiagnosticEvent; now: number }) {
	const copy = copyFor(event.code);
	return (
		<li {...stylex.props(styles.card)}>
			<div {...stylex.props(styles.cardTop)}>
				<SeverityBadge severity={event.severity} />
				<Link
					to="/diagnostics/$id"
					params={{ id: String(event.id) }}
					{...stylex.props(styles.cardTitle, shared.focusRing)}
				>
					{copy.title}
				</Link>
				<span {...stylex.props(styles.subject)}>{event.subject}</span>
			</div>
			<p {...stylex.props(styles.meta)}>
				Active for {formatDuration(now - event.first_seen)} · {occurrenceText(event.occurrences)} · last failure{" "}
				{formatTime(event.last_seen)}
			</p>
		</li>
	);
}

function HistoryRow({ event, onPurge, busy }: { event: DiagnosticEvent; onPurge: () => void; busy: boolean }) {
	const copy = copyFor(event.code);
	return (
		<tr {...stylex.props(styles.row)}>
			<td {...stylex.props(styles.cell)}>
				<SeverityBadge severity={event.severity} />
			</td>
			<td {...stylex.props(styles.cell)}>
				<Link
					to="/diagnostics/$id"
					params={{ id: String(event.id) }}
					{...stylex.props(styles.rowLink, shared.focusRing)}
				>
					{copy.title}
				</Link>
			</td>
			<td {...stylex.props(styles.cell)}>{event.subject}</td>
			<td {...stylex.props(styles.cell, styles.nowrap)}>{formatTime(event.first_seen)}</td>
			<td {...stylex.props(styles.cell, styles.nowrap)}>
				{event.resolved_at === null ? "—" : formatTime(event.resolved_at)}
			</td>
			<td {...stylex.props(styles.cell, styles.nowrap, shared.tabularNums)}>{event.occurrences}</td>
			<td {...stylex.props(styles.cell, styles.nowrap)}>
				<button
					type="button"
					onClick={onPurge}
					disabled={busy}
					{...stylex.props(shared.dangerLinkButton, shared.focusRing)}
				>
					Purge
				</button>
			</td>
		</tr>
	);
}

export default function DiagnosticsPage() {
	const search = useSearch({ from: "/shell/diagnostics" });
	const navigate = useNavigate({ from: "/diagnostics" });
	const state = search.state ?? "all";

	const base = diagnosticsFilterOf(search);

	const active = useInfiniteQuery(diagnosticsInfiniteQuery({ ...base, state: "active" }, state !== "resolved"));
	const history = useInfiniteQuery(diagnosticsInfiniteQuery({ ...base, state: "resolved" }, state !== "active"));

	const queryClient = useQueryClient();
	const purgeOne = useMutation(diagnosticPurgeMutation(queryClient));
	const purgeAll = useMutation(diagnosticsPurgeResolvedMutation(queryClient));
	// `null` is "no dialog"; the id is which row it is about, and `"all"` the
	// whole history. One piece of state, so the two dialogs cannot both be open.
	const [pendingPurge, setPendingPurge] = useState<number | "all" | null>(null);

	const activeRows = rowsOf(active);
	const historyRows = rowsOf(history);
	const now = Math.floor(Date.now() / 1000);
	const purging = purgeOne.isPending || purgeAll.isPending;

	function setSearch(patch: Partial<typeof search>) {
		void navigate({ search: (prev) => ({ ...prev, ...patch }) });
	}

	function confirmPurge() {
		if (pendingPurge === null) return;
		if (pendingPurge === "all") {
			purgeAll.mutate();
		} else {
			purgeOne.mutate(pendingPurge);
		}
		setPendingPurge(null);
	}

	return (
		<section>
			<h1 {...stylex.props(styles.heading)}>Diagnostics</h1>
			<p {...stylex.props(styles.intro)}>
				Operational failures, one entry per subject that failed. An entry opens on the first failure, counts
				repeats, and closes when the subject recovers.
			</p>

			<HealthStrip />

			<RangeNotice since={search.since} until={search.until} />

			<div {...stylex.props(styles.filterGrid)}>
				<Select
					variant="compactField"
					label="Show"
					value={state}
					onChange={(value) => setSearch({ state: asState(value) })}
					options={STATE_OPTIONS}
				/>
				<Select
					variant="compactField"
					label="Severity"
					value={search.severity ?? "any"}
					onChange={(value) => setSearch({ severity: asSeverity(value) })}
					options={SEVERITY_OPTIONS}
				/>
				<Select
					variant="compactField"
					label="Component"
					value={search.component ?? "any"}
					onChange={(value) => setSearch({ component: value === "any" ? undefined : value })}
					options={COMPONENT_OPTIONS}
				/>
			</div>

			{state !== "resolved" && (
				<>
					<h2 {...stylex.props(styles.sectionHeading)}>Active</h2>
					{active.status === "error" ? (
						<InlineError error={active.error} onRetry={() => void active.refetch()} />
					) : active.data === undefined ? (
						<p {...stylex.props(styles.empty, shared.pulse)} role="status">
							Loading diagnostics…
						</p>
					) : activeRows.length === 0 ? (
						<p {...stylex.props(styles.healthy)} role="status">
							No active operational issues.
						</p>
					) : (
						<>
							<ul {...stylex.props(styles.cardList)}>
								{activeRows.map((event) => (
									<ActiveCard key={event.id} event={event} now={now} />
								))}
							</ul>
							<MoreButton section={active} />
						</>
					)}
				</>
			)}

			{state !== "active" && (
				<>
					<div {...stylex.props(styles.sectionHeading, styles.sectionHeadingRow)}>
						<h2>Resolved</h2>
						{historyRows.length > 0 && (
							<button
								type="button"
								onClick={() => setPendingPurge("all")}
								disabled={purging}
								{...stylex.props(shared.dangerLinkButton, shared.focusRing)}
							>
								Purge all resolved
							</button>
						)}
					</div>
					{history.status === "error" ? (
						<InlineError error={history.error} onRetry={() => void history.refetch()} />
					) : history.data === undefined ? (
						<p {...stylex.props(styles.empty, shared.pulse)} role="status">
							Loading history…
						</p>
					) : historyRows.length === 0 ? (
						<p {...stylex.props(styles.empty)}>Nothing has failed and recovered in the retained window.</p>
					) : (
						<>
							<div {...stylex.props(styles.tableWrap)}>
								<table {...stylex.props(styles.table)}>
									<thead {...stylex.props(styles.head)}>
										<tr>
											<th {...stylex.props(styles.th)}>Severity</th>
											<th {...stylex.props(styles.th)}>Event</th>
											<th {...stylex.props(styles.th)}>Subject</th>
											<th {...stylex.props(styles.th)}>Started</th>
											<th {...stylex.props(styles.th)}>Resolved</th>
											<th {...stylex.props(styles.th)}>Occurrences</th>
											<th {...stylex.props(styles.th)}>
												<span {...stylex.props(shared.srOnly)}>Actions</span>
											</th>
										</tr>
									</thead>
									<tbody>
										{historyRows.map((event) => (
											<HistoryRow
												key={event.id}
												event={event}
												busy={purging}
												onPurge={() => setPendingPurge(event.id)}
											/>
										))}
									</tbody>
								</table>
							</div>
							<p {...stylex.props(styles.footer, styles.note)}>
								Showing {historyRows.length} resolved {historyRows.length === 1 ? "entry" : "entries"}
								{hasMore(history) ? "" : " — end of history"}
							</p>
							<MoreButton section={history} />
						</>
					)}
					<InlineError error={purgeOne.error ?? purgeAll.error} />
				</>
			)}

			<ConfirmDialog
				isOpen={pendingPurge !== null}
				title={pendingPurge === "all" ? "Purge resolved history" : "Purge event"}
				message={
					pendingPurge === "all"
						? "Purge all resolved events? Active events are kept."
						: "Purge this resolved event? Its history is gone for good."
				}
				confirmLabel={pendingPurge === "all" ? "Purge all" : "Purge"}
				onConfirm={confirmPurge}
				onCancel={() => setPendingPurge(null)}
			/>
		</section>
	);
}
