"use client"

import Link from "next/link"
import { useCallback, useEffect, useMemo, useState } from "react"
import { PageHeader } from "@/components/page-header"
import { FormToggle } from "@/components/form-kit"
import { Badge } from "@/components/reui/badge"
import { Button, buttonVariants } from "@/components/ui/button"
import { KpiStatGrid } from "@/components/reui-kit/kpi-stat-grid"
import { OpsPanel } from "@/components/ops-panel"
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, ApiClientError } from "@/shared/api/http-client"
import {
  parseSchedulerRunSnapshot,
  type AlertEngineRunSnapshot,
  type GreBgpSnapshotRunSnapshot,
  type InternetPathRunSnapshot,
  type CertificatesRenewRunSnapshot,
  type BackupsRunSnapshot,
  type PingRunSnapshot,
  type ResourcesRunSnapshot,
  type SchedulerRunSnapshot,
  type ServersRestPingRunSnapshot,
  type SpeedScheduledRunSnapshot,
  type TrafficRunSnapshot,
} from "@/lib/scheduler-run-snapshot"
import {
  AlertEngineRuleDiagGrid,
  PingSnapshotGrid,
  ResourcesSnapshotGrid,
  ServersRestPingSnapshotGrid,
  SpeedSnapshotGrid,
  TrafficSnapshotGrid,
} from "@/components/data-grids/snapshot-data-grid"
import {
  DataCollectionSchedulerDataGrid,
  type SchedulerJobGridRow,
} from "@/components/data-grids/data-collection-scheduler-data-grid"
import { DataPageCard } from "@/components/data-page-card"
import { NetflowSettingsPanel } from "@/components/traffic/netflow-settings-panel"
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 extractApiError(reason: unknown): string {
  if (reason instanceof ApiClientError) return reason.message
  if (reason instanceof Error) return reason.message
  return String(reason)
}

function readSettled<T>(result: PromiseSettledResult<T>): T | null {
  return result.status === "fulfilled" ? result.value : null
}

function collectSettledErrors(results: PromiseSettledResult<unknown>[], labels: string[]): string[] {
  const errors: string[] = []
  for (let i = 0; i < results.length; i += 1) {
    const result = results[i]
    if (result.status === "rejected") {
      errors.push(`${labels[i]}: ${extractApiError(result.reason)}`)
    }
  }
  return errors
}

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 SnapshotTables({ snap }: { snap: SchedulerRunSnapshot }) {
  if (snap.job === "traffic") {
    const t = snap as TrafficRunSnapshot
    return (
      <div className="flex flex-col gap-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>
        <DataPageCard>
          <TrafficSnapshotGrid servers={t.servers} />
        </DataPageCard>
      </div>
    )
  }
  if (snap.job === "uptime_resources") {
    const u = snap as ResourcesRunSnapshot
    return (
      <div className="flex flex-col gap-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>
        <DataPageCard>
          <ResourcesSnapshotGrid servers={u.servers} />
        </DataPageCard>
      </div>
    )
  }
  if (snap.job === "servers_rest_ping") {
    const s = snap as ServersRestPingRunSnapshot
    return (
      <div className="flex flex-col gap-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>
        <DataPageCard>
          <ServersRestPingSnapshotGrid servers={s.servers} />
        </DataPageCard>
      </div>
    )
  }
  if (snap.job === "uptime_ping") {
    const p = snap as PingRunSnapshot
    return (
      <div className="flex flex-col gap-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>
        <DataPageCard>
          <PingSnapshotGrid probes={p.probes} />
        </DataPageCard>
      </div>
    )
  }
  if (snap.job === "uptime_speed") {
    const s = snap as SpeedScheduledRunSnapshot
    return (
      <div className="flex flex-col gap-3">
        <p className="text-xs text-muted-foreground">
          Прогоны speed на <span className="font-mono tabular-nums">{new Date(s.sampledAt).toLocaleString("ru-RU")}</span> — по очереди для каждой включённой пробы
        </p>
        <DataPageCard>
          <SpeedSnapshotGrid runs={s.runs} />
        </DataPageCard>
      </div>
    )
  }
  if (snap.job === "gre_bgp") {
    const g = snap as GreBgpSnapshotRunSnapshot
    return (
      <div className="flex flex-col gap-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 flex flex-col gap-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="flex flex-col gap-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 === "certificates_renew") {
    const c = snap as CertificatesRenewRunSnapshot
    return (
      <div className="flex flex-col gap-3">
        {c.skipped ? (
          <p className="text-xs text-amber-600 dark:text-amber-400">Прогон пропущен: предыдущая проверка ещё выполнялась или задача отключена.</p>
        ) : null}
        <dl className="grid grid-cols-2 gap-3 text-xs sm:grid-cols-4">
          <div>
            <dt className="text-muted-foreground">Проверено</dt>
            <dd className="font-mono font-medium">{c.checked}</dd>
          </div>
          <div>
            <dt className="text-muted-foreground">Обновлено</dt>
            <dd className="font-mono font-medium">{c.renewed}</dd>
          </div>
          <div>
            <dt className="text-muted-foreground">Пропущено</dt>
            <dd className="font-mono font-medium">{c.skippedTargets}</dd>
          </div>
          <div>
            <dt className="text-muted-foreground">Ошибки</dt>
            <dd className="font-mono font-medium">{c.errors.length}</dd>
          </div>
        </dl>
        {c.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 flex flex-col gap-1">
            {c.errors.map((e, i) => (
              <p key={i} className="break-words">{e}</p>
            ))}
          </div>
        ) : null}
      </div>
    )
  }
  if (snap.job === "backups") {
    const b = snap as BackupsRunSnapshot
    return (
      <div className="flex flex-col gap-3">
        <dl className="grid grid-cols-2 gap-3 text-xs sm:grid-cols-4">
          <div>
            <dt className="text-muted-foreground">Слот расписания</dt>
            <dd className="font-mono font-medium">{b.due ? "да" : "нет"}</dd>
          </div>
          <div>
            <dt className="text-muted-foreground">Создано</dt>
            <dd className="font-mono font-medium">{b.created}</dd>
          </div>
          <div>
            <dt className="text-muted-foreground">Ошибки</dt>
            <dd className="font-mono font-medium">{b.failures}</dd>
          </div>
          <div>
            <dt className="text-muted-foreground">Удалено старых</dt>
            <dd className="font-mono font-medium">{b.pruned}</dd>
          </div>
        </dl>
        {b.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 flex flex-col gap-1">
            {b.errors.map((e, i) => (
              <p key={i} className="break-words">{e}</p>
            ))}
          </div>
        ) : null}
      </div>
    )
  }
  if (snap.job === "alert_engine") {
    const a = snap as AlertEngineRunSnapshot
    return (
      <div className="flex flex-col gap-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="flex flex-col gap-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 flex flex-col gap-2">
            <p className="text-[11px] font-medium text-muted-foreground">По правилам (почему не ушло в Telegram)</p>
            <DataPageCard className="border-0 shadow-none bg-transparent">
              <AlertEngineRuleDiagGrid ruleDiag={a.ruleDiag} />
            </DataPageCard>
          </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="flex flex-col gap-4 text-sm">
      <dl className="grid grid-cols-1 sm:grid-cols-2 gap-x-8 gap-y-3">
        <div className="flex flex-col gap-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="flex flex-col gap-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 flex flex-col gap-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="flex flex-col gap-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="flex flex-col gap-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="flex flex-col gap-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="flex flex-col gap-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="flex flex-col gap-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="flex flex-col gap-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>
  )
}

type SchedulerToggleOverrides = {
  trafficEnabled?: boolean
  serversApiEnabled?: boolean
  resourcesEnabled?: boolean
  pingEnabled?: boolean
  speedEnabled?: boolean
  internetPathEnabled?: boolean
  certRenewEnabled?: boolean
}

function applySchedulerJobsToDrafts(
  jobs: SchedulerJobStatusDto[],
  setters: {
    setDraftTrafficEnabled: (value: boolean) => void
    setDraftServersApiEnabled: (value: boolean) => void
    setDraftResourcesEnabled: (value: boolean) => void
    setDraftPingEnabled: (value: boolean) => void
    setDraftSpeedEnabled: (value: boolean) => void
    setDraftInternetPathEnabled: (value: boolean) => void
    setDraftCertRenewEnabled: (value: boolean) => void
  },
) {
  const byKey = Object.fromEntries(jobs.map((job) => [job.jobKey, job])) as Record<string, SchedulerJobStatusDto>
  if (byKey.traffic) setters.setDraftTrafficEnabled(!!byKey.traffic.enabled)
  if (byKey.servers_rest_ping) setters.setDraftServersApiEnabled(!!byKey.servers_rest_ping.enabled)
  if (byKey.uptime_resources) setters.setDraftResourcesEnabled(!!byKey.uptime_resources.enabled)
  if (byKey.uptime_ping) setters.setDraftPingEnabled(!!byKey.uptime_ping.enabled)
  if (byKey.uptime_speed) setters.setDraftSpeedEnabled(!!byKey.uptime_speed.enabled)
  if (byKey.internet_path) setters.setDraftInternetPathEnabled(!!byKey.internet_path.enabled)
  if (byKey.certificates_renew) setters.setDraftCertRenewEnabled(!!byKey.certificates_renew.enabled)
}

export default function DataCollectionPage() {
  const { mode, backendUrl, prefsHydrated } = useDataSource()
  const isLive = prefsHydrated && 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 [draftCertRenewEnabled, setDraftCertRenewEnabled] = useState(true)
  const [certRenewIntervalDraft, setCertRenewIntervalDraft] = useState("21600")
  const [renewBeforeDaysDraft, setRenewBeforeDaysDraft] = useState("30")
  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 [trafficRes, serversApiRes, uptimeRes, internetPathRes, certRenewRes, runsRes] = await Promise.allSettled([
        apiFetch<CollectorSettingsDto>("/api/traffic/settings"),
        apiFetch<CollectorSettingsDto>("/api/servers-api-ping/settings"),
        apiFetch<UptimeSettingsDto>("/api/uptime/settings"),
        apiFetch<CollectorSettingsDto>("/api/internet-path/settings"),
        apiFetch<{ enabled: boolean; intervalSec: number; renewBeforeDays: number }>("/api/certificates/renew-settings"),
        apiFetch<{ runs: SchedulerRunRowDto[] }>(`/api/scheduler/runs${runsQuery}`),
      ])

      const loadErrors = collectSettledErrors(
        [trafficRes, serversApiRes, uptimeRes, internetPathRes, certRenewRes, runsRes],
        ["трафик", "серверы REST API", "uptime", "internet path", "сертификаты", "журнал планировщика"],
      )
      if (loadErrors.length > 0) {
        setCollectorError(loadErrors.join("; "))
      }

      const traffic = readSettled(trafficRes)
      if (traffic) {
        setTrafficCollector(traffic)
        setTrafficIntervalDraft(String(traffic.intervalSec))
        setTrafficRetentionDraft(String(traffic.retentionDays))
        setDraftTrafficEnabled(!!traffic.enabled)
      }

      const serversApi = readSettled(serversApiRes)
      if (serversApi) {
        setServersApiCollector(serversApi)
        setDraftServersApiEnabled(!!serversApi.enabled)
        setServersApiIntervalDraft(String(serversApi.intervalSec ?? 120))
      }

      const uptime = readSettled(uptimeRes)
      if (uptime) {
        setUptimeCollector(uptime)
        setUptimeResourceIntervalDraft(String(uptime.intervalSec ?? 300))
        setUptimeIntervalDraft(String(uptime.probeIntervalSec ?? 15))
        setUptimeSpeedIntervalDraft(String(uptime.speedIntervalSec ?? 60))
        setUptimeRetentionDraft(String(uptime.retentionDays))
        setDraftResourcesEnabled(!!(uptime.resourcesEnabled ?? uptime.enabled))
        setDraftPingEnabled(!!(uptime.pingEnabled ?? uptime.enabled))
        setDraftSpeedEnabled(!!(uptime.speedEnabled ?? uptime.enabled))
        if (uptime.scheduler?.jobs?.length) {
          applySchedulerJobsToDrafts(uptime.scheduler.jobs, {
            setDraftTrafficEnabled,
            setDraftServersApiEnabled,
            setDraftResourcesEnabled,
            setDraftPingEnabled,
            setDraftSpeedEnabled,
            setDraftInternetPathEnabled,
            setDraftCertRenewEnabled,
          })
        }
      }

      const internetPath = readSettled(internetPathRes)
      if (internetPath) {
        setInternetPathCollector(internetPath)
        setDraftInternetPathEnabled(!!internetPath.enabled)
        setInternetPathIntervalDraft(String(internetPath.intervalSec ?? 300))
      }

      const certRenew = readSettled(certRenewRes)
      if (certRenew) {
        setDraftCertRenewEnabled(!!certRenew.enabled)
        setCertRenewIntervalDraft(String(certRenew.intervalSec ?? 21600))
        setRenewBeforeDaysDraft(String(certRenew.renewBeforeDays ?? 30))
      }

      const runsPayload = readSettled(runsRes)
      if (runsPayload) {
        setSchedulerRuns(runsPayload.runs ?? [])
      }
    } catch (e) {
      setCollectorError(e instanceof Error ? e.message : "Не удалось загрузить данные")
    } finally {
      setPageBusy(false)
    }
  }, [apiFetch, isLive, runFilterJobKey])

  const persistSchedulerDrafts = useCallback(async (overrides: SchedulerToggleOverrides = {}) => {
    const trafficEnabled = overrides.trafficEnabled ?? draftTrafficEnabled
    const serversApiEnabled = overrides.serversApiEnabled ?? draftServersApiEnabled
    const resourcesEnabled = overrides.resourcesEnabled ?? draftResourcesEnabled
    const pingEnabled = overrides.pingEnabled ?? draftPingEnabled
    const speedEnabled = overrides.speedEnabled ?? draftSpeedEnabled
    const internetPathEnabled = overrides.internetPathEnabled ?? draftInternetPathEnabled
    const certRenewEnabled = overrides.certRenewEnabled ?? draftCertRenewEnabled

    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)
    const certRenewInt = Math.max(300, Number.parseInt(certRenewIntervalDraft, 10) || 21600)
    const renewBeforeDays = Math.max(1, Math.min(90, Number.parseInt(renewBeforeDaysDraft, 10) || 30))

    const saveResults = await Promise.allSettled([
      apiFetch("/api/traffic/settings", {
        method: "PUT",
        body: JSON.stringify({
          enabled: trafficEnabled,
          intervalSec: tInt,
          retentionDays: tRet,
        }),
      }),
      apiFetch("/api/servers-api-ping/settings", {
        method: "PUT",
        body: JSON.stringify({
          enabled: serversApiEnabled,
          intervalSec: sApiInt,
        }),
      }),
      apiFetch("/api/uptime/settings", {
        method: "PUT",
        body: JSON.stringify({
          resourcesEnabled,
          pingEnabled,
          speedEnabled,
          intervalSec: uRes,
          probeIntervalSec: uPing,
          speedIntervalSec: uSpd,
          retentionDays: uRet,
        }),
      }),
      apiFetch("/api/internet-path/settings", {
        method: "PUT",
        body: JSON.stringify({
          enabled: internetPathEnabled,
          intervalSec: ipInt,
        }),
      }),
      apiFetch("/api/certificates/renew-settings", {
        method: "PUT",
        body: JSON.stringify({
          enabled: certRenewEnabled,
          intervalSec: certRenewInt,
          renewBeforeDays,
        }),
      }),
    ])

    const saveErrors = collectSettledErrors(
      saveResults,
      ["трафик", "серверы REST API", "uptime", "internet path", "сертификаты"],
    )
    if (saveErrors.length > 0) {
      throw new Error(`Не все настройки сохранились: ${saveErrors.join("; ")}`)
    }
  }, [
    apiFetch,
    certRenewIntervalDraft,
    draftCertRenewEnabled,
    draftInternetPathEnabled,
    draftPingEnabled,
    draftResourcesEnabled,
    draftServersApiEnabled,
    draftSpeedEnabled,
    draftTrafficEnabled,
    internetPathIntervalDraft,
    renewBeforeDaysDraft,
    serversApiIntervalDraft,
    trafficIntervalDraft,
    trafficRetentionDraft,
    uptimeIntervalDraft,
    uptimeResourceIntervalDraft,
    uptimeRetentionDraft,
    uptimeSpeedIntervalDraft,
  ])

  const handleJobEnabledChange = useCallback(async (jobKey: (typeof SCHEDULER_JOB_KEYS)[number], nextEnabled: boolean) => {
    const overrides: SchedulerToggleOverrides = {}
    if (jobKey === "traffic") {
      setDraftTrafficEnabled(nextEnabled)
      overrides.trafficEnabled = nextEnabled
    } else if (jobKey === "servers_rest_ping") {
      setDraftServersApiEnabled(nextEnabled)
      overrides.serversApiEnabled = nextEnabled
    } else if (jobKey === "uptime_resources") {
      setDraftResourcesEnabled(nextEnabled)
      overrides.resourcesEnabled = nextEnabled
    } else if (jobKey === "uptime_ping") {
      setDraftPingEnabled(nextEnabled)
      overrides.pingEnabled = nextEnabled
    } else if (jobKey === "uptime_speed") {
      setDraftSpeedEnabled(nextEnabled)
      overrides.speedEnabled = nextEnabled
    } else if (jobKey === "certificates_renew") {
      setDraftCertRenewEnabled(nextEnabled)
      overrides.certRenewEnabled = nextEnabled
    } else if (jobKey === "internet_path") {
      setDraftInternetPathEnabled(nextEnabled)
      overrides.internetPathEnabled = nextEnabled
    } else {
      return
    }

    setCollectorError(null)
    setSchedulerSaveBusy(true)
    try {
      await persistSchedulerDrafts(overrides)
      await loadCollectors()
    } catch (e) {
      setCollectorError(e instanceof Error ? e.message : "Не удалось сохранить")
    } finally {
      setSchedulerSaveBusy(false)
    }
  }, [loadCollectors, persistSchedulerDrafts])

  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 schedulerGridRows = useMemo<SchedulerJobGridRow[]>(() => {
    return SCHEDULER_JOB_KEYS.map((jobKey) => {
      const j = schedulerJobsByKey[jobKey]
      const fixedSchedule = jobKey === "gre_bgp" || jobKey === "alert_engine" || jobKey === "backups"
      const enabled = 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
                  : jobKey === "certificates_renew"
                    ? draftCertRenewEnabled
                    : draftInternetPathEnabled
      const intervalValue = 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
                  : jobKey === "certificates_renew"
                    ? certRenewIntervalDraft
                    : internetPathIntervalDraft
      const onIntervalChange = fixedSchedule
        ? () => {}
        : jobKey === "traffic"
          ? setTrafficIntervalDraft
          : jobKey === "servers_rest_ping"
            ? setServersApiIntervalDraft
            : jobKey === "uptime_resources"
              ? setUptimeResourceIntervalDraft
              : jobKey === "uptime_ping"
                ? setUptimeIntervalDraft
                : jobKey === "uptime_speed"
                  ? setUptimeSpeedIntervalDraft
                  : jobKey === "certificates_renew"
                    ? setCertRenewIntervalDraft
                    : setInternetPathIntervalDraft
      const defaultInterval = 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
                  : jobKey === "certificates_renew"
                    ? 21600
                    : 300

      return {
        id: jobKey,
        jobKey,
        label: SCHEDULER_JOB_LABELS[jobKey] ?? jobKey,
        description: SCHEDULER_JOB_DESCRIPTIONS[jobKey],
        fixedSchedule,
        enabled,
        intervalValue,
        intervalReadOnly: fixedSchedule,
        intervalDisabled: !enabled && !fixedSchedule,
        defaultInterval,
        job: j,
        onEnabledChange: fixedSchedule
          ? undefined
          : (nextEnabled) => {
              void handleJobEnabledChange(jobKey, nextEnabled)
            },
        onIntervalChange,
        onRunNow: 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)
          }
        },
        runNowLoading: runNowJobKey === jobKey,
        saveBusy: schedulerSaveBusy,
      }
    })
  }, [
    apiFetch,
    certRenewIntervalDraft,
    draftCertRenewEnabled,
    draftInternetPathEnabled,
    draftPingEnabled,
    draftResourcesEnabled,
    draftServersApiEnabled,
    draftSpeedEnabled,
    draftTrafficEnabled,
    handleJobEnabledChange,
    internetPathIntervalDraft,
    loadCollectors,
    runNowJobKey,
    schedulerJobsByKey,
    schedulerSaveBusy,
    serversApiIntervalDraft,
    trafficIntervalDraft,
    uptimeIntervalDraft,
    uptimeResourceIntervalDraft,
    uptimeSpeedIntervalDraft,
  ])

  const enabledJobsCount = useMemo(() => {
    const jobs = uptimeCollector?.scheduler?.jobs
    if (jobs?.length) return jobs.filter((job) => job.enabled).length

    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
    if (draftCertRenewEnabled) n += 1
    return n
  }, [
    draftCertRenewEnabled,
    draftInternetPathEnabled,
    draftPingEnabled,
    draftResourcesEnabled,
    draftServersApiEnabled,
    draftSpeedEnabled,
    draftTrafficEnabled,
    uptimeCollector?.scheduler?.jobs,
  ])

  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: uptimeCollector?.scheduler?.jobs?.length
          ? "По сохранённым задачам планировщика"
          : "По переключателям на этой странице",
        icon: <CalendarClockIcon className="size-4" />,
      },
      {
        label: "Сейчас выполняется",
        value: String(runningJobsCount),
        sub: "Фоновые прогоны планировщика",
        icon: <LoaderCircleIcon className="size-4" />,
      },
      {
        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" />
        ) : (
          <CheckCircleIcon className="size-4" />
        ),
      },
      {
        label: "Журнал (в списке)",
        value: String(schedulerRuns.length),
        sub: errorRunsInView ? `${errorRunsInView} с ошибкой` : "ошибок в показанных — нет",
        icon: <DatabaseIcon className="size-4" />,
      },
    ],
    [
      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">
          {!prefsHydrated && (
            <OpsPanel
              title="Загрузка настроек подключения"
              description="Читаем режим данных и адрес API из локальных настроек."
            >
              <div />
            </OpsPanel>
          )}

          {prefsHydrated && !isLive && (
            <OpsPanel
              title="Нужен live-режим"
              description="Планировщик и журнал читаются только с бекенда. Включите «Живые» данные и проверьте URL бекенда в настройках."
              contentClassName="px-5 pb-5"
            >
                <Link href="/settings" className={cn(buttonVariants({ variant: "default", size: "sm" }), "h-8")}>
                  Открыть настройки
                </Link>
            </OpsPanel>
          )}

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

          {isLive && (
            <>
              <KpiStatGrid
                aria-label="Сводка сбора данных"
                items={stats.map((s, i) => ({
                  id: `dc-${i}`,
                  label: s.label,
                  value: s.value,
                  hint: s.sub,
                  icon: s.icon,
                  iconClassName:
                    s.label === "Трафик — последний сбор" && trafficCollector?.lastError
                      ? "text-destructive"
                      : s.label === "Сейчас выполняется" && runningJobsCount > 0
                        ? "text-warning"
                        : s.label === "Журнал (в списке)" && errorRunsInView
                          ? "text-destructive"
                          : s.label === "Трафик — последний сбор"
                            ? "text-success"
                            : "text-muted-foreground",
                  variant:
                    s.label === "Трафик — последний сбор" && trafficCollector?.lastError
                      ? "destructive" as const
                      : s.label === "Журнал (в списке)" && errorRunsInView
                        ? "warning" as const
                        : "default" as const,
                }))}
              />

              <DataPageCard>
                <div className="border-b border-border px-5 py-4">
                  <p className="text-base font-medium">Планировщик сбора данных</p>
                  <p className="text-xs text-muted-foreground mt-1">
                    Интервалы и вкл/выкл по задачам. Переключатель сразу сохраняет задачу на бекенде; кнопка ниже — интервалы и срок хранения.
                  </p>
                </div>
                <DataCollectionSchedulerDataGrid rows={schedulerGridRows} />
                <Separator />
                <div className="flex flex-col gap-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-xs text-muted-foreground mb-1.5">Обновлять сертификаты за (дней)</p>
                        <Input
                          value={renewBeforeDaysDraft}
                          onChange={(e) => setRenewBeforeDaysDraft(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 {
                          await persistSchedulerDrafts()
                          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 flex flex-col gap-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>
              </DataPageCard>

              {isLive ? <NetflowSettingsPanel backendUrl={backendUrl} enabled={isLive} /> : null}

              <OpsPanel
                title="Журнал прогонов"
                description="SQLite `scheduler_runs` — до 80 записей; раскройте строку для полей и текста ошибки."
                headerRight={
                  <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>
                }
                contentClassName="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>
                  )}
              </OpsPanel>
            </>
          )}
        </div>
      </div>
    </div>
  )
}
