#!/usr/bin/env bash
# php-check.sh — projde všechny běžící Docker kontejnery (Coolify server),
# zjistí verzi PHP a porovná ji s nejnovější verzí dané větve z php.net.
#
# Použití:
#   ./php-check.sh            # kontrola proti aktuálním verzím z php.net
#   ./php-check.sh --all      # vypíše i kontejnery bez PHP
#   ./php-check.sh --rebuild  # u zastaralých spustí v Coolify force rebuild (se potvrzením)
#   ./php-check.sh --rebuild --yes   # bez ptaní (cron)
#   MIN_8_4=8.4.27 ./php-check.sh   # ruční přepsání minimální verze větve
#
# --rebuild volá Coolify přímo přes `docker exec coolify php artisan tinker` — žádný token.
# Když tinker selže (interní funkce Coolify se můžou s updatem změnit) a je nastavený
# COOLIFY_TOKEN, zkusí se API (COOLIFY_URL, výchozí http://localhost:8000).
#
# Exit code: 0 = vše aktuální, 1 = něco je zastaralé (vhodné pro cron).

set -uo pipefail

SHOW_ALL=0; REBUILD=0; ASSUME_YES=0
for arg in "$@"; do
  case "$arg" in
    --all)     SHOW_ALL=1 ;;
    --rebuild) REBUILD=1 ;;
    --yes|-y)  ASSUME_YES=1 ;;
    *) echo "neznámá volba: $arg (--all, --rebuild, --yes)"; exit 2 ;;
  esac
done

RED=$'\e[31m'; GREEN=$'\e[32m'; YELLOW=$'\e[33m'; DIM=$'\e[2m'; BOLD=$'\e[1m'; RESET=$'\e[0m'
[[ -t 1 ]] || { RED=""; GREEN=""; YELLOW=""; DIM=""; BOLD=""; RESET=""; }

command -v docker >/dev/null || { echo "docker nenalezen"; exit 2; }

COOLIFY_URL="${COOLIFY_URL:-http://localhost:8000}"
COOLIFY_TOKEN="${COOLIFY_TOKEN:-}"

declare -A LATEST

# Nejnovější verze dané větve z php.net (cache v rámci běhu)
latest_for_branch() {
  local branch="$1" var="MIN_${1//./_}"
  # ruční override přes env, např. MIN_8_4=8.4.27
  if [[ -n "${!var:-}" ]]; then echo "${!var}"; return; fi
  if [[ -z "${LATEST[$branch]:-}" ]]; then
    LATEST[$branch]=$(curl -fsS --max-time 10 \
      "https://www.php.net/releases/index.php?json&version=${branch}" 2>/dev/null \
      | grep -o '"version":"[^"]*"' | head -n1 | cut -d'"' -f4)
  fi
  echo "${LATEST[$branch]:-}"
}

# vrátí 0, pokud $1 < $2
version_lt() {
  [[ "$1" != "$2" ]] && [[ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)" == "$1" ]]
}

# PHP verze uvnitř kontejneru (zkouší php i php-fpm)
php_version_in() {
  docker exec "$1" sh -c '
    for b in php php-fpm php-fpm8.5 php-fpm8.4 php-fpm8.3 php-fpm8.2 php8.5 php8.4 php8.3 php8.2; do
      if command -v "$b" >/dev/null 2>&1; then
        "$b" -v 2>/dev/null | head -n1 | grep -oE "[0-9]+\.[0-9]+\.[0-9]+" | head -n1
        exit 0
      fi
    done' 2>/dev/null | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | head -n1
  # grep odfiltruje chybové hlášky (např. kontejnery bez shellu — distroless)
}

# Coolify přidává do názvů 24znakové UUID a časové razítko — pryč s nimi
strip_uuid() { sed -E 's/(^|-)[a-z0-9]{24}([-_].*)?$//' <<<"$1"; }

# Image bez registry; image postavené Coolify (<uuid>:<commit>) jako "(build)"
short_image() {
  local i="${1##*/}"
  [[ "$i" =~ ^[a-z0-9]{24}(_[a-z0-9-]+)?(:|$) ]] && i="(build)"
  echo "$i"
}

# Zkrátí na $2 znaků (ASCII), ať se nerozjedou sloupce
clip() { local s="$1" w="$2"; (( ${#s} > w )) && s="${s:0:w-2}.."; echo "$s"; }

# Řádky: pořadí \t stav \t název \t verze \t nejnovější \t image \t typ:uuid v Coolify (nebo -)
# pořadí: 0 zastaralé, 1 nelze ověřit, 2 OK, 3 bez PHP
rows=()
n_old=0; n_unknown=0; n_ok=0

while IFS='|' read -r id name image; do
  ver=$(php_version_in "$id")
  [[ -z "$ver" ]] && (( ! SHOW_ALL )) && continue

  # čitelný název: Coolify resourceName (+ compose služba), jinak jméno kontejneru;
  # compose projekt je u aplikací i služeb Coolify jejich UUID
  IFS='|' read -r res svc project managed ctype < <(docker inspect -f \
    '{{index .Config.Labels "coolify.resourceName"}}|{{index .Config.Labels "com.docker.compose.service"}}|{{index .Config.Labels "com.docker.compose.project"}}|{{index .Config.Labels "coolify.managed"}}|{{index .Config.Labels "coolify.type"}}' \
    "$id" 2>/dev/null)
  res=$(strip_uuid "${res:-}"); svc=$(strip_uuid "${svc:-}")
  [[ -z "$res" ]] && res="$name"
  [[ -n "$svc" && "$svc" != "$res" ]] && res="$res/$svc"
  uuid="-"
  if [[ "${managed:-}" == "true" && "${project:-}" =~ ^[a-z0-9]{24}$ ]]; then
    [[ "${ctype:-}" == "service" ]] && uuid="service:$project" || uuid="application:$project"
  fi

  app=$(clip "$res" 36)
  img=$(clip "$(short_image "$image")" 32)

  if [[ -z "$ver" ]]; then
    rows+=("3"$'\t'"none"$'\t'"$app"$'\t'"-"$'\t'"-"$'\t'"$img"$'\t'"$uuid")
    continue
  fi

  branch="${ver%.*}"
  latest=$(latest_for_branch "$branch")

  if [[ -z "$latest" ]]; then
    rows+=("1"$'\t'"unknown"$'\t'"$app"$'\t'"$ver"$'\t'"-"$'\t'"$img"$'\t'"$uuid"); ((n_unknown++))
  elif version_lt "$ver" "$latest"; then
    rows+=("0"$'\t'"old"$'\t'"$app"$'\t'"$ver"$'\t'"$latest"$'\t'"$img"$'\t'"$uuid"); ((n_old++))
  else
    rows+=("2"$'\t'"ok"$'\t'"$app"$'\t'"$ver"$'\t'"$latest"$'\t'"$img"$'\t'"$uuid"); ((n_ok++))
  fi
done < <(docker ps --format '{{.ID}}|{{.Names}}|{{.Image}}')

if (( ${#rows[@]} == 0 )); then
  echo "Žádný běžící kontejner s PHP."
  exit 0
fi

# šířka sloupce s názvem podle nejdelšího názvu
w_app=8
for r in "${rows[@]}"; do
  IFS=$'\t' read -r _ _ app _ _ _ _ <<<"$r"
  (( ${#app} > w_app )) && w_app=${#app}
done

# "NEJNOVĚJŠÍ" má 3 dvoubajtové znaky, printf počítá bajty → šířka +3
printf "  ${BOLD}%-*s  %-8s  %-*s  %s${RESET}\n" "$w_app" "APLIKACE" "PHP" 13 "NEJNOVĚJŠÍ" "IMAGE"

sorted=$(printf '%s\n' "${rows[@]}" | sort -t$'\t' -k1,1n -k3,3)
while IFS=$'\t' read -r _ state app ver latest img _; do
  case "$state" in
    old)     mark="${RED}✗${RESET}";    color="$RED" ;;
    unknown) mark="${YELLOW}?${RESET}"; color="$YELLOW"; latest="EOL?" ;;
    ok)      mark="${GREEN}✓${RESET}";  color="" ;;
    none)    mark="${DIM}·${RESET}";    color="$DIM"; ver="bez PHP"; latest="" ;;
  esac
  printf "%s %-*s  ${color}%-8s${RESET}  %-10s  ${DIM}%s${RESET}\n" \
    "$mark" "$w_app" "$app" "$ver" "$latest" "$img"
done <<<"$sorted"

echo
summary="${GREEN}${n_ok} OK${RESET}"
(( n_old ))     && summary="${RED}${n_old} zastaralé${RESET}, $summary"
(( n_unknown )) && summary="$summary, ${YELLOW}${n_unknown} nelze ověřit${RESET}"
echo "$summary"

(( n_old || n_unknown )) || exit 0

echo
(( n_unknown )) && echo "${DIM}? php.net pro tuhle větev nic nevrací — nejspíš skončila podpora, rebuild nepomůže, je čas na upgrade.${RESET}"

if (( ! REBUILD )); then
  (( n_old )) && echo "${DIM}✗ Redeploy v Coolify s vypnutou cache (force rebuild), nebo spusť s --rebuild. U připnutých tagů (php:8.4.26-fpm) nejdřív zvedni verzi v Dockerfile.${RESET}"
  exit 1
fi

# Force rebuild přímo v Coolify (tinker) — funkce, které volá i jeho API. Vypíše "ok|zpráva" / "fail|zpráva".
rebuild_tinker() {
  local kind="$1" uuid="$2" php out
  if [[ "$kind" == "service" ]]; then
    php='$s = App\Models\Service::where("uuid", "'"$uuid"'")->first();
      if (! $s) { echo "fail|služba v Coolify nenalezena"; return; }
      App\Actions\Service\StartService::run($s, pullLatestImages: true);
      echo "ok|služba se spouští s novými image";'
  else
    php='$a = App\Models\Application::where("uuid", "'"$uuid"'")->first();
      if (! $a) { echo "fail|aplikace v Coolify nenalezena"; return; }
      $r = queue_application_deployment(application: $a, deployment_uuid: new_public_id(), force_rebuild: true);
      echo (($r["status"] ?? "") === "queue_full" ? "fail|" : "ok|").($r["message"] ?? "");'
  fi
  out=$(docker exec coolify php artisan tinker --execute="try { $php } catch (\Throwable \$e) { echo 'fail|'.\$e->getMessage(); }" 2>&1 \
    | grep -E '^(ok|fail)\|' | tail -n1)
  echo "${out:-fail|tinker nevrátil výsledek}"
}

# Záloha přes Coolify API (jen s COOLIFY_TOKEN)
rebuild_api() {
  local uuid="$1" resp code body
  resp=$(curl -sS --max-time 20 -w $'\n%{http_code}' \
    -H "Authorization: Bearer $COOLIFY_TOKEN" -H "Accept: application/json" \
    "$COOLIFY_URL/api/v1/deploy?uuid=$uuid&force=true" 2>&1)
  code="${resp##*$'\n'}"; body="${resp%$'\n'*}"
  if [[ "$code" == 2* ]]; then echo "ok|přes API"
  else echo "fail|API HTTP $code: $(grep -o '"message":"[^"]*"' <<<"$body" | head -n1 | cut -d'"' -f4)"; fi
}

# --rebuild: zastaralé aplikace/služby Coolify, každá jen jednou (compose má víc kontejnerů)
declare -A seen
targets=(); skipped=()
while IFS=$'\t' read -r _ state app _ _ _ ref; do
  [[ "$state" == "old" ]] || continue
  if [[ "$ref" == "-" ]]; then skipped+=("$app"); continue; fi
  [[ -n "${seen[$ref]:-}" ]] && continue
  seen[$ref]=1
  targets+=("$ref"$'\t'"${app%%/*}")
done <<<"$sorted"

for s in "${skipped[@]}"; do
  echo "${YELLOW}přeskočeno:${RESET} $s — není to aplikace ani služba Coolify (Coolify samo aktualizuj v Settings → Update)"
done
(( ${#targets[@]} )) || exit 1

echo "${BOLD}Force rebuild:${RESET}"
for t in "${targets[@]}"; do ref="${t%%$'\t'*}"; echo "  ${t#*$'\t'} ${DIM}(${ref%%:*} ${ref#*:})${RESET}"; done

if (( ! ASSUME_YES )); then
  # stdin může být roura (curl | bash), ptáme se přes terminál
  if ! { exec 3</dev/tty; } 2>/dev/null; then
    echo "Bez terminálu se neptám — přidej --yes."; exit 1
  fi
  read -r -u 3 -p "Spustit rebuild u ${#targets[@]} aplikací? [a/N] " answer
  exec 3<&-
  [[ "$answer" =~ ^[aAyY] ]] || { echo "Nic se nespustilo."; exit 1; }
fi

queued=0
for t in "${targets[@]}"; do
  ref="${t%%$'\t'*}"; app="${t#*$'\t'}"
  kind="${ref%%:*}"; uuid="${ref#*:}"
  result=$(rebuild_tinker "$kind" "$uuid")
  if [[ "$result" == fail* && -n "$COOLIFY_TOKEN" ]]; then
    api=$(rebuild_api "$uuid")
    [[ "$api" == ok* ]] && result="$api (tinker: ${result#fail|})" || result="$result; $api"
  fi
  if [[ "$result" == ok* ]]; then
    echo "${GREEN}✓${RESET} $app — ${result#ok|}"; ((queued++))
  else
    echo "${RED}✗${RESET} $app — ${result#fail|}"
  fi
done
(( queued )) && echo "${DIM}Buildy běží v Coolify na pozadí (vidíš je v Deployments). Po dostavění spusť kontrolu znovu.${RESET}"
exit 1
