#!/usr/bin/env bash
# WebPanel sürüm güncelleyicisi: SHA-256 doğrulama, sağlık kontrolü ve tek adım geri dönüş.
set -Eeuo pipefail

umask 077

ENV_INSTALL_DIR="${WEBPANEL_INSTALL_DIR:-}"
ENV_SERVICE_NAME="${WEBPANEL_SERVICE_NAME:-}"
ENV_BIN="${WEBPANEL_BIN:-}"
ENV_UPDATER_PATH="${WEBPANEL_UPDATER_PATH:-}"
ENV_UNINSTALLER_PATH="${WEBPANEL_UNINSTALLER_PATH:-}"
ENV_RELEASE_BASE_URL="${WEBPANEL_RELEASE_BASE_URL:-}"
ENV_CHANNEL_URL="${WEBPANEL_CHANNEL_URL:-}"
ENV_ALLOW_INSECURE="${WEBPANEL_ALLOW_INSECURE:-}"

CONFIG_DIR="${WEBPANEL_CONFIG_DIR:-/etc/webpanel}"
RELEASE_CONFIG="$CONFIG_DIR/release.conf"
RUNTIME_CONFIG="$CONFIG_DIR/webpanel.env"
INSTALL_DIR="${ENV_INSTALL_DIR:-/opt/webpanel}"
SERVICE_NAME="${ENV_SERVICE_NAME:-webpanel}"
BIN="${ENV_BIN:-$INSTALL_DIR/webpanel}"
UPDATER_PATH="${ENV_UPDATER_PATH:-/usr/local/sbin/webpanel-update}"
UNINSTALLER_PATH="${ENV_UNINSTALLER_PATH:-/usr/local/sbin/webpanel-uninstall}"
RELEASE_BASE_URL="${ENV_RELEASE_BASE_URL:-https://gits.hibna.com.tr/api/packages/hibna/generic/webpanel}"
CHANNEL_URL="${ENV_CHANNEL_URL:-https://gits.hibna.com.tr/hibna/Source-WebPanel/raw/branch/main/release/stable}"
ALLOW_INSECURE="${ENV_ALLOW_INSECURE:-0}"
PORT="8888"
TARGET="x86_64-unknown-linux-musl"
TMP_DIR=""

log() { printf '==> %s\n' "$*"; }
warn() { printf 'UYARI: %s\n' "$*" >&2; }
die() { printf 'HATA: %s\n' "$*" >&2; exit 1; }

cleanup() {
    if [ -n "$TMP_DIR" ] && [ -d "$TMP_DIR" ]; then
        rm -rf -- "$TMP_DIR"
    fi
}
trap cleanup EXIT

load_config() {
    local key value
    if [ -r "$RELEASE_CONFIG" ]; then
        while IFS='=' read -r key value; do
            case "$key" in
                WEBPANEL_INSTALL_DIR) [ -n "$ENV_INSTALL_DIR" ] || INSTALL_DIR="$value" ;;
                WEBPANEL_SERVICE_NAME) [ -n "$ENV_SERVICE_NAME" ] || SERVICE_NAME="$value" ;;
                WEBPANEL_BIN) [ -n "$ENV_BIN" ] || BIN="$value" ;;
                WEBPANEL_UPDATER_PATH) [ -n "$ENV_UPDATER_PATH" ] || UPDATER_PATH="$value" ;;
                WEBPANEL_UNINSTALLER_PATH) [ -n "$ENV_UNINSTALLER_PATH" ] || UNINSTALLER_PATH="$value" ;;
                WEBPANEL_RELEASE_BASE_URL) [ -n "$ENV_RELEASE_BASE_URL" ] || RELEASE_BASE_URL="$value" ;;
                WEBPANEL_CHANNEL_URL) [ -n "$ENV_CHANNEL_URL" ] || CHANNEL_URL="$value" ;;
                WEBPANEL_ALLOW_INSECURE) [ -n "$ENV_ALLOW_INSECURE" ] || ALLOW_INSECURE="$value" ;;
            esac
        done < "$RELEASE_CONFIG"
    fi
    if [ -r "$RUNTIME_CONFIG" ]; then
        value="$(awk -F= '$1 == "WEBPANEL_PORT" { print $2; exit }' "$RUNTIME_CONFIG")"
        PORT="${value:-8888}"
    fi
}

require_command() {
    command -v "$1" >/dev/null 2>&1 || die "Gerekli komut bulunamadı: $1"
}

validate_absolute_path() {
    local canonical
    case "$2" in /*) ;; *) die "$1 mutlak bir yol olmalı: $2" ;; esac
    [[ "$2" != *$'\n'* && "$2" != *$'\r'* && "$2" != *' '* ]] ||
        die "$1 boşluk veya satır sonu içeremez"
    canonical="$(realpath -m -- "$2")"
    [ "$canonical" = "$2" ] || die "$1 kanonik olmalı: $2"
}

validate_version() {
    [[ "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.-][A-Za-z0-9]+)*$ ]] ||
        die "Geçersiz sürüm: $1"
}

validate_url() {
    local value="$2"
    [[ "$value" != *[[:space:]]* ]] || die "$1 boşluk içeremez"
    if [ "$ALLOW_INSECURE" = "1" ]; then
        [[ "$value" =~ ^(https?|file):// ]] || die "$1 geçerli bir URL değil: $value"
    else
        [[ "$value" == https://* ]] || die "$1 HTTPS kullanmalı: $value"
    fi
}

download() {
    local url="$1" output="$2"
    if [ "$ALLOW_INSECURE" = "1" ]; then
        curl --fail --location --silent --show-error --output "$output" "$url"
    else
        curl --proto '=https' --tlsv1.2 --fail --location --silent --show-error \
            --output "$output" "$url"
    fi
}

current_version() {
    [ -x "$BIN" ] || die "WebPanel binary bulunamadı: $BIN"
    local output version
    output="$($BIN --version)"
    [[ "$output" =~ ^webpanel[[:space:]]+(.+)$ ]] || die "Kurulu binary sürüm bildirmiyor"
    version="${BASH_REMATCH[1]}"
    validate_version "$version"
    printf '%s\n' "$version"
}

stable_version() {
    validate_url "Sürüm kanalı" "$CHANNEL_URL"
    local output="$TMP_DIR/stable"
    download "$CHANNEL_URL" "$output"
    local version
    version="$(tr -d '[:space:]' < "$output")"
    validate_version "$version"
    printf '%s\n' "$version"
}

version_is_older() {
    local candidate="$1" current="$2" first
    [ "$candidate" != "$current" ] || return 1
    first="$(printf '%s\n%s\n' "$candidate" "$current" | sort -V | head -n 1)"
    [ "$first" = "$candidate" ]
}

verify_checksum() {
    local artifact="$1" checksum_file="$2" expected actual
    expected="$(awk 'NF { print $1; exit }' "$checksum_file")"
    [[ "$expected" =~ ^[0-9a-fA-F]{64}$ ]] || die "Checksum dosyası geçersiz"
    actual="$(sha256sum "$artifact" | awk '{print $1}')"
    [ "${actual,,}" = "${expected,,}" ] || die "Paket SHA-256 doğrulaması başarısız"
}

extract_package() {
    local artifact="$1" destination="$2"
    local count=0 seen_binary=0 seen_update=0 seen_uninstall=0 seen_version=0 entry
    while IFS= read -r entry; do
        count=$((count + 1))
        case "$entry" in
            webpanel) seen_binary=$((seen_binary + 1)) ;;
            webpanel-update) seen_update=$((seen_update + 1)) ;;
            webpanel-uninstall) seen_uninstall=$((seen_uninstall + 1)) ;;
            VERSION) seen_version=$((seen_version + 1)) ;;
            *) die "Paket beklenmeyen bir yol içeriyor: $entry" ;;
        esac
    done < <(tar -tzf "$artifact")
    if [ "$count" -ne 4 ] || [ "$seen_binary" -ne 1 ] || [ "$seen_update" -ne 1 ] ||
        [ "$seen_uninstall" -ne 1 ] || [ "$seen_version" -ne 1 ]; then
        die "Paket içeriği eksik veya tekrarlı"
    fi
    mkdir -p "$destination"
    tar -xzf "$artifact" -C "$destination" --no-same-owner --no-same-permissions
}

fetch_package() {
    local version="$1" destination="$2"
    local name="webpanel-v$version-$TARGET.tar.gz"
    local artifact="$TMP_DIR/$name" checksum="$TMP_DIR/$name.sha256"
    validate_url "Paket deposu" "$RELEASE_BASE_URL"
    log "WebPanel $version paketi indiriliyor"
    download "$RELEASE_BASE_URL/$version/$name" "$artifact"
    download "$RELEASE_BASE_URL/$version/$name.sha256" "$checksum"
    verify_checksum "$artifact" "$checksum"
    extract_package "$artifact" "$destination"
    [ "$(tr -d '[:space:]' < "$destination/VERSION")" = "$version" ] ||
        die "Paket içindeki sürüm beklenen değerle eşleşmiyor"
    chmod 0755 "$destination/webpanel" "$destination/webpanel-update" \
        "$destination/webpanel-uninstall"
    [ "$("$destination/webpanel" --version)" = "webpanel $version" ] ||
        die "Binary sürüm doğrulaması başarısız"
    bash -n "$destination/webpanel-update"
    bash -n "$destination/webpanel-uninstall"
}

health_check() {
    local _
    for _ in {1..30}; do
        if systemctl is-active --quiet "$SERVICE_NAME" &&
            curl --fail --silent --show-error --max-time 2 \
                "http://127.0.0.1:$PORT/api/health" | grep -q '"status":"ok"'; then
            return 0
        fi
        sleep 1
    done
    return 1
}

save_previous() {
    local version="$1" source_dir="$2" previous="$INSTALL_DIR/releases/previous"
    local staging="$INSTALL_DIR/releases/.previous-new"
    rm -rf -- "$staging"
    install -d -m 0700 "$staging"
    install -m 0755 "$source_dir/webpanel" "$staging/webpanel"
    install -m 0755 "$source_dir/webpanel-update" "$staging/webpanel-update"
    install -m 0755 "$source_dir/webpanel-uninstall" "$staging/webpanel-uninstall"
    printf '%s\n' "$version" > "$staging/VERSION"
    chmod 0600 "$staging/VERSION"
    rm -rf -- "$previous"
    mv -- "$staging" "$previous"
}

capture_current() {
    local destination="$1" version="$2"
    install -d -m 0700 "$destination"
    install -m 0755 "$BIN" "$destination/webpanel"
    install -m 0755 "$UPDATER_PATH" "$destination/webpanel-update"
    install -m 0755 "$UNINSTALLER_PATH" "$destination/webpanel-uninstall"
    printf '%s\n' "$version" > "$destination/VERSION"
}

install_package_files() {
    local source="$1" version="$2"
    rm -f -- "$BIN.new" "$UPDATER_PATH.new" "$UNINSTALLER_PATH.new"
    install -m 0755 "$source/webpanel" "$BIN.new"
    install -m 0755 "$source/webpanel-update" "$UPDATER_PATH.new"
    install -m 0755 "$source/webpanel-uninstall" "$UNINSTALLER_PATH.new"
    mv -f -- "$BIN.new" "$BIN"
    mv -f -- "$UPDATER_PATH.new" "$UPDATER_PATH"
    mv -f -- "$UNINSTALLER_PATH.new" "$UNINSTALLER_PATH"
    printf '%s\n' "$version" > "$INSTALL_DIR/VERSION"
    chmod 0600 "$INSTALL_DIR/VERSION"
}

restart_or_restore() {
    local previous="$INSTALL_DIR/releases/previous"
    if systemctl restart "$SERVICE_NAME" && health_check; then
        return 0
    fi
    warn "Yeni sürüm sağlık kontrolünü geçemedi; önceki sürüm geri yükleniyor"
    install_package_files "$previous" "$(tr -d '[:space:]' < "$previous/VERSION")"
    systemctl restart "$SERVICE_NAME" || true
    health_check || die "Önceki sürüm de sağlık kontrolünü geçemedi; journalctl -u $SERVICE_NAME inceleyin"
    return 1
}

update_command() {
    local requested="" allow_downgrade=0
    while [ "$#" -gt 0 ]; do
        case "$1" in
            --version)
                [ "$#" -ge 2 ] || die "--version bir değer ister"
                requested="$2"
                shift 2
                ;;
            --allow-downgrade) allow_downgrade=1; shift ;;
            *) die "Bilinmeyen güncelleme seçeneği: $1" ;;
        esac
    done
    local current target
    current="$(current_version)"
    if [ -n "$requested" ]; then
        target="$requested"
        validate_version "$target"
    else
        target="$(stable_version)"
    fi
    if [ "$current" = "$target" ]; then
        log "WebPanel zaten güncel: $current"
        return 0
    fi
    if version_is_older "$target" "$current" && [ "$allow_downgrade" != "1" ]; then
        die "$target, kurulu $current sürümünden eski; bilinçli düşürme için --allow-downgrade kullanın"
    fi
    fetch_package "$target" "$TMP_DIR/package"
    capture_current "$TMP_DIR/current" "$current"
    save_previous "$current" "$TMP_DIR/current"
    log "$current → $target güncellemesi uygulanıyor"
    install_package_files "$TMP_DIR/package" "$target"
    if ! restart_or_restore; then
        die "Güncelleme geri alındı"
    fi
    log "WebPanel $target sürümüne güncellendi"
}

rollback_command() {
    local previous="$INSTALL_DIR/releases/previous"
    if [ ! -x "$previous/webpanel" ] || [ ! -r "$previous/VERSION" ]; then
        die "Geri dönülebilecek sürüm yok"
    fi
    local current target
    current="$(current_version)"
    target="$(tr -d '[:space:]' < "$previous/VERSION")"
    validate_version "$target"
    capture_current "$TMP_DIR/current" "$current"
    log "$current → $target geri dönüşü uygulanıyor"
    install_package_files "$previous" "$target"
    if systemctl restart "$SERVICE_NAME" && health_check; then
        save_previous "$current" "$TMP_DIR/current"
        log "WebPanel $target sürümüne döndürüldü"
        return 0
    fi
    warn "Geri dönülen sürüm sağlık kontrolünü geçemedi; $current geri yükleniyor"
    install_package_files "$TMP_DIR/current" "$current"
    systemctl restart "$SERVICE_NAME" || true
    health_check || die "Servis geri yükleme sonrasında da sağlıksız"
    die "Geri dönüş uygulanamadı"
}

status_command() {
    local current previous="yok" stable="erişilemedi" stable_value
    current="$(current_version)"
    if [ -r "$INSTALL_DIR/releases/previous/VERSION" ]; then
        previous="$(tr -d '[:space:]' < "$INSTALL_DIR/releases/previous/VERSION")"
    fi
    if stable_value="$(stable_version 2>/dev/null)"; then
        stable="$stable_value"
    fi
    printf 'Kurulu sürüm : %s\n' "$current"
    printf 'Kararlı sürüm : %s\n' "$stable"
    printf 'Geri dönüş    : %s\n' "$previous"
    printf 'Servis        : %s\n' "$(systemctl is-active "$SERVICE_NAME" 2>/dev/null || true)"
}

check_command() {
    local current stable
    current="$(current_version)"
    stable="$(stable_version)"
    if [ "$current" = "$stable" ]; then
        log "WebPanel güncel: $current"
    else
        log "Yeni sürüm var: $current → $stable"
    fi
}

usage() {
    cat <<'EOF'
Kullanım:
  webpanel-update status
  webpanel-update check
  webpanel-update update [--version X.Y.Z] [--allow-downgrade]
  webpanel-update rollback

Güncelleme paketi SHA-256 ile doğrulanır. Yeni servis sağlık kontrolünü geçemezse
önceki binary ve bakım araçları otomatik olarak geri yüklenir.
EOF
}

if [ "$(id -u)" -ne 0 ]; then
    die "Bu komut root olarak çalıştırılmalı (sudo)."
fi
for command in awk bash curl flock grep head install mkdir mv realpath rm seq sha256sum sort systemctl tar tr; do
    require_command "$command"
done
[ "$(uname -s)" = "Linux" ] || die "Yalnızca Linux destekleniyor"
case "$(uname -m)" in x86_64 | amd64) ;; *) die "Desteklenmeyen mimari: $(uname -m)" ;; esac

load_config
validate_absolute_path "Kurulum dizini" "$INSTALL_DIR"
validate_absolute_path "Binary yolu" "$BIN"
validate_absolute_path "Güncelleyici yolu" "$UPDATER_PATH"
validate_absolute_path "Kaldırıcı yolu" "$UNINSTALLER_PATH"
case "$INSTALL_DIR" in / | /opt | /usr | /usr/local | /var | /var/lib | /etc | /root | /home)
    die "Kurulum dizini fazla geniş: $INSTALL_DIR" ;;
esac
[ "$BIN" = "$INSTALL_DIR/webpanel" ] || die "Binary kurulum dizini altında webpanel olmalı"
[ "$UPDATER_PATH" = "/usr/local/sbin/$SERVICE_NAME-update" ] || die "Güncelleyici yolu servis adıyla eşleşmiyor"
[ "$UNINSTALLER_PATH" = "/usr/local/sbin/$SERVICE_NAME-uninstall" ] || die "Kaldırıcı yolu servis adıyla eşleşmiyor"
[[ "$SERVICE_NAME" =~ ^[a-zA-Z0-9][a-zA-Z0-9_.@-]*$ ]] || die "Geçersiz servis adı"
if ! [[ "$PORT" =~ ^[0-9]+$ ]] || [ "$PORT" -lt 1 ] || [ "$PORT" -gt 65535 ]; then
    die "Geçersiz panel portu"
fi
case "$ALLOW_INSECURE" in 0 | 1) ;; *) die "WEBPANEL_ALLOW_INSECURE 0 veya 1 olmalı" ;; esac

TMP_DIR="$(mktemp -d -t webpanel-update.XXXXXXXX)"
COMMAND="${1:-update}"
if [ "$#" -gt 0 ]; then shift; fi

case "$COMMAND" in
    status) status_command "$@" ;;
    check) check_command "$@" ;;
    update)
        exec 9>"/run/lock/$SERVICE_NAME-update.lock"
        flock -n 9 || die "Başka bir WebPanel güncelleme işlemi çalışıyor"
        update_command "$@"
        ;;
    rollback)
        exec 9>"/run/lock/$SERVICE_NAME-update.lock"
        flock -n 9 || die "Başka bir WebPanel güncelleme işlemi çalışıyor"
        rollback_command "$@"
        ;;
    -h | --help | help) usage ;;
    *) usage >&2; die "Bilinmeyen komut: $COMMAND" ;;
esac
