#!/bin/bash
# fda_check.sh — kontrola DNS záznamů pro Full Domain Alignment (SmartEmailing).
# Ověřuje MX, DKIM, click doménu a SPF/DMARC proti veřejnému resolveru i autoritativním NS.
# Vzor záznamů: https://docs.google.com/document/d/16Nx-OPpr_s6hovHocMynrgFmHo3vaxBf5_msupQSQpc/edit

set -uo pipefail
VERSION="1.7"
VERSION_DATE="2026-09-24"
DOC_URL="https://docs.google.com/document/d/16Nx-OPpr_s6hovHocMynrgFmHo3vaxBf5_msupQSQpc/edit"
UPDATE_BASE="${FDA_UPDATE_URL:-https://fda.smartemailing.net/update}"
UPDATE_FILE="fda_check.command"   # self-update stahuje .command variantu
SPF_BUDGET="${FDA_SPF_BUDGET:-40}"   # strop DNS dotazů při procházení SPF stromu (na jednu doménu)
SPF_Q=0; SPF_TRUNC=0; SPF_TOTAL=0; SPF_VIA=""

usage() {
  echo "FDA check $VERSION ($VERSION_DATE)"
  echo
  cat <<'EOF'
Použití: fda_check.sh [volby] <doména|e-mail> [další ...]

  -f, --file SOUBOR   seznam domén ze souboru (jedna na řádek, # = komentář)
  -s, --dns SERVER    veřejný resolver (výchozí 8.8.8.8, nebo proměnná FDA_DNS)
  -j, --json          výstup ve formátu JSON
  -A, --no-analytics  neposílat statistiku využití (tzv. „Dejvova analytika"; jde i přes FDA_ANALYTICS=0)
  -u, --update        stáhnout a nainstalovat novější verzi (z fda.smartemailing.net)
      --check         jen zjistit, zda je novější verze
  -h, --help          tato nápověda

Příklady:
  fda_check.sh smartemailing.cz
  fda_check.sh david@smartemailing.cz
  fda_check.sh zije.smartemailing.cz smartemailing.cz
EOF
}

# ── self-update (z vlastního serveru, HTTPS) ────────────────────────────────
ver_gt() { [ "$1" != "$2" ] && [ "$(printf '%s\n%s\n' "$1" "$2" | sort -t. -k1,1n -k2,2n -k3,3n | tail -1)" = "$1" ]; }
_remote_meta() { curl -fsS --max-time "${1:-3}" "$UPDATE_BASE/version.json" 2>/dev/null; }
_json_str() { printf '%s' "$1" | grep -o "\"$2\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" | head -1 | sed -E 's/.*:[[:space:]]*"([^"]*)".*/\1/'; }

check_update() {  # tichý nezávazný náznak (jen terminál, ne JSON)
  command -v curl >/dev/null 2>&1 || return
  local meta rv; meta="$(_remote_meta 2)"; [ -z "$meta" ] && return
  rv="$(_json_str "$meta" version)"; [ -z "$rv" ] && return
  ver_gt "$rv" "$VERSION" && \
    printf '\n  %bℹ K dispozici je novější verze %s (máte %s) – aktualizace: %s --update%b\n' \
      "$DIM" "$rv" "$VERSION" "$(basename "$0")" "$N"
}
report_update() {  # --check
  command -v curl >/dev/null 2>&1 || { echo "curl není k dispozici."; return; }
  local meta rv; meta="$(_remote_meta 5)"
  [ -z "$meta" ] && { echo "Server s aktualizacemi je nedostupný (máte verzi $VERSION)."; return; }
  rv="$(_json_str "$meta" version)"
  if   [ -z "$rv" ];               then echo "Nepodařilo se zjistit vzdálenou verzi (máte $VERSION)."
  elif ver_gt "$rv" "$VERSION";    then echo "K dispozici je verze $rv (máte $VERSION). Aktualizace: $(basename "$0") --update"
  else                                  echo "Máte aktuální verzi ($VERSION)."; fi
}
do_update() {  # --update: stáhnout, ověřit SHA-256, přepsat sám sebe
  command -v curl >/dev/null 2>&1 || { echo "curl není k dispozici – aktualizaci nelze provést."; return 1; }
  local meta rv ext sha url tmp got
  meta="$(_remote_meta 5)"; [ -z "$meta" ] && { echo "Server s aktualizacemi je nedostupný."; return 1; }
  rv="$(_json_str "$meta" version)"
  if ! ver_gt "$rv" "$VERSION"; then echo "Máte aktuální verzi ($VERSION)."; return 0; fi
  ext="${UPDATE_FILE##*.}"; sha="$(_json_str "$meta" "sha256_$ext")"; url="$UPDATE_BASE/$UPDATE_FILE"
  tmp="$(mktemp)" || return 1
  curl -fsS --max-time 20 "$url" -o "$tmp" || { echo "Stažení selhalo."; rm -f "$tmp"; return 1; }
  if [ -n "$sha" ]; then
    if   command -v shasum   >/dev/null 2>&1; then got="$(shasum -a 256 "$tmp" | awk '{print $1}')"
    elif command -v sha256sum>/dev/null 2>&1; then got="$(sha256sum "$tmp"   | awk '{print $1}')"; fi
    if [ -n "$got" ] && [ "$got" != "$sha" ]; then echo "Kontrolní součet nesouhlasí – aktualizace zrušena."; rm -f "$tmp"; return 1; fi
  fi
  head -1 "$tmp" | grep -q '^#!' || { echo "Stažený soubor nevypadá jako skript – zrušeno."; rm -f "$tmp"; return 1; }
  chmod +x "$tmp" 2>/dev/null
  mv "$tmp" "$0" 2>/dev/null || { echo "Nepodařilo se přepsat $0 (zkuste práva)."; rm -f "$tmp"; return 1; }
  echo "Aktualizováno na verzi ${rv}. Spusťte prosím znovu."
  return 0
}

DOMAINS=(); DNS="${FDA_DNS:-8.8.8.8}"; JSON=0; FILE=""; DO_UPDATE=0; DO_CHECK=0
is_ip() { [[ "$1" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]] || [[ "$1" =~ ^[0-9a-fA-F:]*:[0-9a-fA-F:]+$ ]]; }

while [ $# -gt 0 ]; do
  case "$1" in
    -f|--file) FILE="${2:-}"; shift; [ $# -gt 0 ] && shift ;;
    -s|--dns)  DNS="${2:-}"; shift; [ $# -gt 0 ] && shift ;;
    --) shift; for _rest in "$@"; do if is_ip "$_rest"; then DNS="$_rest"; else DOMAINS+=("$_rest"); fi; done; set -- ;;
    -j|--json) JSON=1; shift ;;
    -A|--no-analytics|--dejv-off) FDA_ANALYTICS=0; shift ;;
    -u|--update) DO_UPDATE=1; shift ;;
    --check|--check-update) DO_CHECK=1; shift ;;
    -V|--version) echo "FDA check $VERSION ($VERSION_DATE)"; exit 0 ;;
    -h|--help) usage; exit 0 ;;
    -*)        echo "Neznámá volba: $1"; usage; exit 1 ;;
    *)         if is_ip "$1"; then DNS="$1"; else DOMAINS+=("$1"); fi; shift ;;
  esac
done

if [ "$DO_UPDATE" -eq 1 ]; then do_update; exit $?; fi
if [ "$DO_CHECK"  -eq 1 ]; then report_update; exit 0; fi

if [ -n "$FILE" ]; then
  [ -r "$FILE" ] || { echo "Chyba: soubor '$FILE' nelze číst."; exit 1; }
  while IFS= read -r line; do
    line="$(printf '%s' "$line" | tr -d '[:space:]')"
    [ -z "$line" ] && continue
    [[ "$line" == \#* ]] && continue
    DOMAINS+=("$line")
  done < "$FILE"
fi

# bez zadané domény: v terminálu se zeptej, jinak (roura/cron) vypiš nápovědu
if [ ${#DOMAINS[@]} -eq 0 ] && [ -t 0 ]; then
  printf 'Zadejte doménu nebo e-mail (více oddělte mezerou): '
  read -r _line
  for tok in $_line; do
    if is_ip "$tok"; then DNS="$tok"; else DOMAINS+=("$tok"); fi
  done
fi
if [ ${#DOMAINS[@]} -eq 0 ]; then usage; exit 1; fi
command -v dig >/dev/null 2>&1 || { echo "Chyba: nástroj 'dig' není nainstalován (macOS: brew install bind, Debian/Ubuntu: apt install dnsutils)."; exit 1; }

# barvy (jen do terminálu, ne v JSON)
if [ "$JSON" -eq 0 ] && [ -t 1 ]; then
  G='\033[0;32m'; R='\033[0;31m'; Y='\033[0;33m'; B='\033[1m'; DIM='\033[2m'; N='\033[0m'
else
  G=''; R=''; Y=''; B=''; DIM=''; N=''
fi
SEP="──────────────────────────────────────────────────────────────────"

# dig zkratky: $1=server $2=jméno $3=typ
qshort()  { dig +short "$2" "$3" @"$1" +time=3 +tries=1 2>/dev/null; }
qstatus() { dig "$2" "$3" @"$1" +time=3 +tries=1 2>/dev/null | grep -o 'status: [A-Z]*' | head -1 | awk '{print $2}'; }
dns_reachable() { dig +time=3 +tries=1 @"$1" "${2:-.}" SOA >/dev/null 2>&1; }
record_ttl() {
  local t
  [ -z "$1" ] && return
  t=$(dig +nocmd +noall +answer @"$1" "$2" "$3" +time=3 +tries=1 2>/dev/null | awk 'NR==1{print $2}')
  [[ "$t" =~ ^[0-9]+$ ]] && printf '%s' "$t"
}
pad_to() { local s="$1" w="$2" n; n=$((w - ${#1})); [ "$n" -lt 0 ] && n=0; printf '%s%*s' "$s" "$n" ''; }
json_escape() { local s="$1"; s="${s//\\/\\\\}"; s="${s//\"/\\\"}"; s="${s//$'\n'/ }"; s="${s//$'\t'/ }"; printf '%s' "$s"; }

# anonymní statistika do Matomo (neblokující, na pozadí; vypnout: FDA_ANALYTICS=0)
MATOMO_URL="https://analytics.omnisys.cz/matomo.php"
MATOMO_SITE="3"
track() { # doména výsledek počet_chyb
  [ -z "$MATOMO_URL" ] && return 0
  case "${FDA_ANALYTICS:-1}" in 0|off|no|false|OFF|NO|FALSE) return 0 ;; esac
  command -v curl >/dev/null 2>&1 || return 0
  local host user
  host="$(hostname 2>/dev/null)"; user="$(whoami 2>/dev/null)"
  curl -s -m 3 -o /dev/null \
    --data-urlencode "idsite=$MATOMO_SITE" --data-urlencode "rec=1" --data-urlencode "send_image=0" \
    --data-urlencode "e_c=fda_check" --data-urlencode "e_a=$2" --data-urlencode "e_n=$1" --data-urlencode "e_v=$3" \
    --data-urlencode "uid=${user}@${host}" --data-urlencode "url=https://fda-check.local/check/$1" \
    "$MATOMO_URL" >/dev/null 2>&1 &
}

# autoritativní NS zóny nad zadaným jménem (mezerou oddělené)
auth_ns_all() {
  local d="$1" ns
  while :; do
    ns=$(dig +short NS "$d" @"$DNS" +time=3 +tries=1 2>/dev/null | grep -v '^$' | sed 's/\.$//')
    [ -n "$ns" ] && { printf '%s' "$ns" | tr '\n' ' '; return 0; }
    if [[ "$d" == *.*.* ]]; then d="${d#*.}"; else break; fi
  done
  return 1
}

# ověření jednoho záznamu proti serveru -> RES_STATUS (ok|err|na) + RES_ACTUAL
RES_STATUS=""; RES_ACTUAL=""
eval_record() {
  local server="$1" fqdn="$2" type="$3" expect="$4" actual status
  [ -z "$server" ] && { RES_STATUS="na"; RES_ACTUAL=""; return; }
  status=$(qstatus "$server" "$fqdn" "$type")
  actual=$(qshort "$server" "$fqdn" "$type")
  if [ "$type" = CNAME ] && [ -z "$actual" ]; then actual=$(qshort "$server" "$fqdn" A); fi
  if [ "$type" = TXT ]; then
    actual=$(printf '%s' "$actual" | tr -d '"')
    printf '%s' "$expect" | grep -qi spf && actual=$(printf '%s\n' "$actual" | grep -i 'v=spf1' | head -1)
  fi
  RES_ACTUAL="$actual"
  if [ "$status" = REFUSED ]; then RES_STATUS="err"; RES_ACTUAL="REFUSED"
  elif [ -z "$actual" ]; then RES_STATUS="err"
  elif printf '%s' "$actual" | grep -qiF "$expect"; then RES_STATUS="ok"
  else RES_STATUS="err"; fi
}

# ověření napříč všemi NS -> N_STAT (ok|err|partial|na) + N_FRAC
N_STAT=""; N_FRAC=""
eval_ns_all() {
  local srv ok=0 total=0
  for srv in $NS_LIST; do
    total=$((total+1)); eval_record "$srv" "$1" "$2" "$3"
    [ "$RES_STATUS" = ok ] && ok=$((ok+1))
  done
  if   [ "$total" -eq 0 ];     then N_STAT=na;      N_FRAC="-"
  elif [ "$ok" -eq "$total" ]; then N_STAT=ok;      N_FRAC="$ok/$total"
  elif [ "$ok" -eq 0 ];        then N_STAT=err;     N_FRAC="$ok/$total"
  else                              N_STAT=partial; N_FRAC="$ok/$total"; fi
}

# DMARC se dědí – hledá se i na nadřazených doménách
DM_STATUS=""; DM_REC=""; DM_DOM=""
dmarc_walk() {
  local server="$1" d="$DOMAIN" rec
  DM_REC=""; DM_DOM=""
  [ -z "$server" ] && { DM_STATUS="na"; return; }
  while :; do
    rec=$(qshort "$server" "_dmarc.$d" TXT | tr -d '"' | grep -i 'v=DMARC1' | head -1)
    [ -n "$rec" ] && { DM_REC="$rec"; DM_DOM="$d"; break; }
    if [[ "$d" == *.*.* ]]; then d="${d#*.}"; else break; fi
  done
  if [ -z "$DM_REC" ]; then DM_STATUS="err"; else DM_STATUS="ok"; fi
}

# klasifikace rua v DMARC záznamu -> DM_RUA_CLASS (se|other|none), DM_RUA_ADDR (adresy bez mailto:)
dmarc_rua_class() { # rec
  local rec="$1" rua
  DM_RUA_CLASS=""; DM_RUA_ADDR=""
  [ -z "$rec" ] && return
  rua=$(printf '%s' "$rec" | tr -d ' ' | grep -oiE 'rua=[^;]*' | head -1 | cut -d= -f2-)
  if [ -z "$rua" ]; then DM_RUA_CLASS="none"
  elif printf '%s' "$rua" | grep -qiE 'mailto:dmarc\+[0-9]+@([a-z0-9-]+\.)*smartemailing-mta\.com'; then DM_RUA_CLASS="se"
  elif printf '%s' "$rua" | grep -qiE 'mailto:dmarc\+[0-9]+@smartemailing\.cz'; then
    DM_RUA_CLASS="se_old"; DM_RUA_ADDR=$(printf '%s' "$rua" | grep -oiE 'dmarc\+[0-9]+@smartemailing\.cz' | head -1)
  else DM_RUA_CLASS="other"; DM_RUA_ADDR=$(printf '%s' "$rua" | sed 's/mailto://gi'); fi
}

_org2() { printf '%s' "$1" | awk -F. '{if (NF>=2) print $(NF-1)"."$NF; else print $0}'; }
# kvalita rua: duplicitní adresa, víc než 2 adresy, externí cíl bez autorizace (RFC 7489 §7.1)
rua_quality() { # rec zdrojová_doména(kde záznam leží)
  local rec="$1" src="$2" rua addrs n dup a dest dorg sorg seen checked
  rua=$(printf '%s' "$rec" | tr -d ' ' | grep -oiE 'rua=[^;]*' | head -1 | cut -d= -f2-)
  [ -z "$rua" ] && return
  addrs=$(printf '%s' "$rua" | tr '[:upper:]' '[:lower:]' | tr ',' '\n' | sed 's/^mailto://' | sed 's/!.*$//' | grep '@')
  [ -z "$addrs" ] && return
  n=$(printf '%s\n' "$addrs" | grep -c .)
  dup=$(printf '%s\n' "$addrs" | sort | uniq -d | head -1)
  if [ -n "$dup" ]; then
    WARN=$((WARN+1)); WARN_LIST+=("DMARC: adresa pro reporty je v rua uvedena vícekrát (${dup}) – na funkci to nemá vliv, stačí ji uvést jednou.")
  fi
  if [ "$n" -gt 2 ]; then
    local sklon="adres"; [ "$n" -le 4 ] && sklon="adresy"
    WARN=$((WARN+1)); WARN_LIST+=("DMARC: rua obsahuje ${n} ${sklon} – příjemci mají povinnost podporovat jen dvě, na další nemusí reporty chodit.")
  fi
  sorg=$(_org2 "$src"); seen=" "; checked=0
  for a in $(printf '%s\n' "$addrs" | sort -u); do
    dest="${a##*@}"; dorg=$(_org2 "$dest")
    [ "$dorg" = "$sorg" ] && continue
    case "$dorg" in smartemailing-mta.com|smartemailing.cz) continue ;; esac
    case "$seen" in *" $dorg "*) continue ;; esac
    seen="${seen}${dorg} "
    checked=$((checked+1)); [ "$checked" -gt 3 ] && break
    if ! qshort "$DNS" "${src}._report._dmarc.${dest}" TXT | grep -qi 'v=DMARC1'; then
      WARN=$((WARN+1)); WARN_LIST+=("DMARC: reporty na ${a} se nejspíš nedoručují – na doméně ${dest} chybí autorizační TXT záznam ${src}._report._dmarc.${dest} (v=DMARC1).")
    fi
  done
}

hput() { [ "$JSON" -eq 0 ] && echo -e "$@"; return 0; }
statuscell() {
  local kind="$1" text="${2:-}" col
  case "$kind" in
    ok)  col="$G"; text="${text:-OK}" ;;
    err) col="$R"; text="${text:-CHYBA}" ;;
    warn)col="$Y"; text="${text:-?}" ;;
    na)  col="$DIM"; text="${text:-n/a}" ;;
    *)   col="$N" ;;
  esac
  printf '%s' "${col}$(pad_to "$text" 10)${N}"
}
print_row() { # label g n frac ttl [poznámka]
  [ "$JSON" -ne 0 ] && return 0
  local g="$2" n="$3" frac="$4" ttl="$5" extra="${6:-}" note="" gcell ncell line
  gcell="$(statuscell "$g")"
  case "$n" in
    ok) ncell="$(statuscell ok)";; err) ncell="$(statuscell err)";;
    partial) ncell="$(statuscell warn "$frac")";; *) ncell="$(statuscell na)";;
  esac
  if [ "$g" = err ] && { [ "$n" = ok ] || [ "$n" = partial ]; }; then
    note="${Y}← v zóně, čeká na propagaci${ttl:+ (TTL ${ttl}s)}${N}"
  fi
  [ "$n" = partial ] && note="${Y}← nekonzistentní mezi NS ($frac)${N}"
  [ "$g" = ok ] && [ "$n" = err ] && note="${DIM}← global z keše, v zóně chybí${N}"
  line="$(pad_to "$1" 32)${gcell}${ncell}"
  [ -n "${extra}${note}" ] && line="${line}  ${extra}${note}"
  printf '  %b\n' "$line"
}

check_domain() {
  DOMAIN="$1"; DERR_MSG=""; DEXIT=0
  OK=0; ERR=0; PROP=0; WARN=0
  OK_LABELS=(); PROP_LIST=(); WARN_LIST=()
  R_LABEL=(); R_NAME=(); R_TYPE=(); R_G=(); R_N=(); R_FRAC=(); R_TTL=(); R_VAL=(); R_VIA=(); DM_RUA_CLASS=""; DM_RUA_ADDR=""; SPF_Q=0; SPF_TRUNC=0
  P_LABEL=(); P_FQDN=(); P_TYPE=(); P_EXP=(); P_WHY=(); P_FOUND=()

  # normalizace na doménu (z e-mailu, URL, portu…)
  DOMAIN="${DOMAIN##*@}"; DOMAIN="${DOMAIN#*://}"; DOMAIN="${DOMAIN%%/*}"
  DOMAIN="${DOMAIN%%:*}"; DOMAIN="${DOMAIN%.}"
  DOMAIN="$(printf '%s' "$DOMAIN" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')"
  if [ -z "$DOMAIN" ] || [[ "$DOMAIN" != *.* ]] || [[ "$DOMAIN" == -* ]]; then
    DERR_MSG="'$1' není platná doména ani e-mail"; DEXIT=1
    hput "\n  ${R}✗ ${DERR_MSG}.${N}"; finalize_domain; return
  fi

  # existuje doména a má DNS?
  local apex_status
  apex_status=$(qstatus "$DNS" "$DOMAIN" SOA)
  [ -z "$apex_status" ] && apex_status=$(qstatus "$DNS" "$DOMAIN" NS)
  case "$apex_status" in
    NOERROR) : ;;
    NXDOMAIN)
      DERR_MSG="doména neexistuje (NXDOMAIN)"; DEXIT=3
      hput "\n${B}══ ${DOMAIN} ══${N}"
      hput "  ${R}✗ Doména neexistuje (NXDOMAIN).${N} ${DIM}Zkontrolujte překlep – FDA záznamy zde nemůžou existovat.${N}"
      finalize_domain; return ;;
    "")
      DERR_MSG="doména bez funkční delegace (NS neodpovídají)"; DEXIT=3
      hput "\n${B}══ ${DOMAIN} ══${N}"
      hput "  ${Y}⚠ Doména nevrací odpověď – nemá funkční delegaci (nameservery neodpovídají).${N}"
      finalize_domain; return ;;
    *)
      hput "\n${B}══ ${DOMAIN} ══${N}"
      hput "  ${Y}⚠ DNS vrací status ${apex_status} (např. SERVFAIL) – výsledky nemusí být spolehlivé.${N}" ;;
  esac

  # NS zóny a jejich dostupnost
  local NS_ALL ns
  NS_ALL="$(auth_ns_all "$DOMAIN" || true)"
  NS_LIST=""; _nsc=0
  for ns in $NS_ALL; do
    _nsc=$((_nsc+1)); [ "$_nsc" -gt 8 ] && break
    dns_reachable "$ns" "$DOMAIN" && NS_LIST="${NS_LIST}${ns} "
  done
  NS_LIST="$(printf '%s' "$NS_LIST" | sed 's/ *$//')"
  PRIMARY_NS="${NS_LIST%% *}"; [ -z "$PRIMARY_NS" ] && PRIMARY_NS="$DNS"
  local ns_total=0; for ns in $NS_ALL; do ns_total=$((ns_total+1)); done
  local ns_reach=0; for ns in $NS_LIST; do ns_reach=$((ns_reach+1)); done
  local NS_HDR
  if [ "$ns_total" -eq 0 ]; then
    NS_HDR="${Y}nezjištěno${N}"; WARN=$((WARN+1)); WARN_LIST+=("Autoritativní NS domény se nepodařilo zjistit – sloupec NS je n/a, řídí se global.")
  elif [ "$ns_reach" -eq 0 ]; then
    NS_HDR="${Y}${NS_ALL}(žádný neodpovídá – NS n/a)${N}"; WARN=$((WARN+1)); WARN_LIST+=("Žádný autoritativní NS (${NS_ALL}) neodpovídá – sloupec NS je n/a, řídí se global.")
  else
    NS_HDR="$NS_LIST"
  fi

  apex_a=$( { qshort "$DNS" "$DOMAIN" A; qshort "$DNS" "$DOMAIN" AAAA; } | tr '\n' ' ' )
  apex_a="$(printf '%s' "$apex_a" | sed 's/[[:space:]]*$//')"
  apex_mx=$( qshort "$DNS" "$DOMAIN" MX | tr '\n' ' ' | sed 's/[[:space:]]*$//' )
  printf '%s' "$apex_mx" | grep -qE '^[[:space:]]*0[[:space:]]+\.[[:space:]]*$' && apex_mx=""   # null MX (RFC 7505)
  if [ -z "$apex_mx" ]; then
    WARN=$((WARN+1)); WARN_LIST+=("Doména nemá vlastní MX záznam – nepřijímá e-mail (bounce MX pro FDA je zvlášť na mkt-smartemailing).")
  fi

  # hlavička + tabulka
  hput "\n${B}══ FDA kontrola DNS: ${DOMAIN} ══${N} ${DIM}(v${VERSION}, ${VERSION_DATE})${N}"
  if [ -n "$apex_a" ]; then hput "  web (A/AAAA): ${apex_a}"
  else hput "  web (A/AAAA): ${Y}žádný záznam${N} ${DIM}(doména nemíří na web – pro e-mailing/FDA to nevadí)${N}"; fi
  if [ -n "$apex_mx" ]; then hput "  pošta (MX):   ${apex_mx}"
  else hput "  pošta (MX):   ${Y}žádný vlastní MX${N} ${DIM}(doména nepřijímá e-mail)${N}"; fi
  hput "  global DNS: ${DNS}    autoritativní NS: ${NS_HDR}"
  hput "${DIM}${SEP}${N}"
  [ "$JSON" -eq 0 ] && printf '  %b\n' "$(pad_to 'Záznam' 32)$(pad_to 'global' 10)$(pad_to 'NS' 10)"
  hput "${DIM}${SEP}${N}"

  local label name type expect why fqdn g gact n frac ttl via src xnote
  while IFS='|' read -r label name type expect why; do
    [ -z "${label:-}" ] && continue
    if [ "$name" = "@" ]; then fqdn="$DOMAIN"; else fqdn="${name}.${DOMAIN}"; fi
    eval_record "$DNS" "$fqdn" "$type" "$expect"; g="$RES_STATUS"; gact="$RES_ACTUAL"
    eval_ns_all "$fqdn" "$type" "$expect"; n="$N_STAT"; frac="$N_FRAC"
    via=""; xnote=""
    # náš SPF include může být autorizovaný i vnořeně (přes cizí include) – rekurzivní kontrola
    if [ "$expect" = "include:spf.smartemailing.cz" ] && { [ "$g" != ok ] || [ "$n" != ok ]; }; then
      spf_find_include "$fqdn" "spf.smartemailing.cz"; src="$SPF_VIA"
      if [ -n "$src" ] && [ "$src" != direct ]; then
        via="$src"; g=ok; n=ok; frac=""
        xnote="${G}← přes vnořený include ($src)${N}"
      fi
    fi
    ttl=""
    if [ "$g" = ok ] || [ "$n" = ok ] || [ "$n" = partial ]; then ttl="$(record_ttl "$PRIMARY_NS" "$fqdn" "$type")"; fi
    R_LABEL+=("$label"); R_NAME+=("$fqdn"); R_TYPE+=("$type"); R_G+=("$g"); R_N+=("$n"); R_FRAC+=("$frac"); R_TTL+=("$ttl"); R_VAL+=("$gact"); R_VIA+=("$via")
    print_row "$label" "$g" "$n" "$frac" "$ttl" "$xnote"
    tally "$label" "$fqdn" "$type" "$expect" "$why" "$g" "$n" "$gact" "$frac" "$ttl"
  done <<'RECORDS'
From doména (bounce / MX)|mkt-smartemailing|MX|bounce.smartemailing-mta.com|Return-Path pro bounce – na izolované subdoméně sbírá hlášení o nedoručení a zarovnává Return-Path s doménou odesílatele (FDA). Nezasahuje do firemní pošty.
DKIM klíč 1|k1-mkt-smartemailing._domainkey|CNAME|mkt-k1.dkim.smartemailing-mta.com|DKIM podpis – příjemce ověří, že e-mail pochází z autorizované domény a nebyl cestou změněn (d= zarovnané s doménou).
DKIM klíč 2|k2-mkt-smartemailing._domainkey|CNAME|mkt-k2.dkim.smartemailing-mta.com|Druhý DKIM klíč pro rotaci/zálohu – spolehlivé podepisování e-mailů.
DKIM klíč 3|k3-mkt-smartemailing._domainkey|CNAME|mkt-k3.dkim.smartemailing-mta.com|Třetí DKIM klíč pro rotaci/zálohu – spolehlivé podepisování e-mailů.
Click doména (odkazy)|lnk-smartemailing|CNAME|customers.smartemailing.cz|Odkazy v e-mailu vedou přes vaši doménu a https – vyšší důvěra příjemců, lepší měřitelnost, nižší riziko phishingu.
SPF odesílací subdomény|mkt-smartemailing|TXT|include:spf.smartemailing.cz|SPF autorizuje servery SmartEmailingu odesílat za subdoménu mkt-smartemailing.
SPF hlavní domény|@|TXT|include:spf.smartemailing.cz|SPF na hlavní doméně autorizuje odesílání a podporuje zarovnání (alignment). Pozn.: pokud doménu pro SE nepoužíváte, může být SPF vlastní.
RECORDS

  # DMARC (přes všechny NS zóny, kde záznam leží)
  dmarc_walk "$DNS"; local g_dstat="$DM_STATUS" g_drec="$DM_REC" g_ddom="$DM_DOM"
  local dmz="${g_ddom:-$DOMAIN}" DM_NS_ALL DM_NS_LIST srv d_ok=0 d_tot=0 n_dstat dfrac dm_primary ttl_dmarc
  DM_NS_ALL="$(auth_ns_all "$dmz" || true)"; DM_NS_LIST=""; _nsc=0
  for srv in $DM_NS_ALL; do
    _nsc=$((_nsc+1)); [ "$_nsc" -gt 8 ] && break
    dns_reachable "$srv" "$dmz" && DM_NS_LIST="${DM_NS_LIST}${srv} "
  done
  DM_NS_LIST="$(printf '%s' "$DM_NS_LIST" | sed 's/ *$//')"
  for srv in $DM_NS_LIST; do d_tot=$((d_tot+1)); dmarc_walk "$srv"; [ "$DM_STATUS" = ok ] && d_ok=$((d_ok+1)); done
  if   [ "$d_tot" -eq 0 ];     then n_dstat=na;      dfrac="-"
  elif [ "$d_ok" -eq "$d_tot" ];then n_dstat=ok;     dfrac="$d_ok/$d_tot"
  elif [ "$d_ok" -eq 0 ];      then n_dstat=err;     dfrac="$d_ok/$d_tot"
  else                              n_dstat=partial; dfrac="$d_ok/$d_tot"; fi
  dm_primary="${DM_NS_LIST%% *}"; [ -z "$dm_primary" ] && dm_primary="$DNS"
  ttl_dmarc=""; { [ "$g_dstat" = ok ] || [ "$n_dstat" = ok ] || [ "$n_dstat" = partial ]; } && ttl_dmarc="$(record_ttl "$dm_primary" "_dmarc.$dmz" TXT)"
  local dnote=""; [ -n "$g_ddom" ] && [ "$g_ddom" != "$DOMAIN" ] && dnote="${DIM}(zděděno z ${g_ddom})${N} "
  R_LABEL+=("DMARC"); R_NAME+=("_dmarc.$dmz"); R_TYPE+=("TXT"); R_G+=("$g_dstat"); R_N+=("$n_dstat"); R_FRAC+=("$dfrac"); R_TTL+=("$ttl_dmarc"); R_VAL+=("$g_drec"); R_VIA+=("")
  print_row "DMARC" "$g_dstat" "$n_dstat" "$dfrac" "$ttl_dmarc" "$dnote"
  dmarc_tally "$g_dstat" "$n_dstat" "$g_drec" "$g_ddom" "$dfrac" "$ttl_dmarc"

  # duplicity SPF/DMARC (na autoritativním serveru)
  dup_spf "mkt-smartemailing.$DOMAIN" "SPF odesílací subdomény" "$PRIMARY_NS"
  dup_spf "$DOMAIN" "SPF hlavní domény" "$PRIMARY_NS"
  dup_dmarc "_dmarc.$dmz" "$dm_primary"

  # SPF 10-lookup limit (PermError) – přes veřejný resolver
  spf_limit "$DOMAIN" "SPF hlavní domény"
  spf_limit "mkt-smartemailing.$DOMAIN" "SPF odesílací subdomény"

  if [ "$ERR" -gt 0 ]; then DEXIT=2; else DEXIT=0; fi
  finalize_domain
}

# započítání výsledku záznamu – hlavní kritérium je NS (co je v zóně), global jen propagace
tally() { # label fqdn type expect why g n gact frac ttl
  local label="$1" fqdn="$2" type="$3" expect="$4" why="$5" g="$6" n="$7" gact="$8" frac="$9" ttl="${10:-}"
  case "$n" in
    ok)
      if [ "$g" = ok ]; then OK=$((OK+1)); OK_LABELS+=("$label")
      else PROP=$((PROP+1)); PROP_LIST+=("$label (TTL ${ttl:-?}s)"); fi ;;
    partial)
      WARN=$((WARN+1)); WARN_LIST+=("$label: nekonzistentní mezi NS ($frac) – změna se šíří mezi nameservery.")
      if [ "$g" = ok ]; then OK=$((OK+1)); OK_LABELS+=("$label")
      else PROP=$((PROP+1)); PROP_LIST+=("$label (TTL ${ttl:-?}s)"); fi ;;
    na)
      if [ "$g" = ok ]; then OK=$((OK+1)); OK_LABELS+=("$label")
      else addprob "$label" "$fqdn" "$type" "$expect" "$why" "$gact"; fi ;;
    *)
      if [ "$g" = ok ]; then WARN=$((WARN+1)); WARN_LIST+=("$label: v zóně (autoritativně) chybí, veřejně ještě žije z keše – ověřte, že nebyl omylem smazán.")
      else addprob "$label" "$fqdn" "$type" "$expect" "$why" "$gact"; fi ;;
  esac
}

dmarc_tally() { # g_dstat n_dstat g_drec g_ddom dfrac ttl
  local g="$1" n="$2" rec="$3" dom="$4" frac="$5" ttl="$6" pol
  policy_warn() {
    pol=$(printf '%s' "$rec" | grep -oiE 'p=[a-z]+' | head -1 | cut -d= -f2 | tr '[:upper:]' '[:lower:]')
    if [ -n "$rec" ] && { [ "$pol" = none ] || [ -z "$pol" ]; }; then
      WARN=$((WARN+1)); WARN_LIST+=("DMARC policy p=${pol:-nezadáno} – doporučeno p=reject nebo p=quarantine.")
    fi
  }
  rua_warn() {  # statistiky v aplikaci jen když rua míří (i) na SmartEmailing
    dmarc_rua_class "$rec"
    case "$DM_RUA_CLASS" in
      se_old) WARN=$((WARN+1)); WARN_LIST+=("DMARC používá starší adresu pro reporty (${DM_RUA_ADDR}) – vše funguje, ale nový tvar je dmarc+ID@dmarc.smartemailing-mta.com.") ;;
      other)  WARN=$((WARN+1)); WARN_LIST+=("DMARC je v pořádku, ale reporty jdou jinam (${DM_RUA_ADDR}) – statistiky v aplikaci SmartEmailing nebudou pro tuto doménu dostupné.") ;;
      none)   WARN=$((WARN+1)); WARN_LIST+=("DMARC je v pořádku, ale nemá rua – reporty se neposílají nikam a statistiky v aplikaci SmartEmailing nebudou dostupné.") ;;
    esac
    rua_quality "$rec" "${dom:-$DOMAIN}"
  }
  problem() {
    addprob "DMARC" "_dmarc.${DOMAIN}" TXT "v=DMARC1; p=reject; sp=reject; rua=mailto:dmarc+ID@dmarc.smartemailing-mta.com" \
      "DMARC řídí kontrolu shody (alignment) a kam jdou reporty. Záznam se dědí – hledáno i na nadřazených doménách." ""
  }
  case "$n" in
    ok)      if [ "$g" = ok ]; then OK=$((OK+1)); OK_LABELS+=("DMARC"); policy_warn; rua_warn; else PROP=$((PROP+1)); PROP_LIST+=("DMARC (TTL ${ttl:-?}s)"); fi ;;
    partial) WARN=$((WARN+1)); WARN_LIST+=("DMARC: nekonzistentní mezi NS ($frac).")
             if [ "$g" = ok ]; then OK=$((OK+1)); OK_LABELS+=("DMARC"); policy_warn; rua_warn; else PROP=$((PROP+1)); PROP_LIST+=("DMARC (TTL ${ttl:-?}s)"); fi ;;
    na)      if [ "$g" = ok ]; then OK=$((OK+1)); OK_LABELS+=("DMARC"); policy_warn; rua_warn; else problem; fi ;;
    *)       if [ "$g" = ok ]; then WARN=$((WARN+1)); WARN_LIST+=("DMARC: v zóně chybí, veřejně ještě z keše – ověřte, že nebyl smazán."); else problem; fi ;;
  esac
}

addprob() { ERR=$((ERR+1)); P_LABEL+=("$1"); P_FQDN+=("$2"); P_TYPE+=("$3"); P_EXP+=("$4"); P_WHY+=("$5"); P_FOUND+=("$6"); }

dup_spf() { # name label server
  local cnt; cnt=$(qshort "$3" "$1" TXT | tr -d '"' | grep -ci 'v=spf1')
  if [ "${cnt:-0}" -gt 1 ]; then
    addprob "${2} — DUPLICITA SPF" "$1" TXT "právě jeden v=spf1" \
      "Na jednom jménu smí být JEN JEDEN SPF. Víc záznamů v=spf1 SPF zneplatní (PermError) a autentizace selže – slučte je do jednoho." "nalezeno ${cnt}× v=spf1"
  fi
}
dup_dmarc() { # name server
  local cnt; cnt=$(qshort "$2" "$1" TXT | tr -d '"' | grep -ci 'v=DMARC1')
  if [ "${cnt:-0}" -gt 1 ]; then
    addprob "DMARC — DUPLICITA" "$1" TXT "právě jeden v=DMARC1" \
      "Víc DMARC záznamů na jednom _dmarc je neplatné – přijímací servery je ignorují. Nechte jen jeden." "nalezeno ${cnt}× v=DMARC1"
  fi
}

# počet DNS-lookup termů v SPF (RFC 7208, limit 10): include/a/mx/ptr/exists/redirect, rekurzivně.
# Bez subshellů – přičítá do SPF_TOTAL; SPF_Q je společný rozpočet dotazů na celou doménu,
# při vyčerpání (nebo >10 mechanismů v jednom záznamu) nastaví SPF_TRUNC=1 a končí.
_spf_budget() { SPF_Q=$((SPF_Q+1)); [ "$SPF_Q" -le "$SPF_BUDGET" ] && return 0; SPF_TRUNC=1; return 1; }
spf_count_walk() { # domain depth -> přičítá do SPF_TOTAL
  local d="$1" depth="${2:-0}" rec t tgt has_all=0 mech=0
  [ "$depth" -gt 10 ] && return
  [ "$SPF_TRUNC" -eq 1 ] && return
  _spf_budget || return
  rec=$(qshort "$DNS" "$d" TXT | tr -d '"' | grep -i 'v=spf1' | head -1)
  [ -z "$rec" ] && return
  case " $rec " in *" all "*|*"~all"*|*"-all"*|*"+all"*|*"?all"*) has_all=1;; esac
  for t in $rec; do
    case "$t" in
      a|a:*|a/*|mx|mx:*|mx/*|ptr|ptr:*|exists:*) SPF_TOTAL=$((SPF_TOTAL+1)); mech=$((mech+1)) ;;
      include:*) tgt="${t#include:}"; SPF_TOTAL=$((SPF_TOTAL+1)); mech=$((mech+1)); spf_count_walk "$tgt" $((depth+1)) ;;
      redirect=*) [ "$has_all" -eq 0 ] && { tgt="${t#redirect=}"; SPF_TOTAL=$((SPF_TOTAL+1)); mech=$((mech+1)); spf_count_walk "$tgt" $((depth+1)); } ;;
    esac
    [ "$SPF_TRUNC" -eq 1 ] && return
    [ "$mech" -ge 10 ] && { SPF_TRUNC=1; return; }
  done
}
spf_limit() { # name label
  local nm="$1" lbl="$2" rec cnt
  rec=$(qshort "$DNS" "$nm" TXT | tr -d '"' | grep -i 'v=spf1' | head -1)
  [ -z "$rec" ] && return   # žádné SPF – řeší se jinde
  SPF_TOTAL=0; SPF_TRUNC=0; spf_count_walk "$nm" 0
  cnt=$SPF_TOTAL
  if [ "$SPF_TRUNC" -eq 1 ]; then
    addprob "${lbl} — SPF limit" "$nm" TXT "≤ 10 DNS lookupů" \
      "SPF strom je příliš rozsáhlý – překračuje limit 10 DNS lookupů (PermError) a SPF přestane platit celé. Zredukujte include: nebo použijte SPF flattening." "více než 10 lookupů (procházení přerušeno)"
  elif [ "$cnt" -gt 10 ]; then
    addprob "${lbl} — SPF limit" "$nm" TXT "≤ 10 DNS lookupů" \
      "SPF vyžaduje ${cnt} DNS lookupů (limit je 10). Nad 10 je to PermError a SPF přestane platit celé – i pro SmartEmailing. Zredukujte include: nebo použijte SPF flattening." "${cnt} lookupů"
  elif [ "$cnt" -ge 8 ]; then
    WARN=$((WARN+1)); WARN_LIST+=("${lbl}: SPF má ${cnt}/10 DNS lookupů – blízko limitu, další include: může SPF rozbít (PermError).")
  fi
}

# je cílový include ve stromu SPF? echo: "direct" | "<include-přes-který>" (vnořeně) | "" (nikde)
_spf_tree_has() { # domain target depth -> návratový kód 0 = nalezeno (bez subshellů, čerpá rozpočet)
  local d="$1" target="$2" depth="${3:-0}" rec t tgt has_all=0 mech=0
  [ "$depth" -gt 10 ] && return 1
  [ "$SPF_TRUNC" -eq 1 ] && return 1
  _spf_budget || return 1
  rec=$(qshort "$DNS" "$d" TXT | tr -d '"' | grep -i 'v=spf1' | head -1 | tr '[:upper:]' '[:lower:]')
  [ -z "$rec" ] && return 1
  case " $rec " in *" all "*|*"~all"*|*"-all"*|*"+all"*|*"?all"*) has_all=1;; esac
  for t in $rec; do
    case "$t" in
      include:*|redirect=*)
        case "$t" in redirect=*) [ "$has_all" -eq 1 ] && continue ;; esac
        tgt="${t#include:}"; tgt="${tgt#redirect=}"
        [ "$tgt" = "$target" ] && return 0
        mech=$((mech+1)); [ "$mech" -gt 10 ] && return 1
        _spf_tree_has "$tgt" "$target" $((depth+1)) && return 0
        [ "$SPF_TRUNC" -eq 1 ] && return 1 ;;
    esac
  done
  return 1
}
spf_find_include() { # domain target -> nastaví SPF_VIA ("direct" | "<include>" | "")
  local d="$1" target="$2" rec t tgt has_all=0 mech=0
  SPF_VIA=""
  _spf_budget || return
  rec=$(qshort "$DNS" "$d" TXT | tr -d '"' | grep -i 'v=spf1' | head -1 | tr '[:upper:]' '[:lower:]')
  [ -z "$rec" ] && return
  case " $rec " in *" all "*|*"~all"*|*"-all"*|*"+all"*|*"?all"*) has_all=1;; esac
  for t in $rec; do
    case "$t" in
      include:*|redirect=*)
        case "$t" in redirect=*) [ "$has_all" -eq 1 ] && continue ;; esac
        tgt="${t#include:}"; tgt="${tgt#redirect=}"
        [ "$tgt" = "$target" ] && { SPF_VIA="direct"; return; }
        mech=$((mech+1)); [ "$mech" -gt 10 ] && return
        if _spf_tree_has "$tgt" "$target" 1; then SPF_VIA="$tgt"; return; fi
        [ "$SPF_TRUNC" -eq 1 ] && return ;;
    esac
  done
}

finalize_domain() {
  BATCH_DOM+=("$DOMAIN"); BATCH_OK+=("$OK"); BATCH_ERR+=("$ERR"); BATCH_PROP+=("$PROP"); BATCH_WARN+=("$WARN"); BATCH_EXIT+=("$DEXIT"); BATCH_MSG+=("$DERR_MSG")
  [ "$DEXIT" -gt "$OVERALL" ] && OVERALL="$DEXIT"

  local _res
  if   [ "$DEXIT" -eq 1 ]; then _res="chyba-vstup"
  elif [ "$DEXIT" -eq 3 ]; then _res="chyba-dns"
  elif [ "$ERR" -gt 0 ];   then _res="problem"
  elif [ "$PROP" -gt 0 ];  then _res="propagace"
  else                          _res="ok"; fi
  [ -n "$DOMAIN" ] && track "$DOMAIN" "$_res" "$ERR"

  if [ "$JSON" -eq 1 ]; then DOM_JSON+=("$(render_json)"); return; fi
  [ -n "$DERR_MSG" ] && return

  echo -e "${DIM}${SEP}${N}"
  echo -e "  ${B}SOUHRN${N}   ${G}${OK}× OK${N}   ${Y}${PROP}× čeká na propagaci${N}   ${R}${ERR}× CHYBA${N}   ${Y}${WARN}× upozornění${N}"
  echo -e "${DIM}${SEP}${N}"

  if [ ${#OK_LABELS[@]} -gt 0 ]; then
    local j="" l; for l in "${OK_LABELS[@]}"; do [ -z "$j" ] && j="$l" || j="$j, $l"; done
    echo -e "  ${G}✔ Správně nastaveno:${N} ${j}"; echo
  fi
  if [ ${#PROP_LIST[@]} -gt 0 ]; then
    local j="" l; for l in "${PROP_LIST[@]}"; do [ -z "$j" ] && j="$l" || j="$j, $l"; done
    echo -e "  ${G}✔${N} ${Y}Nastaveno správně, čeká jen na propagaci${N} (v zóně OK, svět ještě nevidí – počkejte dle TTL): ${j}"; echo
  fi
  if [ ${#P_LABEL[@]} -gt 0 ]; then
    echo -e "  ${R}✗ K opravě:${N}"; local i
    for i in "${!P_LABEL[@]}"; do
      echo -e "     ${R}•${N} ${B}${P_LABEL[$i]}${N}  ${DIM}(${P_FQDN[$i]}, ${P_TYPE[$i]})${N}"
      if [ -z "${P_FOUND[$i]}" ]; then echo -e "         chybí:     ${P_EXP[$i]}"
      else echo -e "         nalezeno:  ${P_FOUND[$i]}"; echo -e "         očekáváno: ${P_EXP[$i]}"; fi
      echo -e "         ${Y}proč:${N} ${P_WHY[$i]}"
    done; echo
  fi
  if [ ${#WARN_LIST[@]} -gt 0 ]; then
    echo -e "  ${Y}⚠ Upozornění:${N}"; local w; for w in "${WARN_LIST[@]}"; do echo -e "     • ${w}"; done; echo
  fi
  echo -e "  ${DIM}Vysvětlení záznamů: ${DOC_URL}${N}"
  if [ "$ERR" -gt 0 ]; then echo -e "  ${R}✗ Doména vyžaduje zásah (viz K opravě).${N}"
  elif [ "$PROP" -gt 0 ]; then echo -e "  ${G}✔ V zóně vše správně, čeká se jen na propagaci.${N}"
  else echo -e "  ${G}✔ Vše správně nastavené a propagované.${N}"; fi
}

render_json() {
  local i first
  printf '{'
  printf '"version":"%s","version_date":"%s",' "$(json_escape "$VERSION")" "$(json_escape "$VERSION_DATE")"
  printf '"domain":"%s","web_a":"%s","mx":"%s",' "$(json_escape "$DOMAIN")" "$(json_escape "${apex_a:-}")" "$(json_escape "${apex_mx:-}")"
  printf '"dmarc_rua":"%s","dmarc_rua_addr":"%s",' "$(json_escape "${DM_RUA_CLASS:-}")" "$(json_escape "${DM_RUA_ADDR:-}")"
  [ -n "$DERR_MSG" ] && printf '"error":"%s",' "$(json_escape "$DERR_MSG")"
  printf '"global_dns":"%s","summary":{"ok":%d,"propagating":%d,"error":%d,"warning":%d},"exit":%d,' \
    "$(json_escape "$DNS")" "$OK" "$PROP" "$ERR" "$WARN" "$DEXIT"
  printf '"records":['
  first=1
  for i in "${!R_LABEL[@]}"; do
    [ "$first" -eq 0 ] && printf ','; first=0
    printf '{"label":"%s","name":"%s","type":"%s","global":"%s","ns":"%s","ns_fraction":"%s","ttl":%s,"value":"%s","via":"%s"}' \
      "$(json_escape "${R_LABEL[$i]}")" "$(json_escape "${R_NAME[$i]}")" "${R_TYPE[$i]}" "${R_G[$i]}" "${R_N[$i]}" "${R_FRAC[$i]}" "${R_TTL[$i]:-null}" "$(json_escape "${R_VAL[$i]}")" "$(json_escape "${R_VIA[$i]:-}")"
  done
  printf '],"problems":['
  first=1
  for i in "${!P_LABEL[@]}"; do
    [ "$first" -eq 0 ] && printf ','; first=0
    printf '{"label":"%s","target":"%s","type":"%s","expected":"%s","found":"%s","why":"%s"}' \
      "$(json_escape "${P_LABEL[$i]}")" "$(json_escape "${P_FQDN[$i]}")" "${P_TYPE[$i]}" "$(json_escape "${P_EXP[$i]}")" "$(json_escape "${P_FOUND[$i]}")" "$(json_escape "${P_WHY[$i]}")"
  done
  printf '],"warnings":['
  wfirst=1
  if [ ${#WARN_LIST[@]} -gt 0 ]; then
    for i in "${!WARN_LIST[@]}"; do
      [ "$wfirst" -eq 0 ] && printf ','; wfirst=0
      printf '"%s"' "$(json_escape "${WARN_LIST[$i]}")"
    done
  fi
  printf ']}'
}

# je zadaný resolver dostupný?
if ! dns_reachable "$DNS"; then
  if [ "$JSON" -eq 1 ]; then printf '{"error":"global DNS server %s neodpovídá","exit":4}\n' "$(json_escape "$DNS")"
  else echo -e "\n  ${R}✗ DNS server '${DNS}' neodpovídá / nelze použít.${N}\n  ${DIM}Zkontrolujte volbu --dns / 2. argument. Bez ní se použije 8.8.8.8.${N}\n"; fi
  exit 4
fi

OVERALL=0
BATCH_DOM=(); BATCH_OK=(); BATCH_ERR=(); BATCH_PROP=(); BATCH_WARN=(); BATCH_EXIT=(); BATCH_MSG=()
DOM_JSON=()

for d in "${DOMAINS[@]}"; do check_domain "$d"; done

if [ "$JSON" -eq 1 ]; then
  first=1; printf '['
  for j in "${DOM_JSON[@]}"; do [ "$first" -eq 0 ] && printf ','; first=0; printf '%s' "$j"; done
  printf ']\n'
elif [ ${#DOMAINS[@]} -gt 1 ]; then
  echo; echo -e "${B}══ Souhrn dávky (${#BATCH_DOM[@]} domén) ══${N}"; echo -e "${DIM}${SEP}${N}"
  for i in "${!BATCH_DOM[@]}"; do
    verdict=""
    if [ -n "${BATCH_MSG[$i]}" ]; then verdict="${R}✗ ${BATCH_MSG[$i]}${N}"
    elif [ "${BATCH_ERR[$i]}" -gt 0 ]; then verdict="${R}✗ ${BATCH_ERR[$i]}× k opravě${N}"
    elif [ "${BATCH_PROP[$i]}" -gt 0 ]; then verdict="${Y}⏳ čeká na propagaci${N}"
    elif [ "${BATCH_WARN[$i]}" -gt 0 ]; then verdict="${G}✔ OK${N} ${Y}(+${BATCH_WARN[$i]} upozornění)${N}"
    else verdict="${G}✔ OK${N}"; fi
    printf '  %b %b\n' "$(pad_to "${BATCH_DOM[$i]}" 40)" "$verdict"
  done
  echo
fi

[ "$JSON" -eq 0 ] && [ -t 1 ] && check_update
# po dvojkliku nech okno otevřené, dokud uživatel nestiskne Enter
if [ -t 0 ]; then echo; read -r -p "Hotovo. Stiskněte Enter pro zavření okna..." _; fi
exit "$OVERALL"
