#!/usr/bin/env bash
# verify.sh — GuruRMM local pre-merge verification.
# Builds/checks a component the SAME way the server-side pipeline does, so you don't
# merge code that fails the post-merge build on .30. Does NOT trigger the production
# build (that's the webhook on push to main) — this is the local gate only.
#
# Usage:
#   bash verify.sh server [--check]      # SQLX_OFFLINE cargo build (or check) + .sqlx freshness warning
#   bash verify.sh agent  [--check]      # cargo build (or check) + per-OS compile-gate warning
#   bash verify.sh dashboard             # npm install + npm run build
#   bash verify.sh all    [--check]      # server + agent + dashboard
#   bash verify.sh migrations            # report highest migration number + any duplicate numbers
#
# See: projects/msp-tools/guru-rmm/docs/BUILD.md
#      projects/msp-tools/guru-rmm/deploy/build-pipeline/README.md

set -uo pipefail

# Resolve repo root (4 levels up: .claude/skills/gururmm-build/scripts/ -> repo root).
CLAUDETOOLS_ROOT="${CLAUDETOOLS_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd)}"
REPO_DIR="$CLAUDETOOLS_ROOT/projects/msp-tools/guru-rmm"

_logerr() { bash "$CLAUDETOOLS_ROOT/.claude/scripts/log-skill-error.sh" "gururmm-build" "$@" >/dev/null 2>&1 || true; }

# ASCII markers (no emojis — fleet rule).
OK="[OK]"; ERR="[ERROR]"; WARN="[WARNING]"; INFO="[INFO]"

die() { echo "$ERR $*" >&2; _logerr "$*"; exit 1; }

[ -d "$REPO_DIR" ] || die "guru-rmm checkout not found at $REPO_DIR (submodule not initialized?)"

# Host OS family.
case "$(uname -s 2>/dev/null)" in
    Linux*)              OS=linux ;;
    Darwin*)             OS=mac ;;
    MINGW*|MSYS*|CYGWIN*) OS=windows ;;
    *)                   OS=unknown ;;
esac

CMD="${1:-}"; shift || true
MODE="build"
for a in "$@"; do
    case "$a" in
        --check) MODE="check" ;;
        *) die "unknown flag: $a" ;;
    esac
done
[ -n "$CMD" ] || die "usage: verify.sh {server|agent|dashboard|all|migrations} [--check]"

have() { command -v "$1" >/dev/null 2>&1; }

# Names of files changed vs origin/main (committed) + uncommitted, restricted to a path prefix.
changed_under() {
    local prefix="$1"
    {
        git -C "$REPO_DIR" diff --name-only origin/main...HEAD -- "$prefix" 2>/dev/null
        git -C "$REPO_DIR" status --porcelain -- "$prefix" 2>/dev/null | awk '{print $2}'
    } | sort -u
}

verify_server() {
    echo "$INFO === server: $MODE (host=$OS) ==="
    have cargo || die "cargo not on PATH — install the Rust toolchain to verify server"

    # sqlx offline-cache freshness: the server build runs SQLX_OFFLINE=true against
    # server/.sqlx. If server/ Rust changed but .sqlx did not, the build can fail on a
    # cache mismatch — warn loudly (not fatal; queries may be unchanged).
    local srv_rs srv_sqlx
    srv_rs=$(changed_under "server" | grep -E '\.rs$' | wc -l | tr -d ' ')
    srv_sqlx=$(changed_under "server/.sqlx" | wc -l | tr -d ' ')
    if [ "$srv_rs" -gt 0 ] && [ "$srv_sqlx" -eq 0 ]; then
        echo "$WARN server/*.rs changed but server/.sqlx was not regenerated."
        echo "$WARN   If you added/changed SQL queries or migrations, run (in server/, DB reachable):"
        echo "$WARN     cargo sqlx prepare && git add server/.sqlx && git commit"
        echo "$WARN   Otherwise the post-merge server build may fail on the offline cache."
    fi

    local sub="build --release"; [ "$MODE" = check ] && sub="check"
    ( cd "$REPO_DIR/server" && SQLX_OFFLINE=true cargo $sub )
    local rc=$?
    [ $rc -eq 0 ] || { _logerr "server cargo $sub failed (rc=$rc)" --context "os=$OS"; echo "$ERR server $MODE FAILED (rc=$rc)"; return 1; }
    echo "$OK server $MODE passed"
}

verify_agent() {
    echo "$INFO === agent: $MODE (host=$OS) ==="
    have cargo || die "cargo not on PATH — install the Rust toolchain to verify agent"

    # Compile-gate warning: platform-gated code is only verified on its own OS.
    if [ "$OS" = windows ]; then
        echo "$WARN On Windows, cargo cannot verify Linux-gated agent code (#[cfg(target_os=\"linux\")];"
        echo "$WARN   openssl-sys won't cross-build). The Linux agent build on .30 is the real gate —"
        echo "$WARN   build on Linux too, or watch /var/log/gururmm-build-linux.log after merge."
    elif [ "$OS" = linux ]; then
        echo "$INFO Linux host: this gates Linux + cross-platform agent code. Windows-only paths"
        echo "$INFO   (MSI/tray/legacy/x86) are gated only by the Beast/Pluto Windows build."
    fi

    local sub="build --release"; [ "$MODE" = check ] && sub="check"
    ( cd "$REPO_DIR/agent" && cargo $sub )
    local rc=$?
    [ $rc -eq 0 ] || { _logerr "agent cargo $sub failed (rc=$rc)" --context "os=$OS"; echo "$ERR agent $MODE FAILED (rc=$rc)"; return 1; }
    echo "$OK agent $MODE passed (host-OS gate only — see compile-gate warning)"
}

verify_dashboard() {
    echo "$INFO === dashboard: build (host=$OS) ==="
    have npm || die "npm not on PATH — install Node to verify dashboard"
    # 'npm install' (not ci): tolerates the package.json version-bump skew the pipeline makes.
    ( cd "$REPO_DIR/dashboard" && npm install --no-audit --no-fund && npm run build )
    local rc=$?
    [ $rc -eq 0 ] || { _logerr "dashboard npm build failed (rc=$rc)" --context "os=$OS"; echo "$ERR dashboard build FAILED (rc=$rc)"; return 1; }
    [ -f "$REPO_DIR/dashboard/dist/index.html" ] || { _logerr "dashboard build produced no dist/index.html"; echo "$ERR dashboard build produced no dist/index.html"; return 1; }
    echo "$OK dashboard build passed (dist/index.html present)"
}

check_migrations() {
    echo "$INFO === migrations: numbering check ==="
    local mdir=""
    for cand in "server/migrations" "migrations"; do
        [ -d "$REPO_DIR/$cand" ] && { mdir="$REPO_DIR/$cand"; break; }
    done
    [ -n "$mdir" ] || die "no migrations directory found (looked for server/migrations, migrations)"
    echo "$INFO migrations dir: ${mdir#$REPO_DIR/}"

    # Highest NNN prefix and any duplicate numbers.
    local nums
    nums=$(ls -1 "$mdir" 2>/dev/null | grep -E '^[0-9]+_' | sed -E 's/^([0-9]+)_.*/\1/' | sort)
    [ -n "$nums" ] || { echo "$WARN no NNN_ migrations found in $mdir"; return 0; }

    local highest next dupes
    highest=$(printf '%s\n' "$nums" | tail -1)
    next=$(printf '%03d' $((10#$highest + 1)))
    echo "$OK highest migration on this checkout: $highest  ->  next free number: $next"

    dupes=$(printf '%s\n' "$nums" | uniq -d)
    if [ -n "$dupes" ]; then
        echo "$ERR duplicate migration number(s) detected: $dupes"
        printf '%s\n' "$dupes" | while read -r d; do
            ls -1 "$mdir" | grep -E "^${d}_" | sed "s/^/$ERR   /"
        done
        _logerr "duplicate migration number(s): $dupes"
        return 1
    fi
    echo "$INFO Before naming a new migration, also check the highest number on origin/main"
    echo "$INFO   (another branch may have claimed $next): git fetch && ls $(printf '%s' "${mdir#$REPO_DIR/}")"
}

rc=0
case "$CMD" in
    server)     verify_server || rc=1 ;;
    agent)      verify_agent || rc=1 ;;
    dashboard)  verify_dashboard || rc=1 ;;
    migrations) check_migrations || rc=1 ;;
    all)
        verify_server   || rc=1
        verify_agent    || rc=1
        verify_dashboard || rc=1
        ;;
    *) die "unknown component: $CMD (use server|agent|dashboard|all|migrations)" ;;
esac

if [ $rc -eq 0 ]; then
    echo "$OK verify.sh $CMD: PASS"
else
    echo "$ERR verify.sh $CMD: FAIL"
fi
exit $rc
