"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, ApiClientError } from "@/shared/api/http-client"
import {
  parseSchedulerRunSnapshot,
  type AlertEngineRuleDiagSnapshot,
  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 { 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 Toggle({
  checked,
  onChange,
  disabled,
}: {
  checked: boolean
  onChange: (v: boolean) => void
  disabled?: boolean
}) {
  return (
    <button
      type="button"
      role="switch"
      aria-checked={checked}
      disabled={disabled}
      onClick={() => onChange(!checked)}
      className={cn(
        "relative inline-flex h-5 w-9 shrink-0 rounded-full border-2 border-transparent transition-colors",
        disabled ? "cursor-not-allowed opacity-50" : "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 === "certificates_renew") {
    const c = snap as CertificatesRenewRunSnapshot
    return (
      <div className="space-y-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 space-y-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="space-y-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 space-y-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
    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>
  )
}

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 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 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">
          {!prefsHydrated && (
            <Card>
              <CardHeader>
                <CardTitle className="text-base">Загрузка настроек подключения</CardTitle>
                <CardDescription className="text-xs">
                  Читаем режим данных и адрес API из локальных настроек.
                </CardDescription>
              </CardHeader>
            </Card>
          )}

          {prefsHydrated && !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" || jobKey === "backups"
                          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
                                      : jobKey === "certificates_renew"
                                        ? draftCertRenewEnabled
                                        : 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
                                    : jobKey === "certificates_renew"
                                      ? certRenewIntervalDraft
                                      : internetPathIntervalDraft
                          const setIv = 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 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
                                    : jobKey === "certificates_renew"
                                      ? 21600
                                      : 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}
                                    disabled={fixedSchedule || schedulerSaveBusy}
                                    onChange={(v) => {
                                      if (fixedSchedule || schedulerSaveBusy) return
                                      void handleJobEnabledChange(jobKey, 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-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 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>
  )
}
