#!/usr/bin/env bash
# 1344 — manage an installed 1344 Network Panel.
set -Eeuo pipefail
CONF_DIR=/etc/1344; APP_DIR=/opt/1344; BACKUP_DIR=/var/backups/1344; INSTALL_CONF=$CONF_DIR/install.conf
if [[ -t 1 ]]; then S=$'\e[38;5;252m'; G=$'\e[38;5;240m'; R=$'\e[38;5;131m'; OK=$'\e[38;5;71m'; W=$'\e[38;5;179m'; B=$'\e[1m'; Z=$'\e[0m'; else S=; G=; R=; OK=; W=; B=; Z=; fi
ok()   { echo "  ${OK}✓${Z} ${S}$*${Z}"; }
bad()  { echo "  ${R}✗${Z} ${S}$*${Z}"; }
warn() { echo "  ${W}!${Z} ${S}$*${Z}"; }
die()  { bad "$*"; exit 1; }
[[ $EUID -eq 0 ]] || die "Run as root (sudo 1344 $*)"
[[ -r $INSTALL_CONF ]] || die "No installation found ($INSTALL_CONF missing)"
# shellcheck disable=SC1090
. "$INSTALL_CONF"
QUIET=0; KEEP=14; ARGS=()
while (($#)); do case $1 in --quiet) QUIET=1;; --keep) KEEP=$2; shift;; --purge) PURGE=1;; *) ARGS+=("$1");; esac; shift; done
set -- "${ARGS[@]:-}"; CMD=${1:-help}; shift || true
PURGE=${PURGE:-0}
say() { ((QUIET)) || echo "$@"; }

scheme() { [[ $SSL_MODE == none ]] && echo http || echo https; }
base_url() { local h=${DOMAIN:-$HOST_IP} p=$HTTPS_PORT; [[ $SSL_MODE == none ]] && p=$([[ $HTTP_PORT -gt 0 ]] && echo "$HTTP_PORT" || echo "$HTTPS_PORT"); local pp=":$p"; [[ ($(scheme) == https && $p == 443) || ($(scheme) == http && $p == 80) ]] && pp=; echo "$(scheme)://$h$pp${BASE_PATH:+/$BASE_PATH}/"; }
health() { curl -fsS --max-time 5 "http://127.0.0.1:$API_PORT/api/health" 2>/dev/null; }
env_value() { grep -E "^$1=" $CONF_DIR/panel.env | head -1 | cut -d= -f2-; }
pg_dump_to() { local url; url=$(env_value DATABASE_URL); pg_dump --no-owner --clean --if-exists "$url" | gzip -9; }

cmd_status() {
  echo "${B}${S}1344 NETWORK PANEL${Z}  ${G}v$(cat $APP_DIR/VERSION)${Z}"
  printf '  %s%-14s%s %s\n' "$G" "Panel URL" "$Z" "$(base_url)"
  local svc; for svc in 1344-panel nginx postgresql certbot.timer 1344-backup.timer; do
    systemctl list-unit-files "$svc*" >/dev/null 2>&1 || continue
    printf '  %s%-14s%s %s\n' "$G" "$svc" "$Z" "$(systemctl is-active "$svc" 2>/dev/null || true)"; done
  printf '  %s%-14s%s %s\n' "$G" "API health" "$Z" "$(health || echo unreachable)"
  [[ -d $BACKUP_DIR ]] && printf '  %s%-14s%s %s\n' "$G" "Last backup" "$Z" "$(ls -1t $BACKUP_DIR 2>/dev/null | head -1 || echo none)"
}
cmd_start()   { systemctl start 1344-panel; [[ $USE_NGINX == 1 ]] && systemctl start nginx; ok "started"; }
cmd_stop()    { systemctl stop 1344-panel; ok "panel stopped (nginx and PostgreSQL left running)"; }
cmd_restart() { systemctl restart 1344-panel; sleep 2; health >/dev/null && ok "restarted and healthy" || die "restarted but the API is not healthy; run '1344 doctor'"; }
cmd_logs()    { exec journalctl -u 1344-panel "${@:--n 100 -f}" ; }

cmd_ssl() {
  case ${1:-status} in
    status)
      [[ $SSL_MODE == none ]] && { warn "No TLS configured"; return; }
      local crt; case $SSL_MODE in letsencrypt) crt=/etc/letsencrypt/live/$DOMAIN/fullchain.pem;; letsencrypt_ip) crt=$CONF_DIR/tls/ip.pem;; custom) crt=$CONF_DIR/tls/custom.crt;; *) crt=$CONF_DIR/tls/selfsigned.crt;; esac
      echo "  ${G}mode${Z}     $SSL_MODE"; openssl x509 -noout -subject -issuer -enddate -in "$crt" | sed "s/^/  /"
      local win=$((14*86400)) label="14 days"; [[ $SSL_MODE == letsencrypt_ip ]] && { win=86400; label="1 day"; }  # IP certificates live ~6 days
      openssl x509 -noout -checkend $win -in "$crt" >/dev/null && ok "valid for more than $label" || warn "expires within $label"
      [[ $SSL_MODE == letsencrypt ]] && echo "  ${G}renewal${Z}  $(systemctl is-active certbot.timer)"
      [[ $SSL_MODE == letsencrypt_ip ]] && echo "  ${G}renewal${Z}  acme.sh cron: $(crontab -l 2>/dev/null | grep -c acme.sh) job(s) · short-lived certificate, renews every few days";;
    renew) [[ $SSL_MODE == letsencrypt_ip ]] && { "$HOME/.acme.sh/acme.sh" --cron && ok "acme.sh renewal run complete"; return; }; [[ $SSL_MODE == letsencrypt ]] || die "Renewal applies to Let's Encrypt certificates only"; certbot renew && systemctl reload nginx && ok "renewal run complete";;
    reissue) [[ $SSL_MODE == letsencrypt_ip ]] && { "$HOME/.acme.sh/acme.sh" --renew -d "$HOST_IP" --force && ok "IP certificate re-issued"; return; }; [[ $SSL_MODE == letsencrypt ]] || die "Only for Let's Encrypt"; certbot renew --force-renewal && systemctl reload nginx && ok "certificate re-issued";;
    *) die "usage: 1344 ssl [status|renew|reissue]";;
  esac
}

cmd_firewall() {
  command -v ufw >/dev/null || die "ufw is not installed"
  case ${1:-status} in
    status) ufw status verbose;;
    allow) [[ ${2:-} =~ ^[0-9]+(/(tcp|udp))?$ ]] || die "usage: 1344 firewall allow PORT[/tcp|udp]"; ufw allow "$2" comment '1344: custom'; ok "allowed $2";;
    deny)  [[ ${2:-} =~ ^[0-9]+(/(tcp|udp))?$ ]] || die "usage: 1344 firewall deny PORT[/tcp|udp]"
           local ssh; ssh=$(ss -tlnpH | awk '/sshd/{n=split($4,a,":"); print a[n]; exit}'); [[ ${2%%/*} == "${ssh:-22}" ]] && die "Refusing to touch the SSH port"
           ufw delete allow "$2"; ok "removed allow rule for $2";;
    *) die "usage: 1344 firewall [status|allow PORT|deny PORT]";;
  esac
}

cmd_backup() {
  mkdir -p $BACKUP_DIR; chmod 700 $BACKUP_DIR
  local ts out tmp; ts=$(date +%Y%m%d-%H%M%S); out=$BACKUP_DIR/panel-$ts.tar.gz; tmp=$(mktemp -d); trap 'rm -rf "$tmp"' RETURN
  (umask 077; pg_dump_to >"$tmp/db.sql.gz"; cp -a $CONF_DIR "$tmp/etc-1344"; cp $APP_DIR/VERSION "$tmp/VERSION"; tar -C "$tmp" -czf "$out" .)
  chmod 600 "$out"; say "  ${OK}✓${Z} backup written: $out ($(du -h "$out" | cut -f1))"
  ls -1t $BACKUP_DIR/panel-*.tar.gz 2>/dev/null | tail -n +$((KEEP+1)) | xargs -r rm -f
  echo "$out"
}

cmd_restore() {
  local f=${1:-}; [[ -f $f ]] || { echo "  ${S}Available backups:${Z}"; ls -1t $BACKUP_DIR 2>/dev/null | sed 's/^/    /'; die "usage: 1344 restore <backup.tar.gz>"; }
  warn "This replaces the panel database and configuration with the backup."
  read -r -p "  Type RESTORE to continue: " a; [[ $a == RESTORE ]] || die "Aborted"
  local pre; pre=$(QUIET=1 cmd_backup | tail -1); ok "safety backup of current state: $pre"
  local tmp; tmp=$(mktemp -d); trap 'rm -rf "$tmp"' RETURN; tar -C "$tmp" -xzf "$f" || die "Not a valid backup archive"
  [[ -f $tmp/db.sql.gz && -d $tmp/etc-1344 ]] || die "Archive is missing db.sql.gz or etc-1344"
  systemctl stop 1344-panel
  local url; url=$(env_value DATABASE_URL)
  gunzip -c "$tmp/db.sql.gz" | psql -q -v ON_ERROR_STOP=1 "$url" >/dev/null || die "Database restore failed. Your previous state is in $pre"
  cp -a "$tmp/etc-1344/panel.env" $CONF_DIR/panel.env; chown root:panel1344 $CONF_DIR/panel.env; chmod 640 $CONF_DIR/panel.env
  [[ -d $tmp/etc-1344/tls ]] && cp -a "$tmp/etc-1344/tls" $CONF_DIR/
  systemctl start 1344-panel; sleep 2; health >/dev/null && ok "restored and healthy" || die "restored but unhealthy; previous state: $pre"
}

cmd_update() {
  local src=${1:-}; [[ -n $src ]] || die "usage: 1344 update <bundle.tar.gz | https://…/bundle.tar.gz>"
  local tmp; tmp=$(mktemp -d); trap 'rm -rf "$tmp"' RETURN
  if [[ $src =~ ^https:// ]]; then curl -fsSL "$src" -o "$tmp/b.tgz" || die "download failed"; src=$tmp/b.tgz; fi
  [[ -f $src ]] || die "bundle not found"; mkdir "$tmp/x"; tar -C "$tmp/x" -xzf "$src"; local d; d=$(find "$tmp/x" -maxdepth 2 -name VERSION -printf '%h\n' | head -1)
  [[ -x $d/bin/panel && -d $d/web ]] || die "Not a valid release bundle"
  local new old; new=$(cat "$d/VERSION"); old=$(cat $APP_DIR/VERSION)
  [[ ${new%%.*} -ge ${old%%.*} ]] || die "Refusing major downgrade $old → $new"
  [[ $new != "$old" ]] || warn "Bundle has the same version ($new); reinstalling it"
  echo "  ${S}Updating $old → $new${Z}"
  local bk; bk=$(QUIET=1 cmd_backup | tail -1); ok "pre-update backup: $bk"
  local prev_bin prev_web; prev_bin=$(readlink -f $APP_DIR/panel); prev_web=$(readlink -f $APP_DIR/web)
  install -m 755 "$d/bin/panel" "$APP_DIR/releases/$new-panel"; rm -rf "$APP_DIR/releases/$new-web"; cp -r "$d/web" "$APP_DIR/releases/$new-web"
  systemctl stop 1344-panel
  ln -sfn "$APP_DIR/releases/$new-panel" $APP_DIR/panel; ln -sfn "$APP_DIR/releases/$new-web" $APP_DIR/web
  systemctl start 1344-panel; local i; for i in $(seq 1 30); do health >/dev/null && break; sleep 1; done
  if health >/dev/null; then echo "$new" >$APP_DIR/VERSION; sed -i "s/^VERSION=.*/VERSION=$new/" $INSTALL_CONF; ok "updated to $new and healthy"
    ls -1dt $APP_DIR/releases/*-panel | tail -n +4 | xargs -r rm -f
  else
    bad "New version failed its health check — rolling back"
    systemctl stop 1344-panel; ln -sfn "$prev_bin" $APP_DIR/panel; ln -sfn "$prev_web" $APP_DIR/web; systemctl start 1344-panel; sleep 3
    health >/dev/null && ok "rolled back to $old (database backup kept at $bk if a migration needs reverting)" || die "Rollback also unhealthy. Restore with: 1344 restore $bk"
    exit 1
  fi
}

cmd_doctor() {
  echo "${B}${S}1344 doctor${Z}"; local fails=0
  c() { local l=$1; shift; if "$@" >/dev/null 2>&1; then ok "$l"; else bad "$l"; fails=$((fails+1)); [[ -n ${FIX:-} ]] && echo "      ${G}→ $FIX${Z}"; fi; FIX=; }
  FIX="journalctl -u 1344-panel -n 50"; c "1344-panel service active" systemctl is-active --quiet 1344-panel
  FIX="check DATABASE_URL in $CONF_DIR/panel.env and that PostgreSQL is running"; c "API healthy (database reachable)" health
  [[ $INSTALL_PG != -1 ]] && { FIX="systemctl start postgresql"; c "PostgreSQL active" systemctl is-active --quiet postgresql; }
  FIX="ss -tlnp | grep 5432 — bind PostgreSQL to 127.0.0.1 in postgresql.conf"; c "PostgreSQL not exposed publicly" bash -c "! ss -tlnH | awk '{print \$4}' | grep -E '^(0\.0\.0\.0|\*|\[::\]):5432\$'"
  if [[ $USE_NGINX == 1 ]]; then FIX="nginx -t; then systemctl restart nginx"; c "nginx active + valid config" bash -c "systemctl is-active --quiet nginx && nginx -t"; fi
  if [[ $SSL_MODE != none ]]; then
    local crt; case $SSL_MODE in letsencrypt) crt=/etc/letsencrypt/live/$DOMAIN/fullchain.pem;; letsencrypt_ip) crt=$CONF_DIR/tls/ip.pem;; custom) crt=$CONF_DIR/tls/custom.crt;; *) crt=$CONF_DIR/tls/selfsigned.crt;; esac
    FIX="1344 ssl renew (Let's Encrypt) or replace the certificate"; local win=$((14*86400)); [[ $SSL_MODE == letsencrypt_ip ]] && win=86400; c "TLS certificate valid" openssl x509 -noout -checkend $win -in "$crt"
    [[ $SSL_MODE == letsencrypt ]] && { FIX="systemctl enable --now certbot.timer"; c "certbot renewal timer active" systemctl is-active --quiet certbot.timer; }
    [[ $SSL_MODE == letsencrypt_ip ]] && { FIX="crontab -l | grep acme.sh  (reinstall: curl https://get.acme.sh | sh)"; c "acme.sh renewal cron installed" bash -c "crontab -l | grep -q acme.sh"; }
  fi
  [[ $MODE == domain ]] && { FIX="update the DNS A/AAAA record of $DOMAIN"; c "$DOMAIN resolves to this server" bash -c "ip=\$(curl -4fsS --max-time 5 https://api.ipify.org); getent ahostsv4 $DOMAIN | awk '{print \$1}' | grep -qx \"\$ip\""; }
  FIX="chmod 640 $CONF_DIR/panel.env"; c "panel.env permissions (640)" bash -c "[ \$(stat -c %a $CONF_DIR/panel.env) = 640 ]"
  FIX="df -h /"; c "disk usage below 90%" bash -c "[ \$(df --output=pcent / | tail -1 | tr -dc 0-9) -lt 90 ]"
  c "a backup exists" bash -c "ls $BACKUP_DIR/panel-*.tar.gz"
  command -v ufw >/dev/null && { FIX="1344 firewall allow $HTTPS_PORT/tcp"; c "firewall allows the panel port" bash -c "ufw status | grep -q '^$HTTPS_PORT' || ! ufw status | grep -q 'Status: active'"; }
  echo; ((fails)) && { bad "$fails check(s) failed"; exit 1; } || ok "everything looks healthy"
}

cmd_uninstall() {
  warn "This removes the panel service, nginx site and program files."
  echo "  ${S}By default your database, $CONF_DIR and $BACKUP_DIR are KEPT. Use --purge to delete them as well.${Z}"
  read -r -p "  Type UNINSTALL to continue: " a; [[ $a == UNINSTALL ]] || die "Aborted"
  if [[ $PURGE -eq 1 ]]; then read -r -p "  --purge deletes the DATABASE and ALL BACKUPS. Type DELETE-EVERYTHING: " b; [[ $b == DELETE-EVERYTHING ]] || die "Aborted"; fi
  [[ $PURGE -eq 1 ]] || { QUIET=1 cmd_backup >/dev/null; ok "final backup stored in $BACKUP_DIR"; }
  systemctl disable --now 1344-panel 1344-backup.timer 2>/dev/null || true
  rm -f /etc/systemd/system/1344-panel.service /etc/systemd/system/1344-backup.service /etc/systemd/system/1344-backup.timer /etc/nginx/sites-enabled/1344-panel /etc/nginx/sites-available/1344-panel* /etc/nginx/conf.d/1344-limits.conf
  systemctl daemon-reload; command -v nginx >/dev/null && nginx -t 2>/dev/null && systemctl reload nginx || true
  rm -rf $APP_DIR /usr/local/bin/1344.bak
  if [[ $PURGE -eq 1 ]]; then
    local url; url=$(env_value DATABASE_URL); [[ $INSTALL_PG != -1 ]] && { (cd /tmp && runuser -u postgres -- psql -qc "DROP DATABASE IF EXISTS $DB_NAME" -c "DROP ROLE IF EXISTS $DB_USER") || true; }
    rm -rf $CONF_DIR $BACKUP_DIR /var/lib/1344; userdel panel1344 2>/dev/null || true
  else rm -f $INSTALL_CONF.removed; mv $INSTALL_CONF $INSTALL_CONF.removed; fi
  ok "uninstalled$([[ $PURGE -eq 1 ]] || echo " (data kept)")"; rm -f /usr/local/bin/1344
}

case $CMD in
  status) cmd_status;; start) cmd_start;; stop) cmd_stop;; restart) cmd_restart;; logs) cmd_logs "$@";;
  ssl) cmd_ssl "$@";; firewall) cmd_firewall "$@";; backup) cmd_backup;; restore) cmd_restore "$@";;
  update) cmd_update "$@";; doctor) cmd_doctor;; uninstall) cmd_uninstall;;
  *) cat <<EOF
${B}${S}1344${Z} ${G}— manage 1344 Network Panel${Z}
  status      system state                       logs [journalctl args]   follow logs
  start|stop|restart                             ssl [status|renew|reissue]
  firewall [status|allow P|deny P]               backup [--keep N]        restore <file>
  update <bundle.tar.gz|URL>   backup → upgrade → health check → auto-rollback
  doctor      diagnostics with suggested fixes   uninstall [--purge]      keeps your data unless --purge
EOF
  ;;
esac
