#!/bin/bash
# Agent Keeper uninstaller — https://www.agentkeeper.dev/uninstall.sh
#
# Removes every endpoint artifact Agent Keeper places on a user machine:
#   • LaunchAgent / systemd timer and any running agentkeeper processes
#   • CLI binary (~/.local/bin/agentkeeper.sh) and gum helper
#   • Claude Code hooks written into ~/.claude/settings.json
#   • Claude Code plugin cache + manifest entries
#   • Cursor / Copilot / Windsurf adapter hooks in the current project
#   • Cowork MCP registration
#   • State directories (~/.agentkeeper, ~/.agentkeeper-plugin)
#   • Shell profile PATH entries added by the installer
#   • macOS Keychain entries (best-effort)
#
# Usage:
#   curl -fsSL https://www.agentkeeper.dev/uninstall.sh | bash
#   curl -fsSL https://www.agentkeeper.dev/uninstall.sh | bash -s -- --dry-run
#   curl -fsSL https://www.agentkeeper.dev/uninstall.sh | bash -s -- --keep-data
#
# Flags:
#   --dry-run     Report what would be removed without changing anything
#   --keep-data   Preserve ~/.agentkeeper (config, api_key, logs)
#   --quiet       Minimal output (one line per section) — good for Kandji logs
#   --yes         No-op; accepted for parity with other uninstallers
#
# Exit codes:
#   0  success (including "nothing to remove" — idempotent)
#   1  catastrophic error — could not determine target user
#
# Kandji deployment (Self Service, one-button uninstall):
#   Command: /bin/bash -c "$(curl -fsSL https://www.agentkeeper.dev/uninstall.sh)"
#   Run as:  Logged-in user (preferred) — so ~ resolves to the real user.
#   If your Kandji library item runs as root, pass TARGET_USER=<shortname>
#   via Audit & Enforce env or wrap with:
#     su - "$(stat -f %Su /dev/console)" -c "curl -fsSL https://www.agentkeeper.dev/uninstall.sh | bash"

set -u  # don't `-e` — we want idempotent best-effort cleanup

# ── Flags ─────────────────────────────────────────────────────────────────
DRY_RUN=0
KEEP_DATA=0
QUIET=0
for arg in "$@"; do
    case "$arg" in
        --dry-run)    DRY_RUN=1 ;;
        --keep-data)  KEEP_DATA=1 ;;
        --quiet)      QUIET=1 ;;
        --yes|-y)     ;;  # accepted, ignored
        -h|--help)
            sed -n '2,40p' "$0" 2>/dev/null | sed 's/^# \{0,1\}//'
            exit 0
            ;;
        *)
            echo "Unknown flag: $arg" >&2
            echo "Try --help" >&2
            exit 1
            ;;
    esac
done

# ── Target user + home ────────────────────────────────────────────────────
# When invoked as root (e.g., some Kandji configurations), resolve the real
# user's $HOME. Otherwise use the current user.
if [ "$(id -u)" -eq 0 ] && [ -n "${SUDO_USER:-}" ] && [ "${SUDO_USER}" != "root" ]; then
    TARGET_USER="$SUDO_USER"
elif [ "$(id -u)" -eq 0 ] && [ "$(uname)" = "Darwin" ]; then
    TARGET_USER=$(stat -f %Su /dev/console 2>/dev/null)
else
    TARGET_USER=$(id -un)
fi

if [ -z "${TARGET_USER:-}" ] || [ "$TARGET_USER" = "root" ]; then
    echo "  ✗ Cannot determine target user. Set TARGET_USER env or run as the user directly." >&2
    exit 1
fi

if [ "$(id -u)" -eq 0 ]; then
    TARGET_HOME=$(eval echo "~${TARGET_USER}")
else
    TARGET_HOME="$HOME"
fi

if [ ! -d "$TARGET_HOME" ]; then
    echo "  ✗ Target home does not exist: $TARGET_HOME" >&2
    exit 1
fi

# ── Output helpers ────────────────────────────────────────────────────────
_say() { [ "$QUIET" = "1" ] || echo "$@"; }
_section() { _say ""; _say "  $*"; }
_ok() { _say "  ✓ $*"; }
_skip() { _say "  · $*"; }
_warn() { echo "  ⚠ $*"; }

_run() {
    # Runs a command as the target user (drops privileges if we're root).
    if [ "$DRY_RUN" = "1" ]; then
        _say "  [dry-run] $*"
        return 0
    fi
    if [ "$(id -u)" -eq 0 ] && [ "$(id -un)" != "$TARGET_USER" ]; then
        sudo -u "$TARGET_USER" -H bash -c "$*" 2>/dev/null
    else
        bash -c "$*" 2>/dev/null
    fi
}

_remove_path() {
    # Remove a file or directory (preserving dry-run semantics).
    local target="$1"
    if [ -e "$target" ] || [ -L "$target" ]; then
        if [ "$DRY_RUN" = "1" ]; then
            _say "  [dry-run] rm -rf $target"
        else
            rm -rf "$target" 2>/dev/null && _ok "Removed $target" || _warn "Could not remove $target"
        fi
        return 0
    fi
    return 1
}

# ── Banner ────────────────────────────────────────────────────────────────
if [ "$QUIET" = "0" ]; then
    echo ""
    echo "  Agent Keeper uninstall"
    echo "  ────────────────────"
    echo "  User:      $TARGET_USER"
    echo "  Home:      $TARGET_HOME"
    echo "  Mode:      $([ "$DRY_RUN" = "1" ] && echo 'DRY RUN (no changes)' || echo 'live')"
    [ "$KEEP_DATA" = "1" ] && echo "  Data:      preserving $TARGET_HOME/.agentkeeper"
    echo ""
fi

REMOVED_SOMETHING=0

# ── 1. Stop running processes ─────────────────────────────────────────────
_section "1. Stopping running processes"
CK_PIDS=$(pgrep -fu "$TARGET_USER" "agentkeeper\\.sh" 2>/dev/null || true)
if [ -n "$CK_PIDS" ]; then
    if [ "$DRY_RUN" = "1" ]; then
        _say "  [dry-run] kill $CK_PIDS"
    else
        echo "$CK_PIDS" | xargs -r kill 2>/dev/null
        sleep 1
        REMAINING=$(pgrep -fu "$TARGET_USER" "agentkeeper\\.sh" 2>/dev/null || true)
        [ -n "$REMAINING" ] && echo "$REMAINING" | xargs -r kill -9 2>/dev/null
        _ok "Stopped agentkeeper processes"
    fi
    REMOVED_SOMETHING=1
else
    _skip "No agentkeeper processes running"
fi

# ── 2. LaunchAgent / systemd timer ────────────────────────────────────────
_section "2. Removing scheduler"
if [ "$(uname)" = "Darwin" ]; then
    for label in com.agentkeeper.agent; do
        PLIST="$TARGET_HOME/Library/LaunchAgents/${label}.plist"
        if [ -f "$PLIST" ]; then
            _run "launchctl unload '$PLIST'"
            _remove_path "$PLIST" && REMOVED_SOMETHING=1
        fi
    done
    if [ "$REMOVED_SOMETHING" = "0" ]; then _skip "No LaunchAgent found"; fi
else
    SVC="$TARGET_HOME/.config/systemd/user/agentkeeper-agent.service"
    TMR="$TARGET_HOME/.config/systemd/user/agentkeeper-agent.timer"
    if [ -f "$SVC" ] || [ -f "$TMR" ]; then
        _run "systemctl --user disable --now agentkeeper-agent.timer"
        _remove_path "$SVC"
        _remove_path "$TMR"
        _run "systemctl --user daemon-reload"
        REMOVED_SOMETHING=1
    else
        _skip "No systemd timer found"
    fi
fi

# ── 3. CLI binary + gum ───────────────────────────────────────────────────
_section "3. Removing CLI"
for bin in "$TARGET_HOME/.local/bin/agentkeeper.sh" "$TARGET_HOME/.local/bin/agentkeeper"; do
    _remove_path "$bin" && REMOVED_SOMETHING=1
done
# gum is owned by Agent Keeper if we installed it (~/.local/bin/gum). If the user
# had gum from brew or elsewhere, it's in /opt/homebrew/bin or /usr/local/bin —
# we never touch those paths.
if [ -f "$TARGET_HOME/.local/bin/gum" ]; then
    _remove_path "$TARGET_HOME/.local/bin/gum" && REMOVED_SOMETHING=1
fi

# ── 4. Claude Code hooks in ~/.claude/settings.json ───────────────────────
_section "4. Cleaning Claude Code hooks"
CC_SETTINGS="$TARGET_HOME/.claude/settings.json"
if [ -f "$CC_SETTINGS" ]; then
    if [ "$DRY_RUN" = "1" ]; then
        _say "  [dry-run] would prune agentkeeper hooks from $CC_SETTINGS"
    else
        BACKUP="${CC_SETTINGS}.pre-agentkeeper-uninstall.$(date +%Y%m%d%H%M%S)"
        cp "$CC_SETTINGS" "$BACKUP" 2>/dev/null
        python3 - "$CC_SETTINGS" <<'PYEOF'
import json, sys, os
path = sys.argv[1]
try:
    with open(path) as f:
        data = json.load(f)
except Exception:
    sys.exit(0)

def is_agentkeeper_hook(h):
    if not isinstance(h, dict):
        return False
    if h.get("_agentkeeper") is True:
        return True
    url = h.get("url", "")
    cmd = h.get("command", "")
    return (
        isinstance(url, str) and "www.agentkeeper.dev" in url
    ) or (
        isinstance(cmd, str) and ("agentkeeper" in cmd or ".agentkeeper/" in cmd)
    )

def clean(hooks_root):
    if not isinstance(hooks_root, dict):
        return hooks_root, 0
    removed = 0
    for event_name, matchers in list(hooks_root.items()):
        if not isinstance(matchers, list):
            continue
        new_matchers = []
        for m in matchers:
            if not isinstance(m, dict):
                new_matchers.append(m)
                continue
            inner = m.get("hooks")
            if isinstance(inner, list):
                kept = [h for h in inner if not is_agentkeeper_hook(h)]
                removed += len(inner) - len(kept)
                if kept:
                    m = {**m, "hooks": kept}
                    new_matchers.append(m)
            else:
                if not is_agentkeeper_hook(m):
                    new_matchers.append(m)
                else:
                    removed += 1
        if new_matchers:
            hooks_root[event_name] = new_matchers
        else:
            del hooks_root[event_name]
    return hooks_root, removed

total_removed = 0
if isinstance(data.get("hooks"), dict):
    data["hooks"], n = clean(data["hooks"])
    total_removed += n
    if not data["hooks"]:
        del data["hooks"]

# Remove agentkeeper-security MCP server entry if present
mcp = data.get("mcpServers")
if isinstance(mcp, dict):
    for name in list(mcp.keys()):
        if "agentkeeper" in name.lower():
            del mcp[name]
            total_removed += 1
    if not mcp:
        data.pop("mcpServers", None)

with open(path, "w") as f:
    json.dump(data, f, indent=2)
    f.write("\n")

print(f"  ✓ Removed {total_removed} agentkeeper entries from settings.json (backup: {os.path.basename(path)}.pre-agentkeeper-uninstall.*)")
PYEOF
        REMOVED_SOMETHING=1
    fi
else
    _skip "No ~/.claude/settings.json"
fi

# ── 5. Claude Code plugin cache + installed_plugins manifest ──────────────
_section "5. Removing Claude Code plugin"
PLUGINS_CACHE="$TARGET_HOME/.claude/plugins/cache"
if [ -d "$PLUGINS_CACHE" ]; then
    for d in "$PLUGINS_CACHE"/*/rad-security/claude-code-plugin "$PLUGINS_CACHE"/*/*/agentkeeper-code "$PLUGINS_CACHE"/*/*/agentkeeper-cowork; do
        [ -d "$d" ] && _remove_path "$d" && REMOVED_SOMETHING=1
    done
fi
MANIFEST="$TARGET_HOME/.claude/plugins/installed_plugins.json"
if [ -f "$MANIFEST" ]; then
    if [ "$DRY_RUN" = "1" ]; then
        _say "  [dry-run] would prune agentkeeper entries from $MANIFEST"
    else
        python3 - "$MANIFEST" <<'PYEOF'
import json, sys
path = sys.argv[1]
try:
    with open(path) as f:
        data = json.load(f)
except Exception:
    sys.exit(0)
plugins = data.get("plugins") or {}
removed = []
for slug in list(plugins.keys()):
    if "agentkeeper" in slug.lower() or "rad-security" in slug.lower():
        removed.append(slug)
        del plugins[slug]
data["plugins"] = plugins
with open(path, "w") as f:
    json.dump(data, f, indent=2)
    f.write("\n")
if removed:
    print(f"  ✓ Removed {len(removed)} plugin entries: {', '.join(removed)}")
PYEOF
        REMOVED_SOMETHING=1
    fi
fi

# ── 6. Cowork MCP registration ────────────────────────────────────────────
_section "6. Removing Cowork MCP registration"
COWORK_BASE="$TARGET_HOME/Library/Application Support/com.anthropic.claudefordesktop"
if [ -d "$COWORK_BASE" ]; then
    for d in "$COWORK_BASE"/plugins/agentkeeper-cowork "$COWORK_BASE"/plugins/agentkeeper-security; do
        [ -d "$d" ] && _remove_path "$d" && REMOVED_SOMETHING=1
    done
fi
# Also check any .mcp.json files that registered agentkeeper-security
for mcpf in "$TARGET_HOME/.mcp.json" "$TARGET_HOME/.claude/.mcp.json" "$COWORK_BASE/.mcp.json"; do
    if [ -f "$mcpf" ]; then
        if [ "$DRY_RUN" = "1" ]; then
            _say "  [dry-run] would prune agentkeeper entries from $mcpf"
        else
            python3 - "$mcpf" <<'PYEOF'
import json, sys
path = sys.argv[1]
try:
    with open(path) as f:
        data = json.load(f)
except Exception:
    sys.exit(0)
servers = data.get("mcpServers")
if not isinstance(servers, dict):
    sys.exit(0)
removed = []
for name in list(servers.keys()):
    if "agentkeeper" in name.lower():
        removed.append(name)
        del servers[name]
if removed:
    data["mcpServers"] = servers
    with open(path, "w") as f:
        json.dump(data, f, indent=2)
        f.write("\n")
    print(f"  ✓ Pruned {path}: {', '.join(removed)}")
PYEOF
        fi
    fi
done

# ── 7. Cursor / Copilot / Windsurf hooks (project-scoped best-effort) ─────
_section "7. Removing IDE adapter hooks in CWD"
# These live in the project tree, not $HOME. We only clean the current working
# directory — deeper fleet cleanup belongs in the MDM policy.
if [ -n "${PWD:-}" ] && [ "$PWD" != "/" ]; then
    for f in \
        "$PWD/.cursor/hooks.json" \
        "$PWD/.github/hooks/agentkeeper.json" \
        "$PWD/.windsurf/hooks.json"; do
        if [ -f "$f" ]; then
            # Only remove if the file looks agentkeeper-owned (has _agentkeeper marker)
            if grep -q '"_agentkeeper"[[:space:]]*:[[:space:]]*true' "$f" 2>/dev/null; then
                _remove_path "$f" && REMOVED_SOMETHING=1
            else
                _skip "$f exists but is not agentkeeper-owned (left untouched)"
            fi
        fi
    done
fi

# ── 8. State directories ──────────────────────────────────────────────────
_section "8. Removing state directories"
_remove_path "$TARGET_HOME/.agentkeeper-plugin" && REMOVED_SOMETHING=1
if [ "$KEEP_DATA" = "1" ]; then
    _skip "Preserving $TARGET_HOME/.agentkeeper (--keep-data)"
else
    _remove_path "$TARGET_HOME/.agentkeeper" && REMOVED_SOMETHING=1
fi

# ── 9. Shell profile PATH entries ─────────────────────────────────────────
_section "9. Cleaning shell profile PATH entries"
for rc in "$TARGET_HOME/.zshrc" "$TARGET_HOME/.bashrc" "$TARGET_HOME/.profile"; do
    if [ -f "$rc" ] && grep -qF '# Added by Agent Keeper' "$rc" 2>/dev/null; then
        if [ "$DRY_RUN" = "1" ]; then
            _say "  [dry-run] would strip '# Added by Agent Keeper' block from $rc"
        else
            # Delete the comment line + the immediately-following export PATH line.
            # Works on both GNU sed and BSD sed (macOS).
            TMP="${rc}.agentkeeper-uninstall.$$"
            awk '
                /^# Added by Agent Keeper$/ { skip=2; next }
                skip > 0 { skip--; next }
                { print }
            ' "$rc" > "$TMP" && mv "$TMP" "$rc"
            _ok "Cleaned PATH block from $rc"
            REMOVED_SOMETHING=1
        fi
    fi
done

# ── 10. macOS Keychain entries (best-effort) ──────────────────────────────
if [ "$(uname)" = "Darwin" ]; then
    _section "10. Clearing Keychain entries"
    KC_REMOVED=0
    if [ "$DRY_RUN" = "0" ]; then
        # Cleared silently — we don't enumerate, we just try known service names.
        for svc in agentkeeper agentkeeper-api agentkeeper-device; do
            while security delete-generic-password -s "$svc" >/dev/null 2>&1; do
                KC_REMOVED=$((KC_REMOVED + 1))
            done
        done
        if [ "$KC_REMOVED" -gt 0 ]; then
            _ok "Removed $KC_REMOVED Keychain entries"
            REMOVED_SOMETHING=1
        else
            _skip "No Keychain entries found"
        fi
    else
        _say "  [dry-run] would delete entries for services: agentkeeper, agentkeeper-api, agentkeeper-device"
    fi
fi

# ── Summary ───────────────────────────────────────────────────────────────
echo ""
if [ "$DRY_RUN" = "1" ]; then
    echo "  Dry run complete. Re-run without --dry-run to apply."
elif [ "$REMOVED_SOMETHING" = "1" ]; then
    echo "  ✓ Agent Keeper uninstalled."
    [ "$QUIET" = "0" ] && cat <<EOF

  Manual follow-ups (all optional):
    • Revoke the device from your org at https://www.agentkeeper.dev/hosts
    • Remove repo-level hooks in .claude/settings.json for any project that committed them
    • Reload shells: exec \$SHELL
EOF
else
    echo "  Nothing to remove — Agent Keeper is not installed."
fi
echo ""
exit 0
