#!/bin/bash
# ============================================================
# MonitorPing — Script de Atualização
# Uso: sudo bash update.sh
# ============================================================
set -euo pipefail

INSTALL_DIR="/opt/monitorping"
COMPOSE_FILE="$INSTALL_DIR/docker-compose.prod.yml"
ENV_FILE="$INSTALL_DIR/.env"
BACKUP_DIR="/var/backups/monitorping"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BACKUP_PATH="$BACKUP_DIR/$TIMESTAMP"
LOG_FILE="$BACKUP_PATH/update.log"
MP_USER="monitorping"
AUTO_MODE=false
# Arquivos de comunicação com o painel web (volume Docker compartilhado)
WEB_LOG="$INSTALL_DIR/.update.log"
WEB_STATUS="$INSTALL_DIR/.update_status.json"
TRIGGER_FILE="$INSTALL_DIR/.update_requested"

# Roda comando como usuário monitorping com ambiente Docker rootless
mp_run() {
    MP_UID=$(id -u "$MP_USER")
    sudo -u "$MP_USER" \
        XDG_RUNTIME_DIR="/run/user/$MP_UID" \
        DOCKER_HOST="unix:///run/user/$MP_UID/docker.sock" \
        HOME="/home/$MP_USER" \
        "$@"
}

# ── Cores ───────────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
BLUE='\033[0;34m'; BOLD='\033[1m'; NC='\033[0m'

_write_log() { echo -e "$1" | tee -a "$LOG_FILE"; }
log()   { _write_log "${GREEN}✓${NC} $1"; }
info()  { _write_log "${BLUE}→${NC} $1"; }
warn()  { _write_log "${YELLOW}⚠${NC}  $1"; }
error() { _write_log "${RED}✗ ERRO:${NC} $1"; web_status "failed" "$1"; exit 1; }

web_status() {
    local status="$1" msg="${2:-}"
    # Sanitiza a mensagem para não quebrar o JSON (remove aspas, barras e newlines)
    msg=$(printf '%s' "$msg" | tr -d '"\\' | tr '\n\r' '  ')
    printf '{"status":"%s","message":"%s","timestamp":"%s","current":"%s"}\n' \
        "$status" "$msg" "$(date '+%Y-%m-%d %H:%M:%S')" "${CURRENT_VERSION:-}" \
        > "$WEB_STATUS" 2>/dev/null || true
    # Garante que www-data pode ler
    chmod 644 "$WEB_STATUS" "$WEB_LOG" 2>/dev/null || true
}

header() {
    echo ""
    echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
    echo -e "${BOLD}       MonitorPing — Atualização           ${NC}"
    echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
    echo ""
}

# ── Verificações iniciais ───────────────────────────────────
preflight() {
    [[ $EUID -ne 0 ]] && { echo -e "${RED}Execute com sudo: sudo bash update.sh${NC}"; exit 1; }
    [[ ! -d "$INSTALL_DIR" ]] && { echo -e "${RED}MonitorPing não encontrado em $INSTALL_DIR${NC}"; exit 1; }
    [[ ! -f "$ENV_FILE" ]]    && { echo -e "${RED}.env não encontrado em $ENV_FILE${NC}"; exit 1; }
    command -v docker &>/dev/null       || { echo -e "${RED}Docker não encontrado.${NC}"; exit 1; }
    docker compose version &>/dev/null  || { echo -e "${RED}Docker Compose não encontrado.${NC}"; exit 1; }

    mkdir -p "$BACKUP_PATH"
    touch "$LOG_FILE"
}

# ── Lê variáveis do .env ────────────────────────────────────
load_env() {
    set -a
    # shellcheck disable=SC1090
    source <(grep -v '^#' "$ENV_FILE" | grep -E '^[A-Z_]+=')
    set +a
}

# ── 1. Versão atual ─────────────────────────────────────────
get_current_version() {
    # Tenta ler do container em execução primeiro (mais confiável)
    local ver
    ver=$(mp_run docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" \
        exec -T app cat /var/www/html/VERSION 2>/dev/null | tr -d '[:space:]') || ver=""
    if [[ -n "$ver" ]]; then echo "$ver"; return; fi
    if [[ -f "$INSTALL_DIR/VERSION" ]]; then cat "$INSTALL_DIR/VERSION"; return; fi
    echo "desconhecida"
}

# ── 2. Backup do banco de dados ─────────────────────────────
backup_database() {
    info "Fazendo backup do banco de dados..."
    cd "$INSTALL_DIR"

    if mp_run docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" ps db 2>/dev/null | grep -q "running\|Up"; then
        if mp_run docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" exec -T db pg_dump -U "${DB_USER}" "${DB_NAME}" \
            > "$BACKUP_PATH/db_backup.sql" 2>>"$LOG_FILE"; then
            local size
            size=$(du -sh "$BACKUP_PATH/db_backup.sql" | cut -f1)
            log "Backup do banco: $BACKUP_PATH/db_backup.sql ($size)"
        else
            warn "Backup do banco falhou — continuando sem ele."
            rm -f "$BACKUP_PATH/db_backup.sql"
        fi
    else
        warn "Container do banco não está rodando — pulando backup do banco."
    fi
}

# ── 3. Backup do código atual ───────────────────────────────
backup_code() {
    info "Salvando código atual..."
    rsync -a \
        --exclude='.git' \
        --exclude='*.log' \
        --exclude='node_modules' \
        "$INSTALL_DIR/" "$BACKUP_PATH/code/" 2>>"$LOG_FILE"
    log "Código salvo em $BACKUP_PATH/code/"
}

# ── 4. Baixa nova versão ────────────────────────────────────
download_update() {
    info "Verificando atualizações disponíveis..."

    # Preserva .env antes de qualquer atualização de código
    cp "$ENV_FILE" "$BACKUP_PATH/.env.bak"

    if [[ -d "$INSTALL_DIR/.git" ]]; then
        # Modo git: pull da branch main
        git -C "$INSTALL_DIR" fetch origin 2>>"$LOG_FILE" \
            || error "Falha ao buscar atualizações (verifique a conexão)."

        LOCAL=$(git -C "$INSTALL_DIR" rev-parse HEAD)
        REMOTE=$(git -C "$INSTALL_DIR" rev-parse origin/main)

        if [[ "$LOCAL" == "$REMOTE" ]]; then
            log "Sistema já está na versão mais recente."
            echo "$LOCAL" > "$BACKUP_PATH/version_current.txt"
            return 1   # sinaliza "sem atualização"
        fi

        git -C "$INSTALL_DIR" pull origin main 2>>"$LOG_FILE" \
            || error "Falha ao atualizar o código via git."

    else
        # Modo tarball/zip: baixa do servidor de releases
        local URL_TAR="https://releases.monitorping.com.br/monitorping.tar.gz"
        local URL_ZIP="https://releases.monitorping.com.br/monitorping.zip"
        local TMP_PKG PKG_TYPE
        TMP_PKG="$(mktemp /tmp/monitorping-update-XXXXXX)"
        PKG_TYPE=""

        if curl -fsSL --max-time 120 "$URL_TAR" -o "$TMP_PKG" 2>>"$LOG_FILE"; then
            info "Pacote .tar.gz baixado"
            PKG_TYPE="tar"
        elif curl -fsSL --max-time 120 "$URL_ZIP" -o "$TMP_PKG" 2>>"$LOG_FILE"; then
            info "Pacote .zip baixado"
            PKG_TYPE="zip"
        else
            rm -f "$TMP_PKG"
            error "Falha ao baixar a atualização. Verifique releases.monitorping.com.br."
        fi

        info "Extraindo atualização..."
        local TMP_DIR
        TMP_DIR="$(mktemp -d /tmp/monitorping-upd-XXXXXX)"

        if [[ "$PKG_TYPE" == "zip" ]]; then
            command -v unzip &>/dev/null || apt-get install -y -qq unzip >> "$LOG_FILE" 2>&1
            unzip -q "$TMP_PKG" -d "$TMP_DIR" >> "$LOG_FILE" 2>&1 \
                || error "Falha ao extrair o .zip."
        else
            tar -xzf "$TMP_PKG" -C "$TMP_DIR" >> "$LOG_FILE" 2>&1 \
                || error "Falha ao extrair o .tar.gz."
        fi
        rm -f "$TMP_PKG"

        # Detecta pasta raiz dentro do pacote
        local SRC
        if [[ -f "$TMP_DIR/index.php" ]]; then
            SRC="$TMP_DIR"
        else
            SRC="$(find "$TMP_DIR" -maxdepth 1 -mindepth 1 -type d | head -1)"
            [[ -z "$SRC" || ! -f "$SRC/index.php" ]] && error "Estrutura do pacote inválida — index.php não encontrado."
        fi

        rsync -a \
            --exclude='.env' \
            --exclude='docker-compose.yml' \
            --exclude='docker-compose.prod.yml' \
            "$SRC/" "$INSTALL_DIR/" >> "$LOG_FILE" 2>&1

        rm -rf "$TMP_DIR"
        log "Arquivos atualizados"
    fi

    # Garante que .env não foi sobrescrito
    cp "$BACKUP_PATH/.env.bak" "$ENV_FILE"

    return 0   # sinaliza "atualização aplicada"
}

DC="mp_run docker compose -f $COMPOSE_FILE --env-file $ENV_FILE"

# ── 5. Reconstrói imagem e containers (volumes intocados) ────
rebuild_containers() {
    info "Reconstruindo imagem Docker (banco de dados preservado)..."
    cd "$INSTALL_DIR"
    # Corrige permissões para que monitorping possa ler os arquivos no build
    chown -R "$MP_USER:$MP_USER" "$INSTALL_DIR"
    find "$INSTALL_DIR" -type f -exec chmod 640 {} \;
    find "$INSTALL_DIR" -type d -exec chmod 750 {} \;
    [[ -f "$INSTALL_DIR/docker-entrypoint.sh" ]] && chmod 750 "$INSTALL_DIR/docker-entrypoint.sh"
    [[ -f "$INSTALL_DIR/update.sh" ]]            && chmod 750 "$INSTALL_DIR/update.sh"
    # /opt/monitorping é bind-montado nos containers: www-data precisa ler/escrever
    # todos os dot-files de estado (licença, cache, update log, etc.)
    chmod 777 "$INSTALL_DIR"
    for f in .update.log .update_status.json .version_check_cache \
              .license_cache .machine_id .license_key; do
        [[ -f "$INSTALL_DIR/$f" ]] && chmod 666 "$INSTALL_DIR/$f" || true
    done

    APP_VERSION=$(cat "$INSTALL_DIR/VERSION" 2>/dev/null | tr -d '[:space:]' || echo 'dev')
    mp_run docker build --build-arg APP_VERSION="$APP_VERSION" -t monitorping-app:latest "$INSTALL_DIR" >> "$LOG_FILE" 2>&1 \
        || error "Falha ao construir a imagem Docker."
    $DC up -d --remove-orphans >> "$LOG_FILE" 2>&1 \
        || error "Falha ao reiniciar containers."
    log "Containers atualizados e rodando."
}

# ── 6. Aguarda banco ficar pronto ───────────────────────────
wait_for_db() {
    info "Aguardando banco de dados inicializar..."
    local retries=60
    # Usa healthcheck status (nunca bloqueia) em vez de exec que pode travar
    until mp_run docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" \
            ps db 2>/dev/null | grep -q "healthy"; do
        ((retries--))
        [[ $retries -eq 0 ]] && error "Banco não respondeu após 120 segundos."
        sleep 2
    done
    log "Banco de dados pronto."
}

# ── 7. Executa migrações ────────────────────────────────────
run_migrations() {
    info "Executando setup_db.php..."
    $DC exec -T app php /var/www/html/setup_db.php \
        >> "$LOG_FILE" 2>&1 && log "setup_db.php OK" \
        || warn "setup_db.php reportou erros — verifique: $LOG_FILE"
}

# ── 8. Invalida cache de licença ────────────────────────────
clear_license_cache() {
    if $DC exec -T cron_license \
        sh -c 'rm -f /opt/monitorping/.license_cache' &>/dev/null; then
        log "Cache de licença removido (será revalidado)."
    fi
}

# ── 9. Remove backups antigos (>30 dias) ────────────────────
cleanup_old_backups() {
    find "$BACKUP_DIR" -maxdepth 1 -type d -mtime +30 -exec rm -rf {} + 2>/dev/null || true
    log "Backups com mais de 30 dias removidos."
}

# ── Rollback ─────────────────────────────────────────────────
rollback() {
    echo ""
    warn "Iniciando rollback para versão anterior..."
    if [[ -d "$BACKUP_PATH/code" ]]; then
        rsync -a --delete \
            --exclude='.env' \
            "$BACKUP_PATH/code/" "$INSTALL_DIR/" 2>/dev/null || true
        cp "$BACKUP_PATH/.env.bak" "$ENV_FILE" 2>/dev/null || true
        cd "$INSTALL_DIR"
        docker build -t monitorping-app:latest . 2>/dev/null || true
        docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" up -d 2>/dev/null || true
        warn "Rollback concluído. Versão anterior restaurada."
    else
        warn "Backup não encontrado — rollback não foi possível."
    fi
}

# ── Trap para rollback automático em caso de erro ───────────
trap 'echo ""; error "Atualização falhou. Execute: sudo bash update.sh --rollback '$TIMESTAMP'"' ERR

# ── Modo --check: apenas verifica se há atualização disponível ──
if [[ "${1:-}" == "--check" ]]; then
    [[ ! -f "$INSTALL_DIR/VERSION" ]] && { echo "VERSION não encontrado."; exit 1; }
    CURRENT=$(cat "$INSTALL_DIR/VERSION")
    UPDATE_URL="https://releases.monitorping.com.br/latest.version"
    LATEST=$(curl -fsSL --max-time 5 "$UPDATE_URL" 2>/dev/null | tr -d '[:space:]') || LATEST=""
    if [[ -z "$LATEST" ]]; then
        echo "Não foi possível verificar atualizações."
        exit 2
    fi
    if [[ "$CURRENT" == "$LATEST" ]]; then
        echo "up-to-date $CURRENT"
        exit 0
    else
        echo "update-available current=$CURRENT latest=$LATEST"
        exit 1
    fi
fi

# Modo --auto: disparado pelo watcher systemd ou pelo painel web
if [[ "${1:-}" == "--auto" ]]; then
    AUTO_MODE=true
    # Limpa log anterior e inicia novo
    : > "$WEB_LOG"
    chmod 644 "$WEB_LOG" 2>/dev/null || true
    exec >> "$WEB_LOG" 2>&1
    echo "══════════════════════════════════════════"
    echo " MonitorPing Auto-Update — $(date '+%Y-%m-%d %H:%M:%S')"
    echo "══════════════════════════════════════════"
fi

# Modo rollback manual
if [[ "${1:-}" == "--rollback" ]]; then
    TARGET="${2:-}"
    [[ -z "$TARGET" ]] && error "Informe o timestamp do backup: sudo bash update.sh --rollback YYYYMMDD-HHMMSS"
    BACKUP_PATH="$BACKUP_DIR/$TARGET"
    [[ ! -d "$BACKUP_PATH" ]] && error "Backup não encontrado: $BACKUP_PATH"
    preflight
    load_env
    rollback
    echo -e "\n${GREEN}Rollback concluído com sucesso!${NC}\n"
    exit 0
fi

# ════════════════════════════════════════════════════════════
# EXECUÇÃO PRINCIPAL
# ════════════════════════════════════════════════════════════
header
preflight
load_env

CURRENT_VERSION=$(get_current_version)
info "Versão instalada: $CURRENT_VERSION"
web_status "running" "Iniciando atualização..."
rm -f "$TRIGGER_FILE"

backup_database
backup_code

if download_update; then
    NEW_VERSION=$(get_current_version)
    web_status "running" "Reconstruindo containers..."
    rebuild_containers
    wait_for_db
    run_migrations
    clear_license_cache
    cleanup_old_backups
    web_status "success" "Atualizado para $NEW_VERSION"

    echo ""
    echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
    echo -e " ${GREEN}${BOLD}Atualização concluída com sucesso!${NC}"
    echo    " Versão anterior : $CURRENT_VERSION"
    echo    " Versão atual    : $NEW_VERSION"
    echo    " Backup salvo em : $BACKUP_PATH"
    echo    " Log completo    : $LOG_FILE"
    echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
    echo ""
else
    # Sem atualização disponível — limpa backup vazio
    rm -rf "$BACKUP_PATH"
    web_status "up_to_date" "Versão $CURRENT_VERSION já é a mais recente."
    echo ""
    echo -e " ${GREEN}${BOLD}Nenhuma atualização disponível.${NC}"
    echo    " Sistema está na versão mais recente: $CURRENT_VERSION"
    echo ""
fi
