"use client";

import {
	ActivityIcon,
	DatabaseIcon,
	RefreshCwIcon,
	Trash2Icon,
	UserPlusIcon,
	UsersIcon,
} from "lucide-react";
import { useEffect, useMemo, useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";

type AdminSummary = {
	period?: PeriodInfo;
	totals?: {
		accounts: number;
		sessions: number;
		visible_sessions?: number;
		child_sessions?: number;
		tool_calls: number;
		tokens: number;
	};
	daily_series?: DailyPoint[];
	memory_queue?: MemoryQueue;
	accounts?: AccountRow[];
};

type PeriodKey =
	| "this_month"
	| "last_month"
	| "last_7_days"
	| "last_30_days"
	| "all";

type PeriodInfo = {
	key: PeriodKey;
	label: string;
	start?: number | null;
	end?: number | null;
};

type DailyPoint = {
	date: string;
	sessions: number;
	visible_sessions?: number;
	child_sessions?: number;
	tool_calls: number;
	tokens: number;
	input_tokens?: number;
	output_tokens?: number;
	reasoning_tokens?: number;
};

type AccountRow = {
	account_id: string;
	registered: boolean;
	period?: PeriodKey;
	session_count: number;
	visible_session_count?: number;
	child_session_count?: number;
	tool_calls: number;
	input_tokens: number;
	output_tokens: number;
	reasoning_tokens: number;
	total_tokens: number;
	average_tokens_per_session: number;
	average_tool_calls_per_session: number;
	latest_session_at: number;
	models: Record<string, number>;
	daily_series?: DailyPoint[];
	user_memory_lines: number;
	skill_memory_count: number;
};

type MemoryQueue = {
	totals: {
		events: number;
		pending: number;
		processing: number;
		done: number;
		failed: number;
	};
	accounts: Array<{
		account_id: string;
		events: number;
		pending: number;
		processing: number;
		done: number;
		failed: number;
	}>;
};

type MemoryEvent = {
	id: string;
	session_id: string;
	skills: string[];
	signals: string[];
	priority: number;
	status: string;
	error?: string | null;
	created_at: string;
	updated_at: string;
};

const TOKEN_KEY = "zk-admin-token";
const PERIOD_OPTIONS: Array<{ key: PeriodKey; label: string }> = [
	{ key: "this_month", label: "本月" },
	{ key: "last_month", label: "上月" },
	{ key: "last_7_days", label: "近7天" },
	{ key: "last_30_days", label: "近30天" },
	{ key: "all", label: "全部" },
];

export default function AdminPage() {
	const [token, setToken] = useState("");
	const [username, setUsername] = useState("admin");
	const [password, setPassword] = useState("");
	const [error, setError] = useState("");
	const [isLoading, setIsLoading] = useState(false);

	useEffect(() => {
		setToken(localStorage.getItem(TOKEN_KEY) ?? "");
	}, []);

	async function login() {
		setError("");
		setIsLoading(true);
		try {
			const response = await fetch("/api/admin/login", {
				method: "POST",
				headers: { "content-type": "application/json" },
				body: JSON.stringify({ username, password }),
			});
			const payload = (await response.json()) as {
				token?: string;
				detail?: string;
			};
			if (!response.ok || !payload.token) {
				throw new Error(payload.detail ?? "登录失败");
			}
			localStorage.setItem(TOKEN_KEY, payload.token);
			setToken(payload.token);
		} catch (err) {
			setError(err instanceof Error ? err.message : "登录失败");
		} finally {
			setIsLoading(false);
		}
	}

	if (!token) {
		return (
			<main className="flex min-h-dvh items-center justify-center bg-background px-4">
				<div className="w-full max-w-sm rounded-lg border bg-card p-6 shadow-sm">
					<div className="mb-5">
						<h1 className="font-semibold text-xl">管理后台</h1>
						<p className="mt-1 text-muted-foreground text-sm">
							查看用户、用量、会话、工具调用和记忆队列。
						</p>
					</div>
					<div className="grid gap-3">
						<Input
							value={username}
							onChange={(event) => setUsername(event.target.value)}
							placeholder="管理员账号"
						/>
						<Input
							type="password"
							value={password}
							onChange={(event) => setPassword(event.target.value)}
							placeholder="管理员密码"
							onKeyDown={(event) => {
								if (event.key === "Enter") login();
							}}
						/>
						{error ? <p className="text-destructive text-sm">{error}</p> : null}
						<Button onClick={login} disabled={isLoading}>
							{isLoading ? "登录中..." : "登录"}
						</Button>
					</div>
				</div>
			</main>
		);
	}

	return <AdminDashboard token={token} onLogout={() => setToken("")} />;
}

function AdminDashboard({
	token,
	onLogout,
}: {
	token: string;
	onLogout: () => void;
}) {
	const [summary, setSummary] = useState<AdminSummary | null>(null);
	const [accounts, setAccounts] = useState<AccountRow[]>([]);
	const [selectedAccountId, setSelectedAccountId] = useState("");
	const [events, setEvents] = useState<MemoryEvent[]>([]);
	const [newAccountId, setNewAccountId] = useState("");
	const [notice, setNotice] = useState("");
	const [period, setPeriod] = useState<PeriodKey>("this_month");

	const selectedAccount = useMemo(
		() => accounts.find((item) => item.account_id === selectedAccountId),
		[accounts, selectedAccountId],
	);

	// biome-ignore lint/correctness/useExhaustiveDependencies: 首次进入后台时加载一次即可，后续由刷新按钮触发。
	useEffect(() => {
		refresh();
	}, [period]);

	// biome-ignore lint/correctness/useExhaustiveDependencies: 账号切换时加载对应记忆；函数体依赖当前 token。
	useEffect(() => {
		if (!selectedAccountId) return;
		loadAccountMemory(selectedAccountId);
	}, [selectedAccountId]);

	async function refresh() {
		const query = `period=${encodeURIComponent(period)}`;
		const [summaryPayload, accountPayload] = await Promise.all([
			adminFetch<AdminSummary>(token, `/summary?${query}`),
			adminFetch<AccountRow[]>(token, `/accounts?${query}`),
		]);
		setSummary(summaryPayload);
		setAccounts(accountPayload);
		const firstAccount =
			selectedAccountId || accountPayload[0]?.account_id || "";
		setSelectedAccountId(firstAccount);
		if (firstAccount) await loadAccountMemory(firstAccount);
	}

	async function loadAccountMemory(accountId: string) {
		const memoryPayload = await adminFetch<{ events: MemoryEvent[] }>(
			token,
			`/memory?account_id=${encodeURIComponent(accountId)}`,
		);
		setEvents(memoryPayload.events ?? []);
	}

	async function createAccount() {
		if (!newAccountId.trim()) return;
		const payload = await adminFetch<{
			account_id: string;
			initial_password: string;
		}>(token, "/accounts", {
			method: "POST",
			body: JSON.stringify({ account_id: newAccountId.trim() }),
		});
		setNotice(
			`已创建 ${payload.account_id}，初始密码 ${payload.initial_password}`,
		);
		setNewAccountId("");
		await refresh();
	}

	async function deleteAccount(accountId: string) {
		if (!confirm(`确定删除账号 ${accountId} 及其本地数据吗？`)) return;
		await adminFetch(token, `/accounts/${encodeURIComponent(accountId)}`, {
			method: "DELETE",
		});
		setNotice(`已删除 ${accountId}`);
		setSelectedAccountId("");
		await refresh();
	}

	function logout() {
		localStorage.removeItem(TOKEN_KEY);
		onLogout();
	}

	return (
		<main className="min-h-dvh bg-background text-foreground">
			<header className="sticky top-0 z-10 border-b bg-background/90 backdrop-blur">
				<div className="mx-auto flex max-w-7xl items-center gap-3 px-5 py-3">
					<div className="flex size-9 items-center justify-center rounded-lg bg-primary text-primary-foreground">
						<DatabaseIcon className="size-4" />
					</div>
					<div className="min-w-0 flex-1">
						<h1 className="font-semibold text-lg">ZK Data Agent 管理后台</h1>
						<p className="text-muted-foreground text-xs">
							用户、会话、工具用量和个性化记忆队列。
						</p>
					</div>
					<Button variant="outline" size="sm" onClick={refresh}>
						<RefreshCwIcon className="size-4" />
						刷新
					</Button>
					<Button variant="ghost" size="sm" onClick={logout}>
						退出
					</Button>
				</div>
			</header>

			<div className="mx-auto grid max-w-7xl gap-4 px-5 py-5">
				{notice ? (
					<div className="rounded-md border bg-muted/40 px-3 py-2 text-sm">
						{notice}
					</div>
				) : null}

				<div className="flex flex-wrap items-center gap-2">
					{PERIOD_OPTIONS.map((option) => (
						<Button
							key={option.key}
							variant={period === option.key ? "default" : "outline"}
							size="sm"
							onClick={() => setPeriod(option.key)}
						>
							{option.label}
						</Button>
					))}
					<span className="text-muted-foreground text-xs">
						当前统计：{summary?.period?.label ?? "本月"}
					</span>
				</div>

				<div className="grid gap-3 md:grid-cols-5">
					<Metric title="用户" value={summary?.totals?.accounts ?? 0} />
					<Metric title="会话" value={summary?.totals?.sessions ?? 0} />
					<Metric
						title="主会话"
						value={summary?.totals?.visible_sessions ?? 0}
					/>
					<Metric title="子会话" value={summary?.totals?.child_sessions ?? 0} />
					<Metric title="工具调用" value={summary?.totals?.tool_calls ?? 0} />
				</div>
				<div className="grid gap-3 md:grid-cols-[1fr_1fr_1fr_240px]">
					<TrendCard
						title="每日会话"
						data={summary?.daily_series ?? []}
						valueKey="sessions"
					/>
					<TrendCard
						title="每日工具调用"
						data={summary?.daily_series ?? []}
						valueKey="tool_calls"
					/>
					<TrendCard
						title="每日 Token"
						data={summary?.daily_series ?? []}
						valueKey="tokens"
					/>
					<Metric
						title="Token"
						value={formatNumber(summary?.totals?.tokens ?? 0)}
					/>
				</div>

				<div className="grid min-h-[640px] gap-4 lg:grid-cols-[320px_1fr]">
					<section className="rounded-lg border bg-card">
						<div className="border-b p-3">
							<div className="mb-3 flex items-center gap-2 font-medium">
								<UsersIcon className="size-4" />
								用户
							</div>
							<div className="flex gap-2">
								<Input
									value={newAccountId}
									placeholder="新账号"
									onChange={(event) => setNewAccountId(event.target.value)}
								/>
								<Button size="icon" variant="outline" onClick={createAccount}>
									<UserPlusIcon className="size-4" />
								</Button>
							</div>
						</div>
						<div className="max-h-[560px] overflow-y-auto p-2">
							{accounts.map((account) => (
								<button
									key={account.account_id}
									type="button"
									className={`mb-1 flex w-full flex-col rounded-md px-3 py-2 text-left transition-colors hover:bg-muted ${
										selectedAccountId === account.account_id ? "bg-muted" : ""
									}`}
									onClick={() => setSelectedAccountId(account.account_id)}
								>
									<span className="flex items-center justify-between gap-2">
										<span className="truncate font-medium text-sm">
											{account.account_id}
										</span>
										<span className="text-muted-foreground text-xs">
											{account.session_count} 会话
										</span>
									</span>
									<span className="mt-1 text-muted-foreground text-xs">
										{account.session_count} 会话 · {account.tool_calls} 工具 ·{" "}
										{formatNumber(account.total_tokens)} tokens
									</span>
								</button>
							))}
						</div>
					</section>

					<section className="grid gap-4">
						<div className="grid gap-3 md:grid-cols-4">
							<Metric title="账号" value={selectedAccount?.account_id ?? "-"} />
							<Metric
								title="本周期会话"
								value={selectedAccount?.session_count ?? 0}
							/>
							<Metric
								title="主/子会话"
								value={`${selectedAccount?.visible_session_count ?? 0}/${selectedAccount?.child_session_count ?? 0}`}
							/>
							<Metric
								title="用户记忆行"
								value={selectedAccount?.user_memory_lines ?? 0}
							/>
							<Metric
								title="Skill 记忆"
								value={selectedAccount?.skill_memory_count ?? 0}
							/>
						</div>

						<div className="grid gap-4 xl:grid-cols-[1.2fr_0.8fr]">
							<div className="rounded-lg border bg-card">
								<div className="border-b p-3 font-medium">用量详情</div>
								<div className="grid gap-3 p-3 md:grid-cols-4">
									<SmallStat
										label="输入 token"
										value={formatNumber(selectedAccount?.input_tokens ?? 0)}
									/>
									<SmallStat
										label="输出 token"
										value={formatNumber(selectedAccount?.output_tokens ?? 0)}
									/>
									<SmallStat
										label="推理 token"
										value={formatNumber(selectedAccount?.reasoning_tokens ?? 0)}
									/>
									<SmallStat
										label="平均 token/会话"
										value={formatNumber(
											selectedAccount?.average_tokens_per_session ?? 0,
										)}
									/>
									<SmallStat
										label="平均工具/会话"
										value={selectedAccount?.average_tool_calls_per_session ?? 0}
									/>
									<SmallStat
										label="注册状态"
										value={selectedAccount?.registered ? "已注册" : "仅目录"}
									/>
									<SmallStat
										label="最近活跃"
										value={formatTime(selectedAccount?.latest_session_at)}
									/>
									<SmallStat
										label="模型数"
										value={Object.keys(selectedAccount?.models ?? {}).length}
									/>
								</div>
							</div>
							<div className="rounded-lg border bg-card">
								<div className="border-b p-3 font-medium">模型分布</div>
								<div className="max-h-40 overflow-y-auto p-3 text-sm">
									{Object.entries(selectedAccount?.models ?? {}).length ? (
										Object.entries(selectedAccount?.models ?? {})
											.sort((a, b) => b[1] - a[1])
											.map(([model, count]) => (
												<div
													key={model}
													className="mb-2 grid grid-cols-[1fr_auto] gap-3"
												>
													<span className="truncate text-muted-foreground">
														{model}
													</span>
													<span>{count} 会话</span>
												</div>
											))
									) : (
										<div className="text-muted-foreground">暂无模型用量</div>
									)}
								</div>
							</div>
						</div>

						<div className="grid gap-3 md:grid-cols-3">
							<TrendCard
								title="当前账号会话"
								data={selectedAccount?.daily_series ?? []}
								valueKey="sessions"
							/>
							<TrendCard
								title="当前账号工具调用"
								data={selectedAccount?.daily_series ?? []}
								valueKey="tool_calls"
							/>
							<TrendCard
								title="当前账号 Token"
								data={selectedAccount?.daily_series ?? []}
								valueKey="tokens"
							/>
						</div>

						<div className="grid gap-4 xl:grid-cols-[360px_1fr]">
							<div className="grid gap-4 content-start">
								<div className="rounded-lg border bg-card">
									<div className="flex items-center gap-2 border-b p-3 font-medium">
										<ActivityIcon className="size-4" />
										记忆队列
									</div>
									<div className="grid grid-cols-2 gap-2 p-3 text-sm">
										<SmallStat
											label="全部"
											value={summary?.memory_queue?.totals.events ?? 0}
										/>
										<SmallStat
											label="待处理"
											value={summary?.memory_queue?.totals.pending ?? 0}
										/>
										<SmallStat
											label="处理中"
											value={summary?.memory_queue?.totals.processing ?? 0}
										/>
										<SmallStat
											label="失败"
											value={summary?.memory_queue?.totals.failed ?? 0}
										/>
									</div>
								</div>

								{selectedAccountId ? (
									<Button
										variant="destructive"
										onClick={() => deleteAccount(selectedAccountId)}
									>
										<Trash2Icon className="size-4" />
										删除当前用户
									</Button>
								) : null}
							</div>

							<div className="rounded-lg border bg-card">
								<div className="border-b p-3 font-medium">最近记忆事件</div>
								<div className="max-h-[520px] overflow-y-auto p-2">
									{events.length ? (
										events.map((event) => (
											<div
												key={event.id}
												className="mb-2 rounded-md border bg-muted/20 p-2 text-xs"
											>
												<div className="flex items-center justify-between gap-2">
													<span className="font-medium">{event.status}</span>
													<span className="text-muted-foreground">
														P{event.priority}
													</span>
												</div>
												<div className="mt-1 truncate text-muted-foreground">
													{event.session_id}
												</div>
												<div className="mt-1">
													{event.signals.join(" / ") || "无信号"}
												</div>
												{event.error ? (
													<div className="mt-1 text-destructive">
														{event.error}
													</div>
												) : null}
											</div>
										))
									) : (
										<p className="p-4 text-center text-muted-foreground text-sm">
											暂无事件。
										</p>
									)}
								</div>
							</div>
						</div>
					</section>
				</div>
			</div>
		</main>
	);
}

function Metric({ title, value }: { title: string; value: string | number }) {
	return (
		<div className="rounded-lg border bg-card p-3">
			<div className="text-muted-foreground text-xs">{title}</div>
			<div className="mt-1 truncate font-semibold text-xl">{value}</div>
		</div>
	);
}

function SmallStat({
	label,
	value,
}: {
	label: string;
	value: string | number;
}) {
	return (
		<div className="rounded-md border bg-muted/20 p-2">
			<div className="font-medium">{value}</div>
			<div className="text-muted-foreground text-xs">{label}</div>
		</div>
	);
}

function TrendCard({
	title,
	data,
	valueKey,
}: {
	title: string;
	data: DailyPoint[];
	valueKey: keyof Pick<DailyPoint, "sessions" | "tool_calls" | "tokens">;
}) {
	const points = data.filter((item) => item.date);
	const latest = points.at(-1)?.[valueKey] ?? 0;
	return (
		<div className="rounded-lg border bg-card p-3">
			<div className="flex items-center justify-between gap-3">
				<div className="text-muted-foreground text-xs">{title}</div>
				<div className="font-medium text-sm">
					{formatNumber(Number(latest))}
				</div>
			</div>
			<Sparkline data={points} valueKey={valueKey} />
		</div>
	);
}

function Sparkline({
	data,
	valueKey,
}: {
	data: DailyPoint[];
	valueKey: keyof Pick<DailyPoint, "sessions" | "tool_calls" | "tokens">;
}) {
	const width = 280;
	const height = 72;
	const padX = 8;
	const padY = 10;
	if (!data.length) {
		return (
			<div className="mt-2 flex h-[72px] items-center justify-center rounded-md bg-muted/30 text-muted-foreground text-xs">
				暂无数据
			</div>
		);
	}
	const values = data.map((item) => Number(item[valueKey] ?? 0));
	const max = Math.max(...values, 1);
	const step = data.length > 1 ? (width - padX * 2) / (data.length - 1) : 0;
	const coords = values.map((value, index) => {
		const x = padX + step * index;
		const y = height - padY - (value / max) * (height - padY * 2);
		return [x, y] as const;
	});
	const path = coords
		.map(
			([x, y], index) =>
				`${index === 0 ? "M" : "L"}${x.toFixed(1)},${y.toFixed(1)}`,
		)
		.join(" ");
	return (
		<div className="mt-2">
			<svg
				viewBox={`0 0 ${width} ${height}`}
				className="h-[72px] w-full rounded-md bg-muted/20"
				role="img"
				aria-label="趋势折线"
			>
				<path
					d={`M${padX},${height - padY}H${width - padX}`}
					stroke="hsl(var(--border))"
					strokeWidth="1"
				/>
				<path
					d={path}
					fill="none"
					stroke="hsl(var(--primary))"
					strokeWidth="2"
					strokeLinecap="round"
					strokeLinejoin="round"
				/>
				{coords.map(([x, y], index) => {
					const point = data[index];
					return (
						<circle
							key={point?.date ?? `${x}-${y}`}
							cx={x}
							cy={y}
							r="2"
							fill="hsl(var(--primary))"
						/>
					);
				})}
			</svg>
			<div className="mt-1 flex justify-between text-muted-foreground text-[11px]">
				<span>{formatShortDate(data[0]?.date)}</span>
				<span>{formatShortDate(data.at(-1)?.date)}</span>
			</div>
		</div>
	);
}

async function adminFetch<T>(
	token: string,
	path: string,
	init?: RequestInit,
): Promise<T> {
	const separator = path.includes("?") ? "&" : "?";
	const response = await fetch(`/api/admin${path}${separator}token=${token}`, {
		...init,
		headers: {
			"content-type": "application/json",
			...(init?.headers ?? {}),
		},
		cache: "no-store",
	});
	const payload = await response.json().catch(() => ({}));
	if (!response.ok) {
		throw new Error(payload.detail ?? payload.error ?? "请求失败");
	}
	return payload as T;
}

function formatNumber(value: number) {
	return new Intl.NumberFormat("zh-CN", {
		notation: value > 10000 ? "compact" : "standard",
		maximumFractionDigits: 1,
	}).format(value);
}

function formatTime(value?: number) {
	if (!value) return "暂无";
	return new Intl.DateTimeFormat("zh-CN", {
		month: "2-digit",
		day: "2-digit",
		hour: "2-digit",
		minute: "2-digit",
	}).format(new Date(value * 1000));
}

function formatShortDate(value?: string) {
	if (!value) return "";
	const parts = value.split("-");
	return parts.length === 3 ? `${parts[1]}/${parts[2]}` : value;
}
