#!/bin/sh
# oikos-web deploy script — triggered by Gitea webhook on push to dtoro/oikos-web.
# Runs on mac-mini as non-root user via launchd unit oikos-web-deploy-webhook.service.
#
# Mirrors the essentials of dtoro/oikos's scripts/deploy.sh (plans D1/D2 there):
#   D1 — CI gate: blocks deploy unless Gitea reports a green run for the SHA.
#   D2 — versioned images: tags the image v$VERSION (from VERSION file),
#        keeps the last 3 tags for rollback.
# No pg_dump / seed steps — this stack serves static files only.

# Notify on deploy failure. Uses the Oikos API to raise an event so the
# scheduler picks it up (best-effort, silent failure).
notify_deploy_failure() {
    local reason="$1"
    local sha="${SHA:-unknown}"
    echo "NOTIFY: deploy failed — $reason"
    if [ -n "${OIKOS_API_TOKEN:-}" ]; then
        curl -sf -X POST "http://localhost:8090/api/v1/events" \
            -H "Authorization: Bearer $OIKOS_API_TOKEN" \
            -H "Content-Type: application/json" \
            -d "{\"type\":\"deploy.failed\",\"severity\":\"critical\",\"source\":\"webhook\",\"data\":{\"repo\":\"oikos-web\",\"sha\":\"$sha\",\"reason\":\"$reason\"}}" \
            >/dev/null 2>&1 || true
    fi
}

set -e

REPO_DIR="${REPO_DIR:-$PWD}"
COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}"
HEALTH_URL="${HEALTH_URL:-http://localhost:8091/}"
RETRIES=${RETRIES:-30}
SLEEP=${SLEEP:-2}

# CI gate (D1). Set GITEA_URL + GITEA_TOKEN to enable; without them the gate
# is skipped with a warning (dev/local builds).
GITEA_URL="${GITEA_URL:-}"
GITEA_TOKEN="${GITEA_TOKEN:-}"
GITEA_OWNER="${GITEA_OWNER:-dtoro}"
GITEA_REPO="${GITEA_REPO:-oikos-web}"
CI_POLL_INTERVAL="${CI_POLL_INTERVAL:-15}"
CI_TIMEOUT="${CI_TIMEOUT:-1200}"

# Serialize deploys (mkdir lock — atomic on POSIX, no flock on macOS).
LOCKDIR="${LOCKDIR:-/tmp/oikos-web-deploy.lock}"
if ! mkdir "$LOCKDIR" 2>/dev/null; then
    oldpid=$(cat "$LOCKDIR/pid" 2>/dev/null || echo "")
    if [ -n "$oldpid" ] && kill -0 "$oldpid" 2>/dev/null; then
        echo "deploy already in progress (pid $oldpid) — exiting"
        exit 0
    fi
    echo "removing stale deploy lock (pid ${oldpid:-?} not running)"
    rm -rf "$LOCKDIR"
    mkdir "$LOCKDIR"
fi
echo $$ > "$LOCKDIR/pid"
trap 'rc=$?; rm -rf "$LOCKDIR" 2>/dev/null || true; if [ "$_ok" != "1" ]; then notify_deploy_failure "deploy aborted (exit $rc)"; fi' EXIT
_ok=0

cd "$REPO_DIR"

echo "=== oikos-web deploy: $(date) ==="

# Resolve the SHA we are ABOUT to deploy from the remote (read-only) so the
# CI gate can run before anything is touched.
REMOTE_FULL=$(git ls-remote origin refs/heads/main 2>/dev/null | awk '{print $1}')
if [ -z "$REMOTE_FULL" ]; then
    echo "ERROR: could not resolve origin/main (offline?) — aborting before any change"
    exit 1
fi
REMOTE_SHA=$(printf '%s' "$REMOTE_FULL" | cut -c1-12)
echo "remote SHA: $REMOTE_SHA"

# ── 1. CI gate (D1) ──────────────────────────────────────────────────────
echo "[1/6] verify CI status for $REMOTE_SHA"
verify_ci() {
    sha=$1
    if [ -z "$GITEA_URL" ] || [ -z "$GITEA_TOKEN" ]; then
        echo "SKIP: GITEA_URL/GITEA_TOKEN not set — CI gate disabled. Set both to enforce."
        return 0
    fi
    origin=$(git remote get-url origin 2>/dev/null || echo "")
    seg=
    case "$origin" in
        *@*:*)  seg=${origin##*:}; seg=${seg%.git} ;;
        http://*|https://*)  seg=${origin#*://}; seg=${seg#*/}; seg=${seg%.git} ;;
    esac
    case "$seg" in
        */*) GITEA_OWNER=${seg%%/*}; GITEA_REPO=${seg#*/} ;;
    esac

    api="$GITEA_URL/api/v1/repos/$GITEA_OWNER/$GITEA_REPO/commits/$sha/status"
    body=$(mktemp)
    elapsed=0
    saw_ci=0
    no_signal=0
    while [ "$elapsed" -lt "$CI_TIMEOUT" ]; do
        code=$(printf 'header = "Authorization: token %s"\n' "$GITEA_TOKEN" | \
            curl -sS -o "$body" -w '%{http_code}' --config - "$api" 2>/dev/null) || code="000"
        state=$(sed -n 's/.*"state"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$body" | head -n1)

        case "$code" in
            200)
                case "$state" in
                    success)
                        rm -f "$body"
                        echo "CI: green for $sha after ${elapsed}s"
                        return 0
                        ;;
                    failure|error)
                        rm -f "$body"
                        echo "ERROR: CI $state for $sha — refusing to deploy."
                        echo "       See $GITEA_URL/$GITEA_OWNER/$GITEA_REPO/actions"
                        return 1
                        ;;
                    pending|"")
                        saw_ci=1
                        no_signal=0
                        ;;
                esac
                ;;
            404)
                rm -f "$body"
                echo "WARN: Gitea has no CI status for $sha (404)."
                echo "      Is Gitea Actions configured with a runner for $GITEA_OWNER/$GITEA_REPO?"
                echo "      Proceeding without a gate."
                return 0
                ;;
            401|403)
                rm -f "$body"
                echo "WARN: GITEA_TOKEN rejected by Gitea ($code) — cannot verify CI."
                echo "      Fix the token to enforce the gate; proceeding without one."
                return 0
                ;;
            *)
                no_signal=$((no_signal + 1))
                ;;
        esac

        if [ "$saw_ci" -eq 0 ] && [ "$no_signal" -ge 4 ]; then
            rm -f "$body"
            echo "WARN: no CI signal from Gitea after ${elapsed}s (last code=$code)."
            echo "      CI may be down or misconfigured; proceeding without a gate."
            return 0
        fi

        sleep "$CI_POLL_INTERVAL"
        elapsed=$((elapsed + CI_POLL_INTERVAL))
        printf '\rCI: waiting (%ss, code=%s state=%s)...' "$elapsed" "$code" "${state:-none}"
    done
    rm -f "$body"
    echo ""
    echo "ERROR: CI did not reach a terminal state within ${CI_TIMEOUT}s for $sha — refusing to deploy."
    return 1
}
verify_ci "$REMOTE_SHA" || exit 1

# ── 2. Update working tree to the verified commit ────────────────────────
echo "[2/6] git pull (ff-only)"
git pull --ff-only origin main
# TOCTOU guard: origin/main may have advanced during the CI wait; refuse to
# ship an unverified commit — a retry verifies the new tip.
PULLED_FULL=$(git rev-parse HEAD)
if [ "$PULLED_FULL" != "$REMOTE_FULL" ]; then
    echo "ERROR: origin/main advanced during deploy (verified $REMOTE_SHA, now at $(git rev-parse --short HEAD)) — aborting; retry verifies the new tip"
    exit 1
fi
SHA=$(git rev-parse --short HEAD)
echo "SHA (deployed): $SHA"

# Resolve the deploy version AFTER pull (D2) so the tag matches the code
# being built. Compose interpolates $OIKOS_VERSION into the image: tag.
VERSION_FILE="$REPO_DIR/VERSION"
if [ -f "$VERSION_FILE" ]; then
    OIKOS_VERSION="v$(head -n1 "$VERSION_FILE" | tr -d '[:space:]')"
    export OIKOS_VERSION
    echo "VERSION: $OIKOS_VERSION"
else
    echo "WARNING: VERSION file missing — image will use :latest (rollback unavailable)"
fi

# ── 3. Build version-tagged image (D2) ───────────────────────────────────
echo "[3/6] docker compose build"
DOCKER_BUILDKIT=1 docker compose -f "$COMPOSE_FILE" build \
    --build-arg BUILDKIT_INLINE_CACHE=1

# ── 4. Rolling restart ───────────────────────────────────────────────────
echo "[4/6] docker compose up -d"
docker compose -f "$COMPOSE_FILE" up -d --remove-orphans

# ── 5. Prune old image tags — keep the 3 newest so rollback ──────────────
#    (OIKOS_VERSION=v0.x.y docker compose up) stays available.
echo "[5/6] prune old image tags (keep 3)"
if [ -n "$OIKOS_VERSION" ]; then
    images=$(docker compose -f "$COMPOSE_FILE" config --images 2>/dev/null || true)
    if [ -z "$images" ]; then
        images="oikos-web"
    fi
    printf '%s\n' $images | sed 's/:.*//' | sort -u | while read -r repo; do
        docker image ls "$repo" --format '{{.Tag}}' 2>/dev/null | grep '^v' | sort -rV | tail -n +4 | while read -r tag; do
            docker rmi "$repo:$tag" >/dev/null 2>&1 || true
        done
    done
fi

# ── 6. Health check wait (SPA answers on the published port) ─────────────
echo "[6/6] health check"
healthy=0
for i in $(seq 1 $RETRIES); do
    if curl -sf "$HEALTH_URL" > /dev/null 2>&1; then
        echo "healthy after ${i}x${SLEEP}s"
        healthy=1
        break
    fi
    sleep "$SLEEP"
done
if [ "$healthy" -ne 1 ]; then
    echo "ERROR: health check failed after $((RETRIES * SLEEP))s"
    exit 1
fi

# All steps completed successfully — clear failure trap
_ok=1
exit 0
