#!/bin/bash
#==============================================================================
# Momentry Core — Upgrade Script
# Usage: bash upgrade_momentry.sh <delivery_dir>
#   <delivery_dir> : path to delivery package (e.g. release/delivery/v1.0.0_xxx/)
#
# Upgrades an existing momentry_core installation from a delivery package.
# 1. Pre-flight: version check, backup, health
# 2. Migration: apply DB schema changes
# 3. Binary: replace momentry binary
# 4. Scripts: replace Python processor scripts
# 5. Verification: health check + API smoke test
#==============================================================================

set -euo pipefail

PROJECT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
COLOR=true

# ─── Color helpers ───
if [ "$COLOR" = true ]; then
    R='\033[0;31m'; G='\033[0;32m'; Y='\033[1;33m'; B='\033[0;34m'; C='\033[0;36m'; N='\033[0m'
else
    R=''; G=''; Y=''; B=''; C=''; N=''
fi
ok()  { echo -e "  ${G}✓${N} $1"; }
fail() { echo -e "  ${R}✗${N} $1"; FAILURES+=("$1"); }
info() { echo -e "  ${B}→${N} $1"; }
warn() { echo -e "  ${Y}⚠${N} $1"; }
header() { echo -e "\n${C}─── $1 ───${N}"; }

FAILURES=()
DELIVERY_DIR=""

# ─── Parse args ───
while [ $# -gt 0 ]; do
    case "$1" in
        --project-dir) PROJECT_DIR="$2"; shift 2 ;;
        --no-color) COLOR=false; shift ;;
        --help) head -20 "$0"; exit 0 ;;
        *)
            [ -z "$DELIVERY_DIR" ] && DELIVERY_DIR="$1" || {
                echo "Unknown: $1"; exit 1; }
            shift ;;
    esac
done

if [ -z "$DELIVERY_DIR" ]; then
    echo "Usage: bash upgrade_momentry.sh <delivery_dir>"
    echo ""
    echo "  <delivery_dir>  Path to delivery package directory"
    echo "                  e.g. $PROJECT_DIR/release/delivery/v1.0.0_0e73d2a_20260515_084750"
    echo ""
    echo "  Delivery package must contain:"
    echo "    - momentry_v*           (production binary)"
    echo "    - scripts/              (processor scripts)"
    echo "    - migrate_*.sql         (DB migrations)"
    echo "    - INSTALL.md or similar (package info)"
    exit 1
fi

DELIVERY_DIR="$(cd "$DELIVERY_DIR" 2>/dev/null && pwd)" || {
    echo "ERROR: Delivery directory not found: $DELIVERY_DIR"
    exit 1
}

PG_BIN="${PG_BIN:-$HOME/pgsql/18.3/bin}"
DB_NAME="${DB_NAME:-momentry}"
BACKUP_DIR="$PROJECT_DIR/backup/upgrade_$(date +%Y%m%d_%H%M%S)"

cd "$PROJECT_DIR"
echo -e "${C}========================================${N}"
echo -e "${C}  Momentry Core — Upgrade${N}"
echo -e "${C}  Delivery: $DELIVERY_DIR${N}"
echo -e "${C}  Project:  $PROJECT_DIR${N}"
echo -e "${C}  Date:     $(date '+%Y-%m-%d %H:%M:%S')${N}"
echo -e "${C}========================================${N}"

# ═══════════════════════════════════════════════════════════
# Phase 0: Pre-flight
# ═══════════════════════════════════════════════════════════
header "Phase 0/5 — Pre-flight"

# 0a. Check delivery package integrity
info "Checking delivery package..."
REQUIRED=()
BINARY=$(ls "$DELIVERY_DIR"/momentry_v* 2>/dev/null | head -1 || true)
if [ -n "$BINARY" ]; then
    ok "Binary: $(basename "$BINARY") ($(ls -lh "$BINARY" | awk '{print $5}'))"
else
    fail "No momentry_v* binary in delivery package"
fi

if [ -d "$DELIVERY_DIR/scripts" ]; then
    SCRIPT_COUNT=$(find "$DELIVERY_DIR/scripts" -name '*.py' -type f | wc -l | tr -d ' ')
    ok "Scripts dir: $SCRIPT_COUNT .py files"
else
    fail "No scripts/ directory in delivery package"
fi

MIGRATIONS=()
for f in "$DELIVERY_DIR"/migrate_*.sql; do
    [ -f "$f" ] && MIGRATIONS+=("$f")
done
if [ ${#MIGRATIONS[@]} -gt 0 ]; then
    for f in "${MIGRATIONS[@]}"; do ok "Migration: $(basename "$f")"; done
else
    warn "No migration SQL files in delivery package"
fi

# 0b. Check current server
HEALTH_ENDPOINT="http://127.0.0.1:3003"
CURL_OPTS="-sf --connect-timeout 5"

if curl $CURL_OPTS "$HEALTH_ENDPOINT/health" &>/dev/null; then
    CURRENT_VER=$(curl -sf "$HEALTH_ENDPOINT/health" | python3 -c "import json,sys;print(json.load(sys.stdin).get('version','?'))" 2>/dev/null)
    CURRENT_HASH=$(curl -sf "$HEALTH_ENDPOINT/health" | python3 -c "import json,sys;print(json.load(sys.stdin).get('build_git_hash','?'))" 2>/dev/null)
    ok "Server running: v$CURRENT_VER ($CURRENT_HASH)"
else
    CURRENT_VER="down"
    warn "Server not reachable at $HEALTH_ENDPOINT"
    HEALTH_ENDPOINT="http://127.0.0.1:3002"
    if curl $CURL_OPTS "$HEALTH_ENDPOINT/health" &>/dev/null; then
        CURRENT_VER=$(curl -sf "$HEALTH_ENDPOINT/health" | python3 -c "import json,sys;print(json.load(sys.stdin).get('version','?'))" 2>/dev/null)
        ok "Production server running: v$CURRENT_VER"
    fi
fi

# Extract package version from INSTALL.md or binary name
PKG_VER=$(echo "$(basename "$DELIVERY_DIR")" | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' || echo "unknown")
PKG_BUILD=$(echo "$(basename "$DELIVERY_DIR")" | grep -oE '[a-f0-9]{7}' || echo "unknown")
info "Package: $PKG_VER (build $PKG_BUILD)"

# 0c. Check PostgreSQL
if "$PG_BIN/pg_isready" -q 2>/dev/null; then
    ok "PostgreSQL"
else
    fail "PostgreSQL not running"
fi

# 0d. Prompt to continue
if [ ${#FAILURES[@]} -gt 0 ]; then
    echo ""
    echo -e "${R}Pre-flight failures detected. Continue anyway? [y/N]${N} "
    read -r CONFIRM
    if [ "$CONFIRM" != "y" ] && [ "$CONFIRM" != "Y" ]; then
        echo "Aborted."
        exit 1
    fi
fi

# ═══════════════════════════════════════════════════════════
# Phase 1: Backup
# ═══════════════════════════════════════════════════════════
header "Phase 1/5 — Backup"

mkdir -p "$BACKUP_DIR"

# 1a. Backup current binary (if exists)
for bin_path in target/release/momentry target/debug/momentry_playground; do
    if [ -f "$PROJECT_DIR/$bin_path" ]; then
        mkdir -p "$BACKUP_DIR/$(dirname "$bin_path")"
        cp "$PROJECT_DIR/$bin_path" "$BACKUP_DIR/$bin_path"
        ok "Backed up: $bin_path"
    fi
done

# 1b. Backup scripts
SCRIPTS_SNAPSHOT="$BACKUP_DIR/scripts"
mkdir -p "$SCRIPTS_SNAPSHOT"
rsync -a "$PROJECT_DIR/scripts/" "$SCRIPTS_SNAPSHOT/" 2>/dev/null
ok "Backed up: scripts/ (to $SCRIPTS_SNAPSHOT)"

# 1c. Backup current .env
if [ -f "$PROJECT_DIR/.env" ]; then
    cp "$PROJECT_DIR/.env" "$BACKUP_DIR/.env"
    ok "Backed up: .env"
fi

# 1d. Schema backup
"$PG_BIN/pg_dump" -U accusys -d "$DB_NAME" --schema-only > "$BACKUP_DIR/schema_pre.sql" 2>/dev/null
ok "Backed up: schema (pre-upgrade)"

echo ""
info "Backup saved to: $BACKUP_DIR"

# ═══════════════════════════════════════════════════════════
# Phase 2: Database Migration
# ═══════════════════════════════════════════════════════════
header "Phase 2/6 — Database Migration"

# Ensure schema_migrations table exists first
"$PG_BIN/psql" -U accusys -d "$DB_NAME" -c "
    CREATE TABLE IF NOT EXISTS schema_migrations (
        id SERIAL PRIMARY KEY,
        filename TEXT NOT NULL UNIQUE,
        checksum TEXT NOT NULL,
        applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
        duration_ms INTEGER DEFAULT 0
    )" &>/dev/null
ok "schema_migrations table ensured"

for mig in "${MIGRATIONS[@]}"; do
    MIG_NAME=$(basename "$mig")
    MIG_HASH=$(shasum -a 256 "$mig" | awk '{print $1}')

    # Check if already applied with matching checksum
    ALREADY=$("$PG_BIN/psql" -U accusys -d "$DB_NAME" -t -A -c \
        "SELECT COUNT(*) FROM schema_migrations WHERE filename='$MIG_NAME' AND checksum='$MIG_HASH'" 2>/dev/null || echo "0")
    if [ "$ALREADY" -gt 0 ]; then
        ok "Migration $MIG_NAME (already applied, checksum match)"
        continue
    fi

    info "Applying $MIG_NAME..."
    T0=$(date +%s%N)
    if "$PG_BIN/psql" -U accusys -d "$DB_NAME" -f "$mig" 2>&1; then
        T1=$(date +%s%N)
        DURATION_MS=$(( (T1 - T0) / 1000000 ))
        "$PG_BIN/psql" -U accusys -d "$DB_NAME" -c \
            "INSERT INTO schema_migrations (filename, checksum, duration_ms) VALUES ('$MIG_NAME', '$MIG_HASH', $DURATION_MS) ON CONFLICT (filename) DO UPDATE SET checksum=EXCLUDED.checksum, applied_at=NOW()" &>/dev/null || true
        ok "Applied: $MIG_NAME (${DURATION_MS}ms)"
    else
        fail "Migration failed: $MIG_NAME"
    fi
done

# Verify schema completeness via binary startup check
info "Verifying schema... (will be checked at server restart)"

# ═══════════════════════════════════════════════════════════
# Phase 3: Binary Replacement
# ═══════════════════════════════════════════════════════════
header "Phase 3/5 — Binary Replacement"

# Determine which binary to install
if echo "$HEALTH_ENDPOINT" | grep -q "3002"; then
    TARGET_BIN="momentry"
    TARGET_PATH="$PROJECT_DIR/target/release/$TARGET_BIN"
    TARGET_PORT=3002
else
    TARGET_BIN="momentry_playground"
    TARGET_PATH="$PROJECT_DIR/target/debug/$TARGET_BIN"
    TARGET_PORT=3003
fi

# Copy binary from delivery
BINARY_SRC=$(ls "$DELIVERY_DIR"/momentry_v* 2>/dev/null | head -1 || true)
if [ -n "$BINARY_SRC" ]; then
    # If the delivery has a pre-built binary (for production)
    mkdir -p "$(dirname "$TARGET_PATH")"
    cp "$BINARY_SRC" "$TARGET_PATH"
    chmod +x "$TARGET_PATH"

    # Remove code signature (required for Apple Silicon when binary is from another Mac)
    if command -v codesign &>/dev/null; then
        codesign --remove-signature "$TARGET_PATH" 2>/dev/null || true
    fi

    ok "Binary replaced: $TARGET_PATH ($(ls -lh "$TARGET_PATH" | awk '{print $5}'))"
else
    # No pre-built binary — need to build from source
    info "No pre-built binary in delivery — building from source..."
    cargo build --bin "$TARGET_BIN" 2>&1 | tail -3
    if [ -f "$PROJECT_DIR/target/debug/momentry_playground" ]; then
        ok "Built: $TARGET_BIN"
    else
        fail "Build failed: $TARGET_BIN"
    fi
fi

# ═══════════════════════════════════════════════════════════
# Phase 4: Scripts Update
# ═══════════════════════════════════════════════════════════
header "Phase 4/5 — Scripts Update"

if [ -d "$DELIVERY_DIR/scripts" ]; then
    # Copy each processor script, preserving existing utility scripts
    # that may not be in the delivery package
    for f in "$DELIVERY_DIR/scripts"/*.py; do
        if [ -f "$f" ]; then
            cp "$f" "$PROJECT_DIR/scripts/"
        fi
    done
    # Copy subdirectories (swift_processors, utils, etc.)
    for d in "$DELIVERY_DIR/scripts"/*/; do
        if [ -d "$d" ]; then
            rsync -a "$d" "$PROJECT_DIR/scripts/$(basename "$d")/"
        fi
    done
    chmod +x "$PROJECT_DIR/scripts"/*.py 2>/dev/null || true
    ok "Processor scripts updated ($(find "$PROJECT_DIR/scripts" -name '*.py' -type f | wc -l | tr -d ' ') .py files)"

    # Verify SHA256 checksums after update
    CHECKSUMS_FILE="$PROJECT_DIR/scripts/checksums.sha256"
    CS_TOTAL=0; CS_PASS=0; CS_FAIL=0
    if [ -f "$CHECKSUMS_FILE" ]; then
        while IFS= read -r line; do
            [ -z "$line" ] && continue
            EXPECTED_HASH=$(echo "$line" | awk '{print $1}')
            FILE_PATH=$(echo "$line" | awk '{print $2}')
            FULL_PATH="$PROJECT_DIR/scripts/$FILE_PATH"
            CS_TOTAL=$((CS_TOTAL + 1))
            if [ -f "$FULL_PATH" ]; then
                ACTUAL_HASH=$(shasum -a 256 "$FULL_PATH" 2>/dev/null | awk '{print $1}')
                if [ "$ACTUAL_HASH" = "$EXPECTED_HASH" ]; then
                    CS_PASS=$((CS_PASS + 1))
                else
                    CS_FAIL=$((CS_FAIL + 1))
                    [ $CS_FAIL -le 5 ] && warn "$FILE_PATH — hash mismatch after update"
                fi
            else
                CS_FAIL=$((CS_FAIL + 1))
                [ $CS_FAIL -le 5 ] && warn "$FILE_PATH — not found after update"
            fi
        done < "$CHECKSUMS_FILE"
        ok "Script integrity: $CS_PASS/$CS_TOTAL checksums match"
        [ $CS_FAIL -gt 0 ] && warn "Script integrity: $CS_FAIL mismatches" || true
    else
        warn "No checksums.sha256 found — generating from updated scripts..."
        cd "$PROJECT_DIR/scripts"
        find . -name '*.py' -type f | sort | while IFS= read -r f; do shasum -a 256 "$f"; done > checksums.sha256
        ok "checksums.sha256 generated ($(wc -l < checksums.sha256) entries)"
    fi
else
    warn "No scripts/ in delivery — skipping"
fi

# Python dependencies check
header "  Python dependencies"
PYTHON_BIN="${MOMENTRY_PYTHON_PATH:-/opt/homebrew/bin/python3.11}"
for pkg in PyPDF2 python-docx openpyxl python-pptx; do
    if "$PYTHON_BIN" -c "import ${pkg%%=*}" &>/dev/null 2>&1; then
        ok "$pkg"
    else
        "$PYTHON_BIN" -m pip install "$pkg" --quiet && ok "$pkg" || warn "$pkg"
    fi
done

# Ensure watch directory exists
WATCH_DIR="${MOMENTRY_SFTP_ROOT:-$PROJECT_DIR/storage/watch}"
mkdir -p "$WATCH_DIR"
ok "Watcher directory: $WATCH_DIR"

# ═══════════════════════════════════════════════════════════
# Phase 6: Restart & Verify
# ═══════════════════════════════════════════════════════════
header "Phase 6/6 — Restart & Verify"

# 5a. Stop old server
if [ -n "$TARGET_PORT" ] && [ "$TARGET_PORT" = "3002" ]; then
    pkill -f "momentry server" 2>/dev/null || true
    pkill -f "momentry_playground server" 2>/dev/null || true
else
    pkill -f "momentry_playground server" 2>/dev/null || true
fi
sleep 2
info "Stopped old server"

# 5b. Start new server
cd "$PROJECT_DIR"
if [ "$TARGET_PORT" = "3002" ]; then
    DATABASE_SCHEMA=public nohup "$TARGET_PATH" server --port 3002 > "$PROJECT_DIR/production_boot.log" 2>&1 &
else
    DATABASE_SCHEMA=dev nohup "$TARGET_PATH" server --port 3003 > "$PROJECT_DIR/playground_boot.log" 2>&1 &
fi

for i in $(seq 1 10); do
    sleep 2
    if curl $CURL_OPTS "$HEALTH_ENDPOINT/health" &>/dev/null; then
        ok "Server restarted on port $TARGET_PORT"
        break
    fi
done
if ! curl $CURL_OPTS "$HEALTH_ENDPOINT/health" &>/dev/null; then
    fail "Server failed to start on port $TARGET_PORT"
fi

# 5c. Health check
HEALTH=$(curl -sf "$HEALTH_ENDPOINT/health" 2>/dev/null || echo '{"status":"error"}')
echo "$HEALTH" | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'    Version:   {d.get(\"version\",\"?\")}')
print(f'    Build:     {d.get(\"build_git_hash\",\"?\")}')
print(f'    Timestamp: {d.get(\"build_timestamp\",\"?\")}')
print(f'    Status:    {d.get(\"status\",\"?\")}')
" 2>/dev/null

# 5d. Processor inventory
DETAILED=$(curl -sf "$HEALTH_ENDPOINT/health/detailed" 2>/dev/null || echo '{}')
echo "$DETAILED" | python3 -c "
import json,sys
d=json.load(sys.stdin)
p=d.get('pipeline',{})
proc=p.get('processors',{})
missing=[k for k in ['asr','yolo','face','pose','ocr','cut','caption','scene','story','asrx','probe','visual_chunk'] if not proc.get(k)]
if missing:
    print(f'    Missing processors: {missing}')
else:
    print('    All 12 processors: ✓')
print(f'    Scripts: {p.get(\"scripts_count\",\"?\")} .py files')
" 2>/dev/null

# 5e. API smoke test
API_KEY="${MOMENTRY_API_KEY:-muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69}"
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" \
    -H "X-API-Key: $API_KEY" \
    "$HEALTH_ENDPOINT/api/v1/videos?page=1&page_size=1" 2>/dev/null || echo "000")
ok "API /v1/videos → HTTP $STATUS"

# ═══════════════════════════════════════════════════════════
# Summary
# ═══════════════════════════════════════════════════════════
echo ""
echo -e "${C}========================================${N}"
if [ ${#FAILURES[@]} -eq 0 ]; then
    echo -e "${G}  Upgrade Complete — All Checks Passed${N}"
else
    echo -e "${Y}  Upgrade Complete — ${#FAILURES[@]} Warnings${N}"
    for f in "${FAILURES[@]}"; do echo -e "  ${R}✗${N} $f"; done
fi
echo -e "${C}========================================${N}"
echo ""
echo "  Backup:     $BACKUP_DIR"
echo "  Server:     $HEALTH_ENDPOINT"
echo "  Binary:     $TARGET_PATH"
echo "  Watch dir:  $WATCH_DIR"
echo "  Version:    $(echo "$HEALTH" | python3 -c "import json,sys;print(json.load(sys.stdin).get('version','?'))" 2>/dev/null)"
echo ""
echo "  To rollback:"
echo "    1. cp $BACKUP_DIR/target/release/momentry $PROJECT_DIR/target/release/"
echo "    2. rsync -a $BACKUP_DIR/scripts/ $PROJECT_DIR/scripts/"
echo "    3. $PG_BIN/psql -U accusys -d $DB_NAME < $BACKUP_DIR/schema_pre.sql"
echo "    4. Restart server"
echo ""

exit $([ ${#FAILURES[@]} -eq 0 ] && echo 0 || echo 1)
