"use client"

import Link from "next/link"
import { useCallback, useEffect, useMemo, useState } from "react"
import { PageHeader } from "@/components/page-header"
import { Badge } from "@/components/ui/badge"
import { Button, buttonVariants } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible"
import { Input } from "@/components/ui/input"
import { Separator } from "@/components/ui/separator"
import { useDataSource } from "@/lib/data-source"
import {
  SCHEDULER_JOB_DESCRIPTIONS,
  SCHEDULER_JOB_KEYS,
  SCHEDULER_JOB_LABELS,
  type CollectorSettingsDto,
  type SchedulerJobStatusDto,
  type SchedulerRunRowDto,
  type UptimeSettingsDto,
} from "@/lib/scheduler-settings"
import { requestJson } from "@/shared/api/http-client"
import {
  parseSchedulerRunSnapshot,
  type AlertEngineRuleDiagSnapshot,
  type AlertEngineRunSnapshot,
  type GreBgpSnapshotRunSnapshot,
  type InternetPathRunSnapshot,
  type PingRunSnapshot,
  type ResourcesRunSnapshot,
  type SchedulerRunSnapshot,
  type ServersRestPingRunSnapshot,
  type SpeedScheduledRunSnapshot,
  type TrafficRunSnapshot,
} from "@/lib/scheduler-run-snapshot"
import { cn } from "@/lib/utils"
import {
  AlertCircleIcon,
  CalendarClockIcon,
  CheckCircleIcon,
  ChevronDownIcon,
  DatabaseIcon,
  LoaderCircleIcon,
  RefreshCwIcon,
  XCircleIcon,
} from "lucide-react"

function makeApiFetch(backendUrl: string) {
  return async function apiFetch<T>(path: string, init?: RequestInit): Promise<T> {
    return requestJson<T>(backendUrl, path, init)
  }
}

function Toggle({ checked, onChange }: { checked: boolean; onChange: (v: boolean) => void }) {
  return (
    <button
      type="button"
      role="switch"
      aria-checked={checked}
      onClick={() => onChange(!checked)}
      className={cn(
        "relative inline-flex h-5 w-9 shrink-0 rounded-full border-2 border-transparent transition-colors cursor-pointer",
        checked ? "bg-primary" : "bg-input",
      )}
    >
      <span
        className={cn(
          "pointer-events-none block h-4 w-4 rounded-full bg-white shadow-sm transition-transform",
          checked ? "translate-x-4" : "translate-x-0",
        )}
      />
    </button>
  )
}

function fmtMs(ms: number): string {
  if (ms < 1000) return `${ms} мс`
  const s = ms / 1000
  return s < 60 ? `${s.toFixed(1)} с` : `${Math.floor(s / 60)} м ${Math.round(s % 60)} с`
}

function fmtUptimeSec(sec: number): string {
  if (sec <= 0) return "—"
  const d = Math.floor(sec / 86400)
  const h = Math.floor((sec % 86400) / 3600)
  const m = Math.floor((sec % 3600) / 60)
  if (d > 0) return `${d}д ${h}ч`
  if (h > 0) return `${h}ч ${m}м`
  return `${m}м`
}

function SnapshotTables({ snap }: { snap: SchedulerRunSnapshot }) {
  if (snap.job === "traffic") {
    const t = snap as TrafficRunSnapshot
    return (
      <div className="space-y-3">
        {t.skipped ? (
          <p className="text-xs text-amber-600 dark:text-amber-400">Прогон пропущен: предыдущий сбор трафика ещё выполнялся.</p>
        ) : null}
        {t.fatalError ? (
          <Alert variant="destructive" className="py-2">
            <AlertCircleIcon />
            <AlertDescription className="text-xs">Критическая ошибка: {t.fatalError}</AlertDescription>
          </Alert>
        ) : null}
        <p className="text-xs text-muted-foreground">
          Сэмплы на момент <span className="font-mono tabular-nums">{new Date(t.sampledAt).toLocaleString("ru-RU")}</span>
        </p>
        <div className="overflow-x-auto rounded-md border border-border">
          <table className="w-full text-xs">
            <thead>
              <tr className="border-b border-border bg-muted/40 text-muted-foreground text-left">
                <th className="px-3 py-2 font-medium">Сервер</th>
                <th className="px-3 py-2 font-medium">Хост</th>
                <th className="px-3 py-2 font-medium">Результат</th>
                <th className="px-3 py-2 font-medium text-right">IF</th>
                <th className="px-3 py-2 font-medium text-right">Σ RX</th>
                <th className="px-3 py-2 font-medium text-right">Σ TX</th>
                <th className="px-3 py-2 font-medium">Ошибка</th>
              </tr>
            </thead>
            <tbody className="divide-y divide-border">
              {t.servers.map((s) => (
                <tr key={s.serverId} className="hover:bg-muted/30">
                  <td className="px-3 py-2 font-medium">{s.name}</td>
                  <td className="px-3 py-2 font-mono text-muted-foreground">{s.host}</td>
                  <td className="px-3 py-2">
                    {s.ok ? (
                      <Badge variant="outline" className="text-[10px] border-emerald-500/40 text-emerald-700 dark:text-emerald-400">
                        ok
                      </Badge>
                    ) : (
                      <Badge variant="outline" className="text-[10px] border-destructive/50 text-destructive">
                        ошибка
                      </Badge>
                    )}
                  </td>
                  <td className="px-3 py-2 text-right tabular-nums">{s.interfaces ?? "—"}</td>
                  <td className="px-3 py-2 text-right tabular-nums">{s.sumRxMbps != null ? `${s.sumRxMbps} Мбит/с` : "—"}</td>
                  <td className="px-3 py-2 text-right tabular-nums">{s.sumTxMbps != null ? `${s.sumTxMbps} Мбит/с` : "—"}</td>
                  <td className="px-3 py-2 text-destructive max-w-[220px] truncate" title={s.error}>{s.error ?? "—"}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>
    )
  }
  if (snap.job === "uptime_resources") {
    const u = snap as ResourcesRunSnapshot
    return (
      <div className="space-y-3">
        {u.skipped ? (
          <p className="text-xs text-amber-600 dark:text-amber-400">Прогон пропущен: сбор ресурсов уже выполняется.</p>
        ) : null}
        {u.fatalError ? (
          <Alert variant="destructive" className="py-2">
            <AlertCircleIcon />
            <AlertDescription className="text-xs">Критическая ошибка: {u.fatalError}</AlertDescription>
          </Alert>
        ) : null}
        <p className="text-xs text-muted-foreground">
          Сэмплы на <span className="font-mono tabular-nums">{new Date(u.sampledAt).toLocaleString("ru-RU")}</span>
        </p>
        <div className="overflow-x-auto rounded-md border border-border">
          <table className="w-full text-xs">
            <thead>
              <tr className="border-b border-border bg-muted/40 text-muted-foreground text-left">
                <th className="px-3 py-2 font-medium">Сервер</th>
                <th className="px-3 py-2 font-medium">Статус</th>
                <th className="px-3 py-2 font-medium text-right">CPU %</th>
                <th className="px-3 py-2 font-medium text-right">Память</th>
                <th className="px-3 py-2 font-medium text-right">% RAM</th>
                <th className="px-3 py-2 font-medium text-right">Диск своб.</th>
                <th className="px-3 py-2 font-medium">Uptime</th>
                <th className="px-3 py-2 font-medium">Плата / ROS</th>
                <th className="px-3 py-2 font-medium">Ошибка</th>
              </tr>
            </thead>
            <tbody className="divide-y divide-border">
              {u.servers.map((s) => (
                <tr key={s.serverId} className="hover:bg-muted/30">
                  <td className="px-3 py-2">
                    <span className="font-medium">{s.name}</span>
                    <span className="block font-mono text-[10px] text-muted-foreground">{s.host}</span>
                  </td>
                  <td className="px-3 py-2">
                    <Badge
                      variant="outline"
                      className={cn(
                        "text-[10px]",
                        s.status === "online" && "border-emerald-500/40 text-emerald-700 dark:text-emerald-400",
                        s.status === "offline" && "border-destructive/50 text-destructive",
                      )}
                    >
                      {s.status}
                    </Badge>
                  </td>
                  <td className="px-3 py-2 text-right tabular-nums">{s.cpuLoadPct ?? "—"}</td>
                  <td className="px-3 py-2 text-right tabular-nums whitespace-nowrap">
                    {s.memUsedMb != null && s.memTotalMb != null ? `${s.memUsedMb} / ${s.memTotalMb} МБ` : "—"}
                  </td>
                  <td className="px-3 py-2 text-right tabular-nums">{s.memUsedPct != null ? `${s.memUsedPct}%` : "—"}</td>
                  <td className="px-3 py-2 text-right tabular-nums whitespace-nowrap">
                    {s.diskFreeMb != null && s.diskTotalMb != null ? `${s.diskFreeMb} / ${s.diskTotalMb} МБ` : "—"}
                  </td>
                  <td className="px-3 py-2 tabular-nums">{s.uptimeSeconds != null ? fmtUptimeSec(s.uptimeSeconds) : "—"}</td>
                  <td className="px-3 py-2 max-w-[140px]">
                    <span className="block truncate" title={s.boardName}>{s.boardName || "—"}</span>
                    <span className="block truncate text-muted-foreground font-mono text-[10px]" title={s.rosVersion}>{s.rosVersion || ""}</span>
                  </td>
                  <td className="px-3 py-2 text-destructive max-w-[160px] truncate" title={s.error}>{s.error ?? "—"}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>
    )
  }
  if (snap.job === "servers_rest_ping") {
    const s = snap as ServersRestPingRunSnapshot
    return (
      <div className="space-y-3">
        {s.skipped ? (
          <p className="text-xs text-amber-600 dark:text-amber-400">Прогон пропущен: предыдущая проверка API ещё выполнялась.</p>
        ) : null}
        {s.fatalError ? (
          <Alert variant="destructive" className="py-2">
            <AlertCircleIcon />
            <AlertDescription className="text-xs">Критическая ошибка: {s.fatalError}</AlertDescription>
          </Alert>
        ) : null}
        <p className="text-xs text-muted-foreground">
          GET <span className="font-mono">/system/identity</span> на{" "}
          <span className="font-mono tabular-nums">{new Date(s.sampledAt).toLocaleString("ru-RU")}</span>
        </p>
        <div className="overflow-x-auto rounded-md border border-border">
          <table className="w-full text-xs">
            <thead>
              <tr className="border-b border-border bg-muted/40 text-muted-foreground text-left">
                <th className="px-3 py-2 font-medium">Сервер</th>
                <th className="px-3 py-2 font-medium">Хост</th>
                <th className="px-3 py-2 font-medium">Результат</th>
                <th className="px-3 py-2 font-medium text-right">RTT REST</th>
                <th className="px-3 py-2 font-medium">Ошибка</th>
              </tr>
            </thead>
            <tbody className="divide-y divide-border">
              {s.servers.map((row) => (
                <tr key={row.serverId} className="hover:bg-muted/30">
                  <td className="px-3 py-2 font-medium">{row.name}</td>
                  <td className="px-3 py-2 font-mono text-muted-foreground">{row.host}</td>
                  <td className="px-3 py-2">
                    {row.ok ? (
                      <Badge variant="outline" className="text-[10px] border-emerald-500/40 text-emerald-700 dark:text-emerald-400">
                        ok
                      </Badge>
                    ) : (
                      <Badge variant="outline" className="text-[10px] border-destructive/50 text-destructive">
                        недоступен
                      </Badge>
                    )}
                  </td>
                  <td className="px-3 py-2 text-right tabular-nums">
                    {row.latencyMs != null ? `${row.latencyMs} мс` : "—"}
                  </td>
                  <td className="px-3 py-2 text-destructive max-w-[220px] truncate" title={row.error}>{row.error ?? "—"}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>
    )
  }
  if (snap.job === "uptime_ping") {
    const p = snap as PingRunSnapshot
    return (
      <div className="space-y-3">
        {p.skipped ? (
          <p className="text-xs text-amber-600 dark:text-amber-400">Прогон пропущен: сбор ping уже выполняется.</p>
        ) : null}
        {p.fatalError ? (
          <Alert variant="destructive" className="py-2">
            <AlertCircleIcon />
            <AlertDescription className="text-xs">Критическая ошибка: {p.fatalError}</AlertDescription>
          </Alert>
        ) : null}
        {p.skippedByInterval != null && p.skippedByInterval > 0 ? (
          <p className="text-xs text-muted-foreground">Пропущено по интервалу проб: {p.skippedByInterval}</p>
        ) : null}
        <p className="text-xs text-muted-foreground">
          Сэмплы на <span className="font-mono tabular-nums">{new Date(p.sampledAt).toLocaleString("ru-RU")}</span> — только пробы, для которых записан замер в этом тике
        </p>
        <div className="overflow-x-auto rounded-md border border-border">
          <table className="w-full text-xs">
            <thead>
              <tr className="border-b border-border bg-muted/40 text-muted-foreground text-left">
                <th className="px-3 py-2 font-medium">Проба</th>
                <th className="px-3 py-2 font-medium">Цель</th>
                <th className="px-3 py-2 font-medium">Источник</th>
                <th className="px-3 py-2 font-medium">IF</th>
                <th className="px-3 py-2 font-medium text-right">RTT</th>
                <th className="px-3 py-2 font-medium text-right">Loss</th>
                <th className="px-3 py-2 font-medium">Статус</th>
                <th className="px-3 py-2 font-medium">Ошибка</th>
              </tr>
            </thead>
            <tbody className="divide-y divide-border">
              {p.probes.map((x) => (
                <tr key={`${x.probeId}-${x.target}`} className="hover:bg-muted/30">
                  <td className="px-3 py-2">
                    <span className="font-medium">{x.name}</span>
                    <span className="block font-mono text-[10px] text-muted-foreground">{x.probeId}</span>
                  </td>
                  <td className="px-3 py-2 font-mono">{x.target}</td>
                  <td className="px-3 py-2">{x.srcServerName}</td>
                  <td className="px-3 py-2 font-mono text-muted-foreground">{x.srcInterface || "—"}</td>
                  <td className="px-3 py-2 text-right tabular-nums">{x.rttMs != null ? `${x.rttMs} мс` : "—"}</td>
                  <td className="px-3 py-2 text-right tabular-nums">{x.lossPct}%</td>
                  <td className="px-3 py-2">
                    <Badge variant="outline" className="text-[10px]">{x.status}</Badge>
                  </td>
                  <td className="px-3 py-2 text-destructive max-w-[180px] truncate" title={x.error}>{x.error ?? "—"}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>
    )
  }
  if (snap.job === "uptime_speed") {
    const s = snap as SpeedScheduledRunSnapshot
    return (
      <div className="space-y-3">
        <p className="text-xs text-muted-foreground">
          Прогоны speed на <span className="font-mono tabular-nums">{new Date(s.sampledAt).toLocaleString("ru-RU")}</span> — по очереди для каждой включённой пробы
        </p>
        <div className="overflow-x-auto rounded-md border border-border">
          <table className="w-full text-xs">
            <thead>
              <tr className="border-b border-border bg-muted/40 text-muted-foreground text-left">
                <th className="px-3 py-2 font-medium">Проба</th>
                <th className="px-3 py-2 font-medium">Маршрут</th>
                <th className="px-3 py-2 font-medium">Интерфейсы</th>
                <th className="px-3 py-2 font-medium">Протокол</th>
                <th className="px-3 py-2 font-medium text-right">TX</th>
                <th className="px-3 py-2 font-medium text-right">RX</th>
                <th className="px-3 py-2 font-medium text-right">Ping RTT</th>
                <th className="px-3 py-2 font-medium text-right">Loss</th>
                <th className="px-3 py-2 font-medium">Результат</th>
                <th className="px-3 py-2 font-medium">Ошибка</th>
              </tr>
            </thead>
            <tbody className="divide-y divide-border">
              {s.runs.map((x) => (
                <tr key={x.probeId} className="hover:bg-muted/30">
                  <td className="px-3 py-2 font-mono">{x.probeId}</td>
                  <td className="px-3 py-2 whitespace-nowrap">
                    {x.srcServerName} <span className="text-muted-foreground">→</span> {x.dstServerName}
                  </td>
                  <td className="px-3 py-2 font-mono text-[10px]">
                    <span className="block">{x.srcInterface || "—"}</span>
                    <span className="block text-muted-foreground">{x.dstInterface || "—"}</span>
                  </td>
                  <td className="px-3 py-2">{x.protocol} / {x.direction} / {x.durationSec}s</td>
                  <td className="px-3 py-2 text-right tabular-nums">{x.txAvgMbps != null ? `${Number(x.txAvgMbps).toFixed(1)}` : "—"}</td>
                  <td className="px-3 py-2 text-right tabular-nums">{x.rxAvgMbps != null ? `${Number(x.rxAvgMbps).toFixed(1)}` : "—"}</td>
                  <td className="px-3 py-2 text-right tabular-nums">{x.pingRttMs != null ? `${x.pingRttMs} мс` : "—"}</td>
                  <td className="px-3 py-2 text-right tabular-nums">{x.pingLossPct != null ? `${x.pingLossPct}%` : "—"}</td>
                  <td className="px-3 py-2">
                    {x.ok ? (
                      <Badge variant="outline" className="text-[10px] border-emerald-500/40 text-emerald-700 dark:text-emerald-400">ok</Badge>
                    ) : (
                      <Badge variant="outline" className="text-[10px] border-destructive/50 text-destructive">ошибка</Badge>
                    )}
                  </td>
                  <td className="px-3 py-2 max-w-[200px]">
                    <span className="text-destructive block truncate" title={x.error}>{x.error ?? ""}</span>
                    {x.pingError ? (
                      <span className="text-[10px] text-amber-600 dark:text-amber-400 block truncate" title={x.pingError ?? ""}>ping: {x.pingError}</span>
                    ) : null}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>
    )
  }
  if (snap.job === "gre_bgp") {
    const g = snap as GreBgpSnapshotRunSnapshot
    return (
      <div className="space-y-3">
        {g.skipped ? (
          <p className="text-xs text-amber-600 dark:text-amber-400">Прогон пропущен: предыдущий сбор GRE/BGP ещё выполнялся.</p>
        ) : null}
        {g.fatalError ? (
          <Alert variant="destructive" className="py-2">
            <AlertCircleIcon />
            <AlertDescription className="text-xs">Критическая ошибка: {g.fatalError}</AlertDescription>
          </Alert>
        ) : null}
        <p className="text-xs text-muted-foreground">
          Запись в SQLite на{" "}
          <span className="font-mono tabular-nums">{new Date(g.sampledAt).toLocaleString("ru-RU")}</span>
          {" "}— строки GRE и BGP для движка оповещений.
        </p>
        <dl className="grid grid-cols-2 gap-3 text-xs">
          <div>
            <dt className="text-muted-foreground">Строк GRE</dt>
            <dd className="font-mono font-medium">{g.greWritten}</dd>
          </div>
          <div>
            <dt className="text-muted-foreground">Строк BGP</dt>
            <dd className="font-mono font-medium">{g.bgpWritten}</dd>
          </div>
        </dl>
        {g.errors?.length ? (
          <div className="rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-xs text-amber-800 dark:text-amber-200 space-y-1">
            {g.errors.map((e, i) => (
              <p key={i} className="break-words">
                {e}
              </p>
            ))}
          </div>
        ) : null}
      </div>
    )
  }
  if (snap.job === "internet_path") {
    const p = snap as InternetPathRunSnapshot
    return (
      <div className="space-y-3">
        <p className="text-xs text-muted-foreground">
          Снимок internet-path на{" "}
          <span className="font-mono tabular-nums">{new Date(p.sampledAt).toLocaleString("ru-RU")}</span>
        </p>
        <dl className="grid grid-cols-2 gap-3 text-xs">
          <div>
            <dt className="text-muted-foreground">Home routers</dt>
            <dd className="font-mono font-medium">{p.homes}</dd>
          </div>
          <div>
            <dt className="text-muted-foreground">Сохранение snapshot</dt>
            <dd className="font-mono font-medium">{p.snapshotSaved ? "ok" : "no"}</dd>
          </div>
        </dl>
        {p.fatalError ? (
          <Alert variant="destructive" className="py-2">
            <AlertCircleIcon />
            <AlertDescription className="text-xs">Критическая ошибка: {p.fatalError}</AlertDescription>
          </Alert>
        ) : null}
      </div>
    )
  }
  if (snap.job === "alert_engine") {
    const a = snap as AlertEngineRunSnapshot
    const transitionRu = (t: AlertEngineRuleDiagSnapshot["hitTransition"]) => {
      switch (t) {
        case "problem":
          return "проблема"
        case "recovery":
          return "восстановление"
        case "neutral":
          return "нейтрально"
        default:
          return "—"
      }
    }
    const blockedRu = (b: AlertEngineRuleDiagSnapshot["blocked"]) => {
      switch (b) {
        case "no_hit":
          return "условие не выполнено"
        case "stability":
          return "стабильность (confirmStabilitySec)"
        case "cooldown":
          return "cooldown"
        case "no_telegram":
          return "нет Telegram"
        case "dedupe_positive":
          return "дедуп восстановления"
        case "in_group":
          return "в группе (отдельно не шлём)"
        default:
          return "—"
      }
    }
    return (
      <div className="space-y-3">
        <p className="text-xs text-muted-foreground">
          Снимок на{" "}
          <span className="font-mono tabular-nums">{new Date(a.sampledAt).toLocaleString("ru-RU")}</span>
          . Данные для правил берутся из SQLite после джоб сбора (трафик, uptime, REST, GRE+BGP и т.д.).
        </p>
        <dl className="grid grid-cols-2 sm:grid-cols-4 gap-3 text-xs">
          <div>
            <dt className="text-muted-foreground">Правил проверено</dt>
            <dd className="font-mono font-medium">{a.rulesChecked}</dd>
          </div>
          <div>
            <dt className="text-muted-foreground">Отправок (правила)</dt>
            <dd className="font-mono font-medium">{a.standaloneFires}</dd>
          </div>
          <div>
            <dt className="text-muted-foreground">Отправок (группы)</dt>
            <dd className="font-mono font-medium">{a.groupFires}</dd>
          </div>
          <div>
            <dt className="text-muted-foreground">Без Telegram</dt>
            <dd className="font-mono font-medium">{a.skippedNoTelegram ? "да" : "нет"}</dd>
          </div>
        </dl>
        {a.errors?.length ? (
          <Alert variant="destructive" className="py-2">
            <AlertCircleIcon />
            <AlertDescription className="space-y-1 text-xs">
              {a.errors.map((e, i) => (
                <p key={i} className="break-words">
                  {e}
                </p>
              ))}
            </AlertDescription>
          </Alert>
        ) : null}
        {a.ruleDiag && a.ruleDiag.length > 0 ? (
          <div className="rounded-md border border-border bg-muted/20 px-3 py-2 space-y-2">
            <p className="text-[11px] font-medium text-muted-foreground">По правилам (почему не ушло в Telegram)</p>
            <div className="overflow-x-auto">
              <table className="w-full text-[11px] border-collapse">
                <thead>
                  <tr className="text-left text-muted-foreground border-b border-border">
                    <th className="py-1 pr-2 font-medium">ID правила</th>
                    <th className="py-1 pr-2 font-medium">Сработало</th>
                    <th className="py-1 pr-2 font-medium">Тип срабатывания</th>
                    <th className="py-1 pr-2 font-medium">Стабильность</th>
                    <th className="py-1 pr-2 font-medium">Кулдаун</th>
                    <th className="py-1 pr-2 font-medium">Telegram</th>
                    <th className="py-1 pr-2 font-medium">Сообщение</th>
                    <th className="py-1 font-medium">Причина блока</th>
                  </tr>
                </thead>
                <tbody>
                  {a.ruleDiag.map((d) => (
                    <tr key={d.ruleId} className="border-b border-border/60 font-mono">
                      <td className="py-1 pr-2 max-w-[140px] truncate" title={d.ruleId}>
                        {d.ruleId}
                      </td>
                      <td className="py-1 pr-2">{d.evalHit ? "Да" : "Нет"}</td>
                      <td className="py-1 pr-2">{transitionRu(d.hitTransition)}</td>
                      <td className="py-1 pr-2">{d.stabilityOk ? "Да" : "Нет"}</td>
                      <td className="py-1 pr-2">{d.cooldownOk ? "Да" : "Нет"}</td>
                      <td className="py-1 pr-2">{d.telegramOk ? "Да" : "Нет"}</td>
                      <td className="py-1 pr-2 max-w-[280px] truncate text-muted-foreground" title={d.hitMessage ?? ""}>
                        {d.hitMessage ?? "—"}
                      </td>
                      <td className="py-1 text-muted-foreground">{blockedRu(d.blocked)}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          </div>
        ) : null}
      </div>
    )
  }
  return null
}

function RunRowDetail({ r }: { r: SchedulerRunRowDto }) {
  const jobTitle = SCHEDULER_JOB_LABELS[r.jobKey] ?? r.jobKey
  const jobDesc = SCHEDULER_JOB_DESCRIPTIONS[r.jobKey] ?? "—"
  const snapshot = useMemo(() => parseSchedulerRunSnapshot(r.resultJson ?? null), [r.resultJson])
  return (
    <div className="space-y-4 text-sm">
      <dl className="grid grid-cols-1 sm:grid-cols-2 gap-x-8 gap-y-3">
        <div className="space-y-1">
          <dt className="text-xs font-medium text-muted-foreground uppercase tracking-wide">ID записи</dt>
          <dd className="font-mono text-xs break-all bg-muted/60 rounded-md px-2 py-1.5 border border-border">{r.id}</dd>
        </div>
        <div className="space-y-1">
          <dt className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Ключ задачи</dt>
          <dd className="font-mono text-xs">{r.jobKey}</dd>
        </div>
        <div className="sm:col-span-2 space-y-1">
          <dt className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Название и назначение</dt>
          <dd>
            <span className="font-medium">{jobTitle}</span>
            <p className="text-xs text-muted-foreground mt-1 leading-relaxed">{jobDesc}</p>
          </dd>
        </div>
        <div className="space-y-1">
          <dt className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Старт</dt>
          <dd className="tabular-nums text-xs">{new Date(r.startedAt).toLocaleString("ru-RU")}</dd>
        </div>
        <div className="space-y-1">
          <dt className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Завершение</dt>
          <dd className="tabular-nums text-xs">{new Date(r.finishedAt).toLocaleString("ru-RU")}</dd>
        </div>
        <div className="space-y-1">
          <dt className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Длительность</dt>
          <dd className="tabular-nums">
            <span className="font-mono">{r.durationMs}</span> мс
            <span className="text-muted-foreground text-xs ml-2">({fmtMs(r.durationMs)})</span>
          </dd>
        </div>
        <div className="space-y-1">
          <dt className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Результат</dt>
          <dd>
            <Badge
              variant="outline"
              className={cn(
                "text-[10px]",
                r.status === "ok" && "border-emerald-500/40 text-emerald-700 dark:text-emerald-400",
                r.status === "error" && "border-destructive/50 text-destructive",
              )}
            >
              {r.status}
            </Badge>
          </dd>
        </div>
      </dl>
      {r.error ? (
        <div className="space-y-1.5">
          <p className="text-xs font-medium text-destructive">Текст ошибки</p>
          <pre
            className="text-xs font-mono whitespace-pre-wrap break-words rounded-md border border-destructive/30 bg-destructive/5 px-3 py-2 max-h-48 overflow-y-auto"
          >
            {r.error}
          </pre>
        </div>
      ) : (
        <p className="text-xs text-muted-foreground">Ошибка не записывалась (статус ok).</p>
      )}

      <Separator />

      <div className="space-y-2">
        <p className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Результаты измерений</p>
        {snapshot ? (
          <SnapshotTables snap={snapshot} />
        ) : (
          <p className="text-xs text-muted-foreground">
            Снимок отсутствует (запись до обновления бекенда или задача без детализации).
          </p>
        )}
      </div>
    </div>
  )
}

export default function DataCollectionPage() {
  const { mode, backendUrl } = useDataSource()
  const isLive = mode === "live"
  const apiFetch = useMemo(() => makeApiFetch(backendUrl), [backendUrl])

  const [trafficCollector, setTrafficCollector] = useState<CollectorSettingsDto | null>(null)
  const [serversApiCollector, setServersApiCollector] = useState<CollectorSettingsDto | null>(null)
  const [uptimeCollector, setUptimeCollector] = useState<UptimeSettingsDto | null>(null)
  const [internetPathCollector, setInternetPathCollector] = useState<CollectorSettingsDto | null>(null)
  const [trafficIntervalDraft, setTrafficIntervalDraft] = useState("30")
  const [trafficRetentionDraft, setTrafficRetentionDraft] = useState("14")
  const [uptimeResourceIntervalDraft, setUptimeResourceIntervalDraft] = useState("300")
  const [uptimeIntervalDraft, setUptimeIntervalDraft] = useState("15")
  const [uptimeSpeedIntervalDraft, setUptimeSpeedIntervalDraft] = useState("60")
  const [uptimeRetentionDraft, setUptimeRetentionDraft] = useState("14")
  const [draftTrafficEnabled, setDraftTrafficEnabled] = useState(true)
  const [draftServersApiEnabled, setDraftServersApiEnabled] = useState(false)
  const [serversApiIntervalDraft, setServersApiIntervalDraft] = useState("120")
  const [draftResourcesEnabled, setDraftResourcesEnabled] = useState(true)
  const [draftPingEnabled, setDraftPingEnabled] = useState(true)
  const [draftSpeedEnabled, setDraftSpeedEnabled] = useState(true)
  const [draftInternetPathEnabled, setDraftInternetPathEnabled] = useState(true)
  const [internetPathIntervalDraft, setInternetPathIntervalDraft] = useState("300")
  const [schedulerRuns, setSchedulerRuns] = useState<SchedulerRunRowDto[]>([])
  const [runFilterJobKey, setRunFilterJobKey] = useState<string>("")
  const [runNowJobKey, setRunNowJobKey] = useState<string | null>(null)
  const [schedulerSaveBusy, setSchedulerSaveBusy] = useState(false)
  const [pageBusy, setPageBusy] = useState(false)
  const [collectorError, setCollectorError] = useState<string | null>(null)
  const [openRunIds, setOpenRunIds] = useState<Set<string>>(() => new Set())

  const toggleRunOpen = useCallback((id: string, open: boolean) => {
    setOpenRunIds((prev) => {
      const next = new Set(prev)
      if (open) next.add(id)
      else next.delete(id)
      return next
    })
  }, [])

  const schedulerJobsByKey = useMemo(() => {
    const jobs = uptimeCollector?.scheduler?.jobs ?? []
    return Object.fromEntries(jobs.map((j) => [j.jobKey, j])) as Record<string, SchedulerJobStatusDto>
  }, [uptimeCollector?.scheduler?.jobs])

  const loadCollectors = useCallback(async () => {
    if (!isLive) return
    setCollectorError(null)
    setPageBusy(true)
    try {
      const runsQuery =
        runFilterJobKey && SCHEDULER_JOB_KEYS.includes(runFilterJobKey as (typeof SCHEDULER_JOB_KEYS)[number])
          ? `?limit=80&jobKey=${encodeURIComponent(runFilterJobKey)}`
          : "?limit=80"
      const [traffic, serversApi, uptime, internetPath, runsRes] = await Promise.all([
        apiFetch<CollectorSettingsDto>("/api/traffic/settings"),
        apiFetch<CollectorSettingsDto>("/api/servers-api-ping/settings"),
        apiFetch<UptimeSettingsDto>("/api/uptime/settings"),
        apiFetch<CollectorSettingsDto>("/api/internet-path/settings"),
        apiFetch<{ runs: SchedulerRunRowDto[] }>(`/api/scheduler/runs${runsQuery}`),
      ])
      setTrafficCollector(traffic)
      setServersApiCollector(serversApi)
      setUptimeCollector(uptime)
      setInternetPathCollector(internetPath)
      setSchedulerRuns(runsRes.runs ?? [])
      setTrafficIntervalDraft(String(traffic.intervalSec))
      setTrafficRetentionDraft(String(traffic.retentionDays))
      setUptimeResourceIntervalDraft(String(uptime.intervalSec ?? 300))
      setUptimeIntervalDraft(String(uptime.probeIntervalSec ?? 15))
      setUptimeSpeedIntervalDraft(String(uptime.speedIntervalSec ?? 60))
      setUptimeRetentionDraft(String(uptime.retentionDays))
      setDraftTrafficEnabled(!!traffic.enabled)
      setDraftServersApiEnabled(!!serversApi.enabled)
      setServersApiIntervalDraft(String(serversApi.intervalSec ?? 120))
      setDraftResourcesEnabled(!!(uptime.resourcesEnabled ?? uptime.enabled))
      setDraftPingEnabled(!!(uptime.pingEnabled ?? uptime.enabled))
      setDraftSpeedEnabled(!!(uptime.speedEnabled ?? uptime.enabled))
      setDraftInternetPathEnabled(!!internetPath.enabled)
      setInternetPathIntervalDraft(String(internetPath.intervalSec ?? 300))
    } catch (e) {
      setCollectorError(e instanceof Error ? e.message : "Не удалось загрузить данные")
    } finally {
      setPageBusy(false)
    }
  }, [apiFetch, isLive, runFilterJobKey])

  useEffect(() => {
    if (!isLive) {
      setTrafficCollector(null)
      setServersApiCollector(null)
      setUptimeCollector(null)
      setInternetPathCollector(null)
      setSchedulerRuns([])
      return
    }
    void loadCollectors()
  }, [isLive, loadCollectors])

  useEffect(() => {
    setOpenRunIds(new Set())
  }, [runFilterJobKey])

  const handleRefreshScheduler = async () => {
    if (!isLive) return
    setCollectorError(null)
    try {
      await apiFetch("/api/scheduler/refresh", { method: "POST" })
      await loadCollectors()
    } catch (e) {
      setCollectorError(e instanceof Error ? e.message : "Не удалось обновить планировщик")
    }
  }

  const enabledJobsCount = useMemo(() => {
    let n = draftTrafficEnabled ? 1 : 0
    if (draftServersApiEnabled) n += 1
    if (draftResourcesEnabled) n += 1
    if (draftPingEnabled) n += 1
    if (draftSpeedEnabled) n += 1
    if (draftInternetPathEnabled) n += 1
    return n
  }, [
    draftInternetPathEnabled,
    draftPingEnabled,
    draftResourcesEnabled,
    draftServersApiEnabled,
    draftSpeedEnabled,
    draftTrafficEnabled,
  ])

  const schedulerJobCount = SCHEDULER_JOB_KEYS.length

  const runningJobsCount = useMemo(
    () => (uptimeCollector?.scheduler?.jobs ?? []).filter((j) => j.running).length,
    [uptimeCollector?.scheduler?.jobs],
  )

  const errorRunsInView = useMemo(() => schedulerRuns.filter((r) => r.status === "error").length, [schedulerRuns])

  const stats = useMemo(
    () => [
      {
        label: "Включено задач",
        value: `${enabledJobsCount} / ${schedulerJobCount}`,
        sub: "По переключателям на этой странице (до сохранения)",
        icon: <CalendarClockIcon className="size-4 text-muted-foreground" />,
      },
      {
        label: "Сейчас выполняется",
        value: String(runningJobsCount),
        sub: "Фоновые прогоны планировщика",
        icon: <LoaderCircleIcon className="size-4 text-amber-500" />,
      },
      {
        label: "Трафик — последний сбор",
        value: trafficCollector?.lastCollectedAt
          ? new Date(trafficCollector.lastCollectedAt).toLocaleString("ru-RU", { hour: "2-digit", minute: "2-digit", day: "2-digit", month: "2-digit" })
          : "—",
        sub: trafficCollector?.lastError ? trafficCollector.lastError : trafficCollector?.lastDurationMs != null ? `${trafficCollector.lastDurationMs} мс` : "нет данных",
        icon: trafficCollector?.lastError ? (
          <XCircleIcon className="size-4 text-destructive" />
        ) : (
          <CheckCircleIcon className="size-4 text-emerald-500" />
        ),
      },
      {
        label: "Журнал (в списке)",
        value: String(schedulerRuns.length),
        sub: errorRunsInView ? `${errorRunsInView} с ошибкой` : "ошибок в показанных — нет",
        icon: <DatabaseIcon className="size-4 text-sky-500" />,
      },
    ],
    [
      enabledJobsCount,
      schedulerJobCount,
      runningJobsCount,
      trafficCollector?.lastCollectedAt,
      trafficCollector?.lastDurationMs,
      trafficCollector?.lastError,
      schedulerRuns.length,
      errorRunsInView,
    ],
  )

  return (
    <div className="flex flex-col h-full">
      <PageHeader
        crumbs={[{ label: "Система" }, { label: "Сбор данных" }]}
        actions={
          <div className="flex flex-wrap items-center gap-2">
            <Link href="/settings" className={cn(buttonVariants({ variant: "outline", size: "sm" }), "h-7 inline-flex items-center")}>
              Настройки
            </Link>
            <Button
              variant="outline"
              size="sm"
              disabled={!isLive || pageBusy}
              onClick={() => { void loadCollectors() }}
            >
              <RefreshCwIcon className={cn("size-4", pageBusy && "animate-spin")} />
              Обновить
            </Button>
            <Button variant="outline" size="sm" disabled={!isLive || pageBusy} onClick={() => { void handleRefreshScheduler() }}>
              Перечитать таймеры
            </Button>
          </div>
        }
      />

      <div className="flex-1 overflow-y-auto p-6">
        <div className="flex flex-col gap-5 max-w-[1100px] mx-auto w-full">
          {!isLive && (
            <Card>
              <CardHeader>
                <CardTitle className="text-base">Нужен live-режим</CardTitle>
                <CardDescription className="text-xs">
                  Планировщик и журнал читаются только с бекенда. Включите «Живые» данные и проверьте URL бекенда в настройках.
                </CardDescription>
              </CardHeader>
              <CardContent className="px-5 pb-5">
                <Link href="/settings" className={cn(buttonVariants({ variant: "default", size: "sm" }), "h-8")}>
                  Открыть настройки
                </Link>
              </CardContent>
            </Card>
          )}

          {isLive && collectorError && (
            <Alert variant="destructive">
              <AlertCircleIcon />
              <AlertTitle>Ошибка загрузки статуса коллекторов</AlertTitle>
              <AlertDescription>{collectorError}</AlertDescription>
            </Alert>
          )}

          {isLive && (
            <>
              <div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
                {stats.map((s) => (
                  <Card key={s.label}>
                    <CardContent className="px-5 py-4 flex items-start justify-between gap-3">
                      <div className="min-w-0">
                        <p className="text-sm text-muted-foreground">{s.label}</p>
                        <p className="text-xl font-semibold tabular-nums mt-0.5 truncate">{s.value}</p>
                        <p className="text-[11px] text-muted-foreground mt-1 leading-snug line-clamp-2">{s.sub}</p>
                      </div>
                      <div className="shrink-0 mt-0.5">{s.icon}</div>
                    </CardContent>
                  </Card>
                ))}
              </div>

              <Card>
                <CardHeader className="border-b border-border pb-4">
                  <CardTitle className="text-base">Планировщик сбора данных</CardTitle>
                  <CardDescription className="text-xs">
                    Интервалы и вкл/выкл по задачам. Сохранение отправляет настройки на бекенд и перезапускает таймеры.
                  </CardDescription>
                </CardHeader>
                <CardContent className="px-0 pb-0">
                  <div className="overflow-x-auto">
                    <table className="w-full text-sm">
                      <thead>
                        <tr className="border-b border-border text-xs text-muted-foreground">
                          <th className="text-left font-medium px-5 py-3">Задача</th>
                          <th className="text-center font-medium px-3 py-3 w-[1%]">Вкл</th>
                          <th className="text-left font-medium px-4 py-3">Интервал (с)</th>
                          <th className="text-left font-medium px-4 py-3">Последний прогон</th>
                          <th className="text-left font-medium px-4 py-3">Статус</th>
                          <th className="text-right font-medium px-4 py-3 w-[1%]">Сейчас</th>
                        </tr>
                      </thead>
                      <tbody className="divide-y divide-border">
                        {SCHEDULER_JOB_KEYS.map((jobKey) => {
                          const j = schedulerJobsByKey[jobKey]
                          const fixedSchedule = jobKey === "gre_bgp" || jobKey === "alert_engine"
                          const en = fixedSchedule
                            ? Boolean(j?.enabled ?? true)
                            : jobKey === "traffic"
                              ? draftTrafficEnabled
                              : jobKey === "servers_rest_ping"
                                ? draftServersApiEnabled
                            : jobKey === "uptime_resources"
                                  ? draftResourcesEnabled
                                  : jobKey === "uptime_ping"
                                    ? draftPingEnabled
                                : jobKey === "uptime_speed"
                                  ? draftSpeedEnabled
                                  : draftInternetPathEnabled
                          const iv = fixedSchedule
                            ? String(j?.intervalSec ?? (jobKey === "gre_bgp" ? 30 : 20))
                            : jobKey === "traffic"
                              ? trafficIntervalDraft
                              : jobKey === "servers_rest_ping"
                                ? serversApiIntervalDraft
                              : jobKey === "uptime_resources"
                                  ? uptimeResourceIntervalDraft
                                  : jobKey === "uptime_ping"
                                    ? uptimeIntervalDraft
                                  : jobKey === "uptime_speed"
                                    ? uptimeSpeedIntervalDraft
                                    : internetPathIntervalDraft
                          const setIv = fixedSchedule
                            ? () => {}
                            : jobKey === "traffic"
                              ? setTrafficIntervalDraft
                              : jobKey === "servers_rest_ping"
                                ? setServersApiIntervalDraft
                              : jobKey === "uptime_resources"
                                  ? setUptimeResourceIntervalDraft
                                  : jobKey === "uptime_ping"
                                    ? setUptimeIntervalDraft
                                  : jobKey === "uptime_speed"
                                    ? setUptimeSpeedIntervalDraft
                                    : setInternetPathIntervalDraft
                          const defSec = fixedSchedule
                            ? Number(j?.intervalSec ?? (jobKey === "gre_bgp" ? 30 : 20))
                            : jobKey === "traffic"
                              ? 30
                              : jobKey === "servers_rest_ping"
                                ? 120
                              : jobKey === "uptime_resources"
                                  ? 300
                                  : jobKey === "uptime_ping"
                                    ? 15
                                  : jobKey === "uptime_speed"
                                    ? 60
                                    : 300
                          return (
                            <tr key={jobKey} className="hover:bg-muted/40">
                              <td className="px-5 py-3 align-top">
                                <span className="font-medium">{SCHEDULER_JOB_LABELS[jobKey] ?? jobKey}</span>
                                <p className="text-[11px] text-muted-foreground mt-0.5 leading-snug">
                                  {SCHEDULER_JOB_DESCRIPTIONS[jobKey]}
                                </p>
                                <p className="text-[11px] text-muted-foreground font-mono mt-1">{jobKey}</p>
                              </td>
                              <td className="px-3 py-3 text-center align-top">
                                <span className={fixedSchedule ? "inline-flex pointer-events-none opacity-50" : "inline-flex"}>
                                  <Toggle
                                    checked={en}
                                    onChange={(v) => {
                                      if (fixedSchedule) return
                                      if (jobKey === "traffic") setDraftTrafficEnabled(v)
                                      else if (jobKey === "servers_rest_ping") setDraftServersApiEnabled(v)
                                      else if (jobKey === "uptime_resources") setDraftResourcesEnabled(v)
                                      else if (jobKey === "uptime_ping") setDraftPingEnabled(v)
                                      else if (jobKey === "uptime_speed") setDraftSpeedEnabled(v)
                                      else setDraftInternetPathEnabled(v)
                                    }}
                                  />
                                </span>
                              </td>
                              <td className="px-4 py-3 w-28 align-top">
                                <Input
                                  value={iv}
                                  onChange={(e) => setIv(e.target.value)}
                                  className="h-8 text-sm tabular-nums"
                                  inputMode="numeric"
                                  readOnly={fixedSchedule}
                                  disabled={!en && !fixedSchedule}
                                  placeholder={String(defSec)}
                                />
                              </td>
                              <td className="px-4 py-3 text-xs text-muted-foreground align-top">
                                {j?.lastFinishedAt ? new Date(j.lastFinishedAt).toLocaleString("ru-RU") : "—"}
                                {j?.lastDurationMs != null && (
                                  <span className="block text-[11px]">{j.lastDurationMs} мс</span>
                                )}
                              </td>
                              <td className="px-4 py-3 align-top">
                                <div className="flex flex-wrap items-center gap-1.5">
                                  {j?.running ? (
                                    <Badge variant="secondary" className="text-[10px]">
                                      выполняется
                                    </Badge>
                                  ) : null}
                                  {j?.lastStatus ? (
                                    <Badge
                                      variant="outline"
                                      className={cn(
                                        "text-[10px]",
                                        j.lastStatus === "ok" && "border-emerald-500/40 text-emerald-700 dark:text-emerald-400",
                                        j.lastStatus === "error" && "border-destructive/50 text-destructive",
                                      )}
                                    >
                                      {j.lastStatus}
                                    </Badge>
                                  ) : null}
                                  {j?.lastError ? (
                                    <span
                                      className="text-[10px] text-destructive max-w-[200px] truncate block"
                                      title={j.lastError}
                                    >
                                      {j.lastError}
                                    </span>
                                  ) : null}
                                </div>
                              </td>
                              <td className="px-4 py-3 text-right align-top">
                                <Button
                                  size="sm"
                                  variant="outline"
                                  className="h-8"
                                  disabled={j?.running || runNowJobKey !== null}
                                  onClick={async () => {
                                    setRunNowJobKey(jobKey)
                                    setCollectorError(null)
                                    try {
                                      await apiFetch(`/api/scheduler/jobs/${encodeURIComponent(jobKey)}/run-now`, {
                                        method: "POST",
                                      })
                                      await loadCollectors()
                                    } catch (e) {
                                      setCollectorError(e instanceof Error ? e.message : "Ошибка запуска")
                                    } finally {
                                      setRunNowJobKey(null)
                                    }
                                  }}
                                >
                                  <RefreshCwIcon className={cn("size-3.5", runNowJobKey === jobKey && "animate-spin")} />
                                </Button>
                              </td>
                            </tr>
                          )
                        })}
                      </tbody>
                    </table>
                  </div>
                  <Separator />
                  <div className="space-y-3 px-5 py-4">
                    <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
                      <div>
                        <p className="text-xs text-muted-foreground mb-1.5">Хранение сэмплов трафика (дней)</p>
                        <Input
                          value={trafficRetentionDraft}
                          onChange={(e) => setTrafficRetentionDraft(e.target.value)}
                          className="h-8 text-sm"
                          inputMode="numeric"
                        />
                      </div>
                      <div>
                        <p className="text-xs text-muted-foreground mb-1.5">Хранение сэмплов uptime (дней)</p>
                        <Input
                          value={uptimeRetentionDraft}
                          onChange={(e) => setUptimeRetentionDraft(e.target.value)}
                          className="h-8 text-sm"
                          inputMode="numeric"
                        />
                      </div>
                    </div>
                    <p className="text-[11px] text-muted-foreground leading-snug">
                      Для ping-проб у отдельных записей в мониторинге можно задать свой интервал (0 = глобальный «Интервал ping» в таблице).
                    </p>
                    <Button
                      size="sm"
                      disabled={schedulerSaveBusy}
                      onClick={async () => {
                        setSchedulerSaveBusy(true)
                        setCollectorError(null)
                        try {
                          const tInt = Math.max(5, Number.parseInt(trafficIntervalDraft, 10) || 30)
                          const tRet = Math.max(1, Number.parseInt(trafficRetentionDraft, 10) || 14)
                          const uRes = Math.max(5, Number.parseInt(uptimeResourceIntervalDraft, 10) || 300)
                          const uPing = Math.max(5, Number.parseInt(uptimeIntervalDraft, 10) || 15)
                          const uSpd = Math.max(10, Number.parseInt(uptimeSpeedIntervalDraft, 10) || 60)
                          const uRet = Math.max(1, Number.parseInt(uptimeRetentionDraft, 10) || 14)
                          const sApiInt = Math.max(10, Number.parseInt(serversApiIntervalDraft, 10) || 120)
                          const ipInt = Math.max(30, Number.parseInt(internetPathIntervalDraft, 10) || 300)
                          await apiFetch("/api/traffic/settings", {
                            method: "PUT",
                            body: JSON.stringify({
                              enabled: draftTrafficEnabled,
                              intervalSec: tInt,
                              retentionDays: tRet,
                            }),
                          })
                          await apiFetch("/api/servers-api-ping/settings", {
                            method: "PUT",
                            body: JSON.stringify({
                              enabled: draftServersApiEnabled,
                              intervalSec: sApiInt,
                            }),
                          })
                          await apiFetch("/api/uptime/settings", {
                            method: "PUT",
                            body: JSON.stringify({
                              resourcesEnabled: draftResourcesEnabled,
                              pingEnabled: draftPingEnabled,
                              speedEnabled: draftSpeedEnabled,
                              intervalSec: uRes,
                              probeIntervalSec: uPing,
                              speedIntervalSec: uSpd,
                              retentionDays: uRet,
                            }),
                          })
                          await apiFetch("/api/internet-path/settings", {
                            method: "PUT",
                            body: JSON.stringify({
                              enabled: draftInternetPathEnabled,
                              intervalSec: ipInt,
                            }),
                          })
                          await loadCollectors()
                        } catch (e) {
                          setCollectorError(e instanceof Error ? e.message : "Не удалось сохранить")
                        } finally {
                          setSchedulerSaveBusy(false)
                        }
                      }}
                    >
                      {schedulerSaveBusy ? <LoaderCircleIcon className="size-4 animate-spin mr-2" /> : null}
                      Сохранить настройки планировщика
                    </Button>
                    <div className="text-xs text-muted-foreground space-y-0.5 pt-1 border-t border-border/60">
                      <p>
                        Трафик — последний сбор:{" "}
                        {trafficCollector?.lastCollectedAt
                          ? new Date(trafficCollector.lastCollectedAt).toLocaleString("ru-RU")
                          : "—"}{" "}
                        · {trafficCollector?.lastDurationMs != null ? `${trafficCollector.lastDurationMs} мс` : "—"}
                        {trafficCollector?.collectorRunning ? (
                          <Badge variant="secondary" className="ml-2 text-[10px]">
                            коллектор занят
                          </Badge>
                        ) : null}
                      </p>
                      <p className={cn(trafficCollector?.lastError ? "text-destructive" : "")}>
                        {trafficCollector?.lastError ? `Трафик: ${trafficCollector.lastError}` : "Трафик: ошибок нет"}
                      </p>
                      <p>
                        REST API (серверы):{" "}
                        {serversApiCollector?.lastCollectedAt
                          ? new Date(serversApiCollector.lastCollectedAt).toLocaleString("ru-RU")
                          : "—"}{" "}
                        · {serversApiCollector?.lastDurationMs != null ? `${serversApiCollector.lastDurationMs} мс` : "—"}
                        {serversApiCollector?.collectorRunning ? (
                          <Badge variant="secondary" className="ml-2 text-[10px]">
                            проверка занята
                          </Badge>
                        ) : null}
                      </p>
                      <p className={cn(serversApiCollector?.lastError ? "text-destructive" : "")}>
                        {serversApiCollector?.lastError
                          ? `REST API (серверы): ${serversApiCollector.lastError}`
                          : "REST API (серверы): ошибок нет"}
                      </p>
                      <p>
                        Uptime (агрегат в настройках):{" "}
                        {uptimeCollector?.lastCollectedAt
                          ? new Date(uptimeCollector.lastCollectedAt).toLocaleString("ru-RU")
                          : "—"}{" "}
                        · {uptimeCollector?.lastDurationMs != null ? `${uptimeCollector.lastDurationMs} мс` : "—"}
                      </p>
                      <p className={cn(uptimeCollector?.lastError ? "text-destructive" : "")}>
                        {uptimeCollector?.lastError ? `Uptime: ${uptimeCollector.lastError}` : "Uptime: ошибок нет"}
                      </p>
                      <p>
                        Internet Path snapshot:{" "}
                        {internetPathCollector?.lastCollectedAt
                          ? new Date(internetPathCollector.lastCollectedAt).toLocaleString("ru-RU")
                          : "—"}{" "}
                        · {internetPathCollector?.lastDurationMs != null ? `${internetPathCollector.lastDurationMs} мс` : "—"}
                      </p>
                      <p className={cn(internetPathCollector?.lastError ? "text-destructive" : "")}>
                        {internetPathCollector?.lastError
                          ? `Internet Path: ${internetPathCollector.lastError}`
                          : "Internet Path: ошибок нет"}
                      </p>
                    </div>
                  </div>
                </CardContent>
              </Card>

              <Card>
                <CardHeader className="border-b border-border flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
                  <div>
                    <CardTitle className="text-base">Журнал прогонов</CardTitle>
                    <CardDescription className="text-xs">
                      SQLite `scheduler_runs` — до 80 записей; раскройте строку для полей и текста ошибки.
                    </CardDescription>
                  </div>
                  <div className="flex items-center gap-2 shrink-0">
                    <label className="text-xs text-muted-foreground whitespace-nowrap" htmlFor="run-filter">
                      Задача
                    </label>
                    <select
                      id="run-filter"
                      className="h-8 rounded-md border border-input bg-background px-2 text-xs min-w-[180px]"
                      value={runFilterJobKey}
                      onChange={(e) => setRunFilterJobKey(e.target.value)}
                    >
                      <option value="">Все</option>
                      {SCHEDULER_JOB_KEYS.map((k) => (
                        <option key={k} value={k}>
                          {SCHEDULER_JOB_LABELS[k]}
                        </option>
                      ))}
                    </select>
                  </div>
                </CardHeader>
                <CardContent className="px-0 pb-0">
                  {schedulerRuns.length === 0 ? (
                    <p className="text-sm text-muted-foreground text-center py-10 px-4">Пока нет прогонов</p>
                  ) : (
                    <div className="divide-y divide-border">
                      {schedulerRuns.map((r) => (
                        <Collapsible
                          key={r.id}
                          open={openRunIds.has(r.id)}
                          onOpenChange={(open) => toggleRunOpen(r.id, open)}
                        >
                          <CollapsibleTrigger
                            className={cn(
                              "flex w-full items-center gap-3 px-5 py-3 text-left text-sm transition-colors",
                              "hover:bg-muted/50 outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
                            )}
                          >
                            <ChevronDownIcon
                              className={cn(
                                "size-4 shrink-0 text-muted-foreground transition-transform duration-200",
                                openRunIds.has(r.id) && "rotate-180",
                              )}
                            />
                            <div className="flex-1 min-w-0 grid grid-cols-1 sm:grid-cols-[minmax(0,1fr)_auto_auto] gap-2 sm:gap-4 items-center">
                              <div className="min-w-0">
                                <span className="font-medium">{SCHEDULER_JOB_LABELS[r.jobKey] ?? r.jobKey}</span>
                                <span className="text-xs text-muted-foreground font-mono ml-2">{r.jobKey}</span>
                              </div>
                              <span className="text-xs text-muted-foreground tabular-nums whitespace-nowrap sm:text-right">
                                {new Date(r.finishedAt).toLocaleString("ru-RU")}
                              </span>
                              <div className="flex items-center gap-2 sm:justify-end">
                                <span className="text-xs tabular-nums text-muted-foreground">{r.durationMs} мс</span>
                                <Badge
                                  variant="outline"
                                  className={cn(
                                    "text-[10px] shrink-0",
                                    r.status === "ok" && "border-emerald-500/40 text-emerald-700 dark:text-emerald-400",
                                    r.status === "error" && "border-destructive/50 text-destructive",
                                  )}
                                >
                                  {r.status}
                                </Badge>
                              </div>
                            </div>
                          </CollapsibleTrigger>
                          <CollapsibleContent>
                            <div className="px-5 pb-4 pt-0">
                              <div className="rounded-lg border border-border bg-muted/20 px-4 py-4">
                                <RunRowDetail r={r} />
                              </div>
                            </div>
                          </CollapsibleContent>
                        </Collapsible>
                      ))}
                    </div>
                  )}
                </CardContent>
              </Card>
            </>
          )}
        </div>
      </div>
    </div>
  )
}
