#!/usr/bin/env bash
# Reset the smoke-test runtime state (tasks, sessions, messages, journals,
# journal entries, notifications, audit, waiting, work_sessions, groups)
# while preserving the project scaffolding (agents, projects, channels).
#
# Also resets agent git workspaces to a clean state on the default
# branch — uncommitted edits or stale feature branches from prior runs
# trap agents that try to claim a fresh task (checkout fails on dirty
# tree, branch names collide).
#
# Works from the host or from inside the orchestrator container — the
# script auto-detects.
#
# Usage:
#   ./scripts/reset_runtime_state.sh           # local docker
#   ssh renzof-nas.local "sudo bash -s" < scripts/reset_runtime_state.sh
#                                               # remote over ssh
#
# Env:
#   WORKSPACES_ROOT — override workspace root (default: tries
#     /volume1/roboco/data/workspaces then /data/workspaces; skip reset
#     if none exist).
#   SKIP_WORKSPACE_RESET=1 — skip the workspace cleanup step entirely
#     (DB + Redis only).
#   FULL_RESET=1 — opt-in aggressive clean-slate: after the DB + Redis wipe,
#     delete everything under the roboco data root EXCEPT ollama/postgres/redis
#     (overridable via ROBOCO_DATA_ROOT) and clear the persisted agent Claude
#     session dirs listed in ROBOCO_CLAUDE_STATE_DIRS (space-separated). Skips
#     the per-workspace git reset (the clones are removed and re-cloned).

set -euo pipefail

SQL_FILE="$(dirname "$0")/reset_runtime_state.sql"

if [ ! -f "$SQL_FILE" ]; then
    echo "reset_runtime_state.sql not found next to this script" >&2
    exit 1
fi

# Prefer sudo when available (NAS env); fall back to plain docker.
DOCKER="docker"
if command -v sudo >/dev/null 2>&1 && [ "$(id -u)" -ne 0 ]; then
    DOCKER="sudo docker"
fi

if ! $DOCKER ps --format '{{.Names}}' | grep -q '^roboco-postgres$'; then
    echo "roboco-postgres container not running; start the stack first." >&2
    exit 1
fi

# Stop all running agent containers first so they don't keep writing to
# the DB while we're wiping it (journal FK violations, task-not-found, etc.).
# `grep` returns 1 when no matches; combined with `set -o pipefail` + `set -e`
# that would abort the entire script before the SQL wipe ever runs. Capture
# the list with `|| true` so a no-match exit is benign.
echo ">>> Stopping all agent containers..."
agents_running=$($DOCKER ps --format '{{.Names}}' | grep '^roboco-agent-' || true)
if [ -n "$agents_running" ]; then
    while IFS= read -r container; do
        echo "    Stopping $container"
        $DOCKER stop -t 5 "$container" >/dev/null 2>&1 || true
        $DOCKER rm -f "$container" >/dev/null 2>&1 || true
    done <<< "$agents_running"
fi

echo ">>> Wiping runtime DB state (preserving agents/projects/channels)..."
$DOCKER exec -i roboco-postgres psql -U roboco -d roboco < "$SQL_FILE"

# Flush Redis — it caches permission checks, session lookups, dispatcher
# heartbeats, and Redis Streams for events. Stale entries after a wipe
# mask the clean state (e.g. cached agent metrics). `FLUSHDB` drops
# everything in the default DB (which is all we use), preserving Redis
# auth/config.
if $DOCKER ps --format '{{.Names}}' | grep -q '^roboco-redis$'; then
    echo ">>> Flushing Redis cache..."
    $DOCKER exec roboco-redis redis-cli FLUSHDB | sed 's/^/    /'
fi

# Optional full clean-slate (opt-in via FULL_RESET=1): wipe everything under the
# roboco data root EXCEPT the persistent service stores (ollama / postgres /
# redis), and clear the persisted agent Claude session state that would
# otherwise replay across runs. Default OFF — the workspace git-reset below is
# the usual path. The data root is resolved from the workspaces root's parent
# (or ROBOCO_DATA_ROOT).
#
# Claude session state lives under the mounted Claude home (ROBOCO_HOST_CLAUDE_DIR,
# the same dir the agent containers mount). When ROBOCO_CLAUDE_STATE_DIRS is unset
# we clear the conversation-transcript and todo subdirs of that home — the state
# that replays across runs — and NEVER the home root itself, which holds
# `.credentials.json` (wiping it would log the host out of Claude). Override
# ROBOCO_CLAUDE_STATE_DIRS (space-separated) to target different paths.
if [ "${FULL_RESET:-0}" = "1" ]; then
    DATA_ROOT="${ROBOCO_DATA_ROOT:-}"
    if [ -z "$DATA_ROOT" ]; then
        for candidate in /volume1/roboco/data /data; do
            if [ -d "$candidate/workspaces" ]; then
                DATA_ROOT="$candidate"
                break
            fi
        done
    fi
    if [ -n "$DATA_ROOT" ] && [ -d "$DATA_ROOT" ]; then
        echo ">>> FULL_RESET: wiping $DATA_ROOT/* except ollama/postgres/redis ..."
        for entry in "$DATA_ROOT"/*; do
            [ -e "$entry" ] || continue
            case "$(basename "$entry")" in
                ollama | postgres | redis)
                    echo "    keep $(basename "$entry")"
                    ;;
                *)
                    echo "    wipe $(basename "$entry")"
                    rm -rf "$entry"
                    ;;
            esac
        done
    else
        echo ">>> FULL_RESET: no data root resolved — skipping data wipe."
    fi
    CLAUDE_HOME="${ROBOCO_HOST_CLAUDE_DIR:-$HOME/.claude}"
    CLAUDE_STATE_DIRS="${ROBOCO_CLAUDE_STATE_DIRS:-$CLAUDE_HOME/projects $CLAUDE_HOME/todos}"
    for cdir in $CLAUDE_STATE_DIRS; do
        # Never wipe the Claude home itself — credentials live there.
        case "$cdir" in
            "$CLAUDE_HOME" | "$CLAUDE_HOME/")
                echo "    refusing to clear Claude home $cdir (holds credentials)"
                continue
                ;;
        esac
        if [ -e "$cdir" ]; then
            echo "    clearing Claude session state $cdir"
            rm -rf "$cdir"
        fi
    done
    echo ">>> FULL_RESET done."
    exit 0
fi

# Workspace reset — each agent has a private git clone at
# {root}/{project}/{team}/{agent}. Leftover staged/untracked edits and
# feature branches from a previous run will fail the claim→start
# workflow (the `roboco_task_start` handler aborts on UNCOMMITTED_CHANGES
# and branch create collides with an already-existing feature branch).
# We bring each clone back to origin/<default> with a hard reset +
# `git clean -fdx` + local feature-branch prune.
if [ "${SKIP_WORKSPACE_RESET:-0}" = "1" ]; then
    echo ">>> Skipping workspace reset (SKIP_WORKSPACE_RESET=1)."
    echo ">>> Done."
    exit 0
fi

# Resolve workspace root. Explicit env wins; then the NAS path; then the
# in-container path. If none exist, skip the reset (fresh deploy —
# there's nothing to clean yet).
_resolve_workspaces_root() {
    if [ -n "${WORKSPACES_ROOT:-}" ]; then
        echo "$WORKSPACES_ROOT"
        return
    fi
    for candidate in /volume1/roboco/data/workspaces /data/workspaces; do
        if [ -d "$candidate" ]; then
            echo "$candidate"
            return
        fi
    done
    echo ""
}

WORKSPACES_ROOT=$(_resolve_workspaces_root)
if [ -z "$WORKSPACES_ROOT" ]; then
    echo ">>> No workspaces directory found — skipping workspace reset."
    echo ">>> Done."
    exit 0
fi

echo ">>> Resetting agent workspaces under $WORKSPACES_ROOT ..."

_reset_workspace() {
    local workspace="$1"
    if [ ! -d "$workspace/.git" ]; then
        return
    fi

    # Each clone is owned by the agent uid (typically the invoking
    # user on the NAS, uid 1000 inside containers). Running git as
    # that user keeps .git/logs from being rewritten root-owned.
    local owner
    owner=$(stat -c '%U' "$workspace" 2>/dev/null || stat -f '%Su' "$workspace")
    local as_owner=""
    if command -v sudo >/dev/null 2>&1 && [ "$owner" != "$(id -un)" ]; then
        as_owner="sudo -u $owner"
    fi

    # Find default branch once — `git remote show origin` can fail if
    # the repo has no origin or auth is missing (private clones keep no
    # persisted PAT), so fall back to main, then master. We swallow both
    # stderr AND the exit code: under `set -e` + `pipefail` a 128 from a
    # credential-less fetch would otherwise abort the whole reset before
    # the local-branch fallback below.
    local default
    default=$(
        { $as_owner git -C "$workspace" remote show origin 2>/dev/null \
            | awk '/HEAD branch/{print $3}'; } || true
    )
    if [ -z "$default" ] || [ "$default" = "(unknown)" ]; then
        if $as_owner git -C "$workspace" show-ref --verify --quiet refs/heads/main; then
            default="main"
        elif $as_owner git -C "$workspace" show-ref --verify --quiet refs/heads/master; then
            default="master"
        else
            echo "    SKIP $workspace (no default branch resolved)"
            return
        fi
    fi

    # Fetch is best-effort — if origin is unreachable we still want to
    # reset locally so the next claim isn't blocked by dirty state.
    $as_owner git -C "$workspace" fetch --prune --quiet origin 2>/dev/null || true
    $as_owner git -C "$workspace" reset --hard --quiet HEAD 2>/dev/null || true
    $as_owner git -C "$workspace" checkout --quiet "$default" 2>/dev/null \
        || $as_owner git -C "$workspace" checkout --quiet -B "$default" "origin/$default" 2>/dev/null \
        || true
    # Align the default branch with the remote; swallow failures when
    # origin isn't reachable (we're still better off than a dirty tree).
    $as_owner git -C "$workspace" reset --hard --quiet "origin/$default" 2>/dev/null || true
    $as_owner git -C "$workspace" clean -fdx --quiet 2>/dev/null || true

    # Delete every local branch except the default — these are
    # smoke-test feature branches that would otherwise collide with
    # next-run branch creation (`fatal: branch already exists`).
    # `grep -v` returning 1 when only the default exists is normal;
    # `|| true` on the pipe prevents `pipefail` + `set -e` from
    # aborting on a clean workspace.
    local all_branches stray_branches
    all_branches=$(
        $as_owner git -C "$workspace" for-each-ref \
            --format='%(refname:short)' refs/heads 2>/dev/null || true
    )
    stray_branches=$(echo "$all_branches" | grep -vxF "$default" || true)
    if [ -n "$stray_branches" ]; then
        while IFS= read -r branch; do
            [ -z "$branch" ] && continue
            $as_owner git -C "$workspace" branch -D --quiet "$branch" \
                2>/dev/null || true
        done <<< "$stray_branches"
    fi

    echo "    OK   $workspace (→ $default)"
}

# Workspace layout is {root}/{project}/{team}/{agent} — depth 3 under
# WORKSPACES_ROOT. Null-separated output handles paths with spaces.
count=0
while IFS= read -r -d '' workspace; do
    _reset_workspace "$workspace"
    count=$((count + 1))
done < <(find "$WORKSPACES_ROOT" -mindepth 3 -maxdepth 3 -type d -print0)

if [ "$count" -eq 0 ]; then
    echo "    (no workspaces to reset)"
fi

echo ">>> Done."
