#!/bin/bash

# pre-push backup hook: backs up changed files to remote server after each commit.
# Logs output to logs/.pre-push.log (gitignored) for debugging.

REPO_ROOT="$(git rev-parse --show-toplevel)"
LOG_FILE="$REPO_ROOT/logs/.pre-push.log"
BACKUP_QUEUE_FILE="$REPO_ROOT/logs/.backup-queue"

# Ensure logs directory exists
mkdir -p "$REPO_ROOT/logs"

# Initialize log with timestamp
echo "[$(date '+%Y-%m-%d %H:%M:%S')] pre-push backup started" >> "$LOG_FILE"

# --- TODO / FIXME warning (non-blocking) ---
_ORANGE='\033[38;5;208m'
_BOLD='\033[1m'
_RESET='\033[0m'

_todo_files=$(git -C "$REPO_ROOT" ls-files \
    2>/dev/null | grep -E '\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|rb|java|kt|c|h|cpp|hpp|cs|php|sql|proto|graphql|gql|sh|bash|zsh|json|jsonc|ya?ml|toml|md)$' | grep -v -E '\.min\.|^node_modules/|^vendor/|^dist/|^build/')

if [ -n "$_todo_files" ]; then
    _todo_matches=$(echo "$_todo_files" | \
        xargs -I{} grep -n --with-filename -E '\b(TODO|FIXME)\b' "$REPO_ROOT/{}" 2>/dev/null || true)
    if [ -n "$_todo_matches" ]; then
        _todo_count=$(echo "$_todo_matches" | grep -c .)
        printf "${_BOLD}${_ORANGE}⚠  %d TODO/FIXME item(s) in tracked files:${_RESET}\n" "$_todo_count"
        while IFS= read -r _match; do
            # Strip REPO_ROOT prefix for cleaner output
            printf "${_ORANGE}   %s${_RESET}\n" "${_match#"$REPO_ROOT"/}"
        done <<< "$_todo_matches"
        echo ""
    fi
fi
unset _ORANGE _BOLD _RESET _todo_files _todo_matches _todo_count _match

# Load backup configuration from .env without executing it as shell code
ENV_FILE="$REPO_ROOT/.env"

read_env_value() {
    key="$1"
    line=$(grep -E "^[[:space:]]*(export[[:space:]]+)?${key}[[:space:]]*=" "$ENV_FILE" | tail -n 1)

    if [ -z "$line" ]; then
        return 1
    fi

    value="${line#*=}"
    value=$(printf '%s' "$value" | sed -e 's/\r$//' -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')

    # Strip matching surrounding quotes if present
    if [[ "$value" == \"*\" && "$value" == *\" ]]; then
        value="${value:1:${#value}-2}"
    elif [[ "$value" == \'*\' && "$value" == *\' ]]; then
        value="${value:1:${#value}-2}"
    fi

    printf '%s' "$value"
}

if [ -f "$ENV_FILE" ]; then
    BACKUP_PASSWORD=$(read_env_value "BACKUP_PASSWORD" || true)
    BACKUP_USER=$(read_env_value "BACKUP_USER" || true)
    BACKUP_SERVER=$(read_env_value "BACKUP_SERVER" || true)
    BACKUP_PATH=$(read_env_value "BACKUP_PATH" || true)
    BACKUP_PORT=$(read_env_value "BACKUP_PORT" || true)  # Optional, defaults to 22
else
    msg="Warning: .env file not found at $REPO_ROOT/.env; pre-push backup is not configured. Skipping backup."
    echo "$msg" >&2
    echo "$msg" >> "$LOG_FILE"
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] pre-push backup skipped (no .env file found)" >> "$LOG_FILE"
    exit 0
fi

if [ -z "$BACKUP_PASSWORD" ] || [ -z "$BACKUP_USER" ] || [ -z "$BACKUP_SERVER" ] || [ -z "$BACKUP_PATH" ]; then
    msg="Error: .env is missing one or more required variables (BACKUP_PASSWORD, BACKUP_USER, BACKUP_SERVER, BACKUP_PATH)"
    echo "$msg" >&2
    echo "$msg" >> "$LOG_FILE"
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] pre-push backup failed" >> "$LOG_FILE"
    exit 1
fi

# Ensure sshpass is installed before attempting remote backup
if ! command -v sshpass >/dev/null 2>&1; then
    msg="Warning: sshpass is not installed; pre-push backup is disabled. Install sshpass or disable the backup hook to avoid this message."
    echo "$msg" >&2
    echo "$msg" >> "$LOG_FILE"
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] pre-push backup skipped (sshpass not found)" >> "$LOG_FILE"
    exit 0
fi

# Spinner: run while a background PID is alive, then clear the line
_spin() {
    local pid=$1 msg=$2
    local i=0 chars='-\|/'
    while kill -0 "$pid" 2>/dev/null; do
        printf "\r  %s %s" "${chars:$i:1}" "$msg"
        i=$(( (i+1) % 4 ))
        sleep 0.1
    done
    printf "\r\033[K"
}

# Merge QUEUE_FAILED (persistent old failures) and NEW_FAILURES (new failures this run),
# deduplicate by file path, and write the combined result to BACKUP_QUEUE_FILE.
_write_final_queue() {
    local _ts _item _path _tmp
    _ts="[$(date '+%Y-%m-%d %H:%M:%S')]"
    _tmp=$(mktemp)

    # Preserve old failures with their original timestamps (dedup by path)
    for _item in "${QUEUE_FAILED[@]}"; do
        _path="${_item#\[*\] }"
        grep -qF "$_path" "$_tmp" 2>/dev/null || echo "$_item" >> "$_tmp"
    done

    # Append new failures not already in the list
    for _path in "${NEW_FAILURES[@]}"; do
        grep -qF "$_path" "$_tmp" 2>/dev/null || echo "$_ts $_path" >> "$_tmp"
    done

    if [ -s "$_tmp" ]; then
        cp "$_tmp" "$BACKUP_QUEUE_FILE"
    else
        rm -f "$BACKUP_QUEUE_FILE"
    fi
    rm -f "$_tmp"
}

QUEUE_FAILED=()  # entries from existing queue that still fail on flush
NEW_FAILURES=()  # entries that fail in this run

# Get files changed in the last commit
CHANGED_FILES=$(git diff-tree --no-commit-id --name-only -r HEAD)

# If no files changed, log and exit cleanly
if [ -z "$CHANGED_FILES" ]; then
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] No files changed in this commit" >> "$LOG_FILE"
    exit 0
fi

# Count changed files for progress display
FILE_COUNT=$(echo "$CHANGED_FILES" | grep -c .)
echo "Backup: $FILE_COUNT file(s) → ${BACKUP_SERVER}"

# Establish a single SSH ControlMaster connection for all operations
CTRL_SOCKET=$(mktemp -u /tmp/ssh-ctrl-XXXXXX)
SSH_OPTS=(-p "${BACKUP_PORT:-22}"
    -o ControlPath="$CTRL_SOCKET"
    -o PubkeyAuthentication=no
    -o PreferredAuthentications=keyboard-interactive
    -o StrictHostKeyChecking=no
    -o ConnectTimeout=10
    -o ServerAliveInterval=5
    -o ServerAliveCountMax=2
)

trap 'ssh -O exit -o ControlPath="$CTRL_SOCKET" "${BACKUP_USER}@${BACKUP_SERVER}" 2>/dev/null || true' EXIT

# Run connection in background so we can show a spinner
_conn_out=$(mktemp)
_conn_exit=$(mktemp)
( sshpass -p "${BACKUP_PASSWORD}" ssh "${SSH_OPTS[@]}" -o ControlMaster=yes -o ControlPersist=60 "${BACKUP_USER}@${BACKUP_SERVER}" true > "$_conn_out" 2>&1; echo $? > "$_conn_exit" ) &
_conn_pid=$!
_spin "$_conn_pid" "Connecting to ${BACKUP_SERVER}..."
wait "$_conn_pid" 2>/dev/null || true
conn_exitcode=$(cat "$_conn_exit" 2>/dev/null || echo 1)
conn_output=$(cat "$_conn_out" 2>/dev/null || true)
rm -f "$_conn_out" "$_conn_exit"

if [ "$conn_exitcode" -ne 0 ]; then
    echo "  ✗ Could not connect to ${BACKUP_SERVER}: $conn_output"
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] Warning: Could not connect (${BACKUP_SERVER}): $conn_output" >> "$LOG_FILE"
    # Preserve any entries already in the queue from previous failed runs
    if [ -f "$BACKUP_QUEUE_FILE" ] && [ -s "$BACKUP_QUEUE_FILE" ]; then
        while IFS= read -r line; do
            QUEUE_FAILED+=("$line")
        done < "$BACKUP_QUEUE_FILE"
    fi
    OLD_IFS="$IFS"
    IFS=$'\n'
    for file in $CHANGED_FILES; do
        [ -f "$file" ] && NEW_FAILURES+=("$file")
    done
    IFS="$OLD_IFS"
    _write_final_queue
    echo "  ${#NEW_FAILURES[@]} file(s) queued for next push."
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] pre-push backup skipped (connection failed)" >> "$LOG_FILE"
    exit 0
fi
echo "  ✓ Connected"

# Process any previously queued files
if [ -f "$BACKUP_QUEUE_FILE" ] && [ -s "$BACKUP_QUEUE_FILE" ]; then
    queued_count=$(wc -l < "$BACKUP_QUEUE_FILE")
    echo "  Flushing $queued_count queued file(s) from previous run(s)..."
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] Processing $queued_count queued file(s)" >> "$LOG_FILE"
    OLD_IFS="$IFS"
    IFS=$'\n'
    while read -r line; do
        # Strip the leading timestamp prefix: [YYYY-MM-DD HH:MM:SS]
        queued_file="${line#\[*\] }"
        if [ ! -f "$queued_file" ]; then
            printf "  %-55s skip (no longer exists)\n" "$queued_file"
            echo "[$(date '+%Y-%m-%d %H:%M:%S')] Queue skip (not found): $queued_file" >> "$LOG_FILE"
        else
            printf "  %-55s" "$queued_file"
            if ! mkdir_output=$(ssh "${SSH_OPTS[@]}" -o ControlMaster=no "${BACKUP_USER}@${BACKUP_SERVER}" "mkdir -p '${BACKUP_PATH}/$(dirname "$queued_file")'" 2>&1); then
                printf "✗ (mkdir failed, kept in queue)\n"
                echo "[$(date '+%Y-%m-%d %H:%M:%S')] Queue flush mkdir failed: $queued_file" >> "$LOG_FILE"
                QUEUE_FAILED+=("$line")
                continue
            fi
            if scp_output=$(scp -P "${BACKUP_PORT:-22}" -o ControlPath="$CTRL_SOCKET" -o ControlMaster=no -o PubkeyAuthentication=no -o PreferredAuthentications=keyboard-interactive -o StrictHostKeyChecking=no "$queued_file" "${BACKUP_USER}@${BACKUP_SERVER}:${BACKUP_PATH}/${queued_file}" 2>&1); then
                printf "✓\n"
                echo "[$(date '+%Y-%m-%d %H:%M:%S')] Queue flush OK: $queued_file" >> "$LOG_FILE"
            else
                printf "✗ (kept in queue)\n"
                echo "[$(date '+%Y-%m-%d %H:%M:%S')] Queue flush failed: $queued_file" >> "$LOG_FILE"
                QUEUE_FAILED+=("$line")
            fi
        fi
    done < "$BACKUP_QUEUE_FILE"
    IFS="$OLD_IFS"
    if [ ${#QUEUE_FAILED[@]} -eq 0 ]; then
        echo "  Queue cleared."
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] Backup queue cleared" >> "$LOG_FILE"
    else
        echo "  Warning: ${#QUEUE_FAILED[@]} queued file(s) still failed -- will retry."
    fi
fi

# Backup each changed file
echo "  Backing up $FILE_COUNT file(s)..."
OLD_IFS="$IFS"
IFS=$'\n'
for file in $CHANGED_FILES; do
    if [ -f "$file" ]; then
        printf "  %-55s" "$file"
        echo "Backing up: $file" >> "$LOG_FILE"

        if ! mkdir_output=$(ssh "${SSH_OPTS[@]}" -o ControlMaster=no "${BACKUP_USER}@${BACKUP_SERVER}" "mkdir -p '${BACKUP_PATH}/$(dirname "$file")'" 2>&1); then
            printf "✗ (mkdir failed, queued)\n"
            echo "Failed to create remote directory for: $file. Error: $mkdir_output" >> "$LOG_FILE"
            NEW_FAILURES+=("$file")
            continue
        fi

        if ! scp_output=$(scp -P "${BACKUP_PORT:-22}" -o ControlPath="$CTRL_SOCKET" -o ControlMaster=no -o PubkeyAuthentication=no -o PreferredAuthentications=keyboard-interactive -o StrictHostKeyChecking=no "$file" "${BACKUP_USER}@${BACKUP_SERVER}:${BACKUP_PATH}/${file}" 2>&1); then
            printf "✗ (queued)\n"
            echo "Failed to copy: $file. Error: $scp_output" >> "$LOG_FILE"
            NEW_FAILURES+=("$file")
        else
            printf "✓\n"
            echo "Successfully backed up: $file" >> "$LOG_FILE"
        fi
    fi
done
IFS="$OLD_IFS"

_write_final_queue

if [ ${#NEW_FAILURES[@]} -gt 0 ]; then
    echo "  ${#NEW_FAILURES[@]} file(s) queued (will retry on next push)."
else
    echo "  All files backed up."
fi

echo "[$(date '+%Y-%m-%d %H:%M:%S')] pre-push backup completed" >> "$LOG_FILE"

