#!/usr/bin/env bash
# Docker management CLI for hermit containers.
# Usage: hermit-docker <command> [args...]
# Commands: up, down, attach, bash, login, logs, restart
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
CONFIG="$SCRIPT_DIR/../config.json"
CMD="${1:?Usage: hermit-docker <up|down|attach|bash|login|logs|restart|update> [args...]}"
shift

if [ ! -f "$CONFIG" ]; then
  echo "[hermit] No config found at $CONFIG" >&2
  echo "[hermit] Run /claude-code-hermit:hatch inside Claude Code first." >&2
  exit 1
fi

PROJECT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
COMPOSE="$PROJECT_DIR/docker-compose.hermit.yml"
COMPOSE_SEC="$PROJECT_DIR/docker-compose.security.yml"
RUNTIME_FILE="$PROJECT_DIR/.claude-code-hermit/state/runtime.json"

if [ ! -f "$COMPOSE" ]; then
  echo "[hermit] No docker-compose.hermit.yml found in $PROJECT_DIR" >&2
  echo "[hermit] Run /claude-code-hermit:docker-setup inside Claude Code first." >&2
  exit 1
fi

# Resolve tmux session name from config
TMUX_SESSION=$(python3 -c "
import json, sys, os
config = json.load(open(sys.argv[1]))
name = config.get('tmux_session_name', 'hermit-{project_name}')
project = os.path.basename(sys.argv[2])
print(name.replace('{project_name}', project))
" "$CONFIG" "$PROJECT_DIR" 2>/dev/null || echo "hermit")

# SERVICE is pinned to "hermit". When the security overlay is in use,
# `docker compose config --services` lists hermit-netguard (or other future
# sidecars) too — never auto-pick first; bash/login/attach must always
# target hermit.
SERVICE="hermit"

# Compose invocation: chain the security overlay if present so every command
# (up/down/exec/logs/...) sees the merged config. Idempotent — wrapper
# behavior is unchanged when the overlay is absent.
DC=(docker compose -f "$COMPOSE")
[ -f "$COMPOSE_SEC" ] && DC=(docker compose -f "$COMPOSE" -f "$COMPOSE_SEC")

_oauth_hint() {
  echo ""
  echo "Note: if the session does not start, the OAuth token may have expired."
  echo "      Run: .claude-code-hermit/bin/hermit-docker login"
}

_require_running() {
  local running
  running="$(cd "$PROJECT_DIR" && "${DC[@]}" ps --status running --format '{{.Service}}' 2>/dev/null | grep -x "$SERVICE")"
  if [ -z "$running" ]; then
    echo "[hermit] Container is not running. Start it first:" >&2
    echo "  .claude-code-hermit/bin/hermit-docker up" >&2
    exit 1
  fi
}

# Poll tmux pane for the Claude Code input box (╭─ / ╰─ box-drawing chars).
# Returns 0 when ready, 1 after 60s timeout.
_wait_for_claude_prompt() {
  local i pane
  for i in $(seq 1 30); do
    pane=$(cd "$PROJECT_DIR" && "${DC[@]}" exec -T "$SERVICE" \
      tmux capture-pane -p -t "$TMUX_SESSION:0.0" 2>/dev/null || true)
    printf '%s\n' "$pane" | tail -10 | grep -qE '╭─|╰─' && return 0
    sleep 2
  done
  return 1
}

case "$CMD" in
  up)
    echo "[hermit] Starting Docker container..."
    cd "$PROJECT_DIR" && "${DC[@]}" up -d "$@"
    echo ""
    echo "Container is up. To attach to the hermit's tmux session:"
    echo ""
    echo "  .claude-code-hermit/bin/hermit-docker attach"
    echo ""
    echo "Detach with: Ctrl+B then D"
    echo "Status: .claude-code-hermit/bin/hermit-status"
    _oauth_hint
    ;;

  down)
    FORCE=false
    [ "${1:-}" = "--force" ] && FORCE=true

    if [ "$FORCE" = false ]; then
      echo "[hermit] Requesting graceful session close..."
      # Split text and Enter into two send-keys calls with a pause — Claude Code's
      # TUI treats text+Enter in one burst as bracketed paste and Enter becomes a
      # literal newline instead of submit. Same fix as scripts/hermit-start.py.
      cd "$PROJECT_DIR" && "${DC[@]}" exec -T "$SERVICE" \
        tmux send-keys -t "$TMUX_SESSION:0.0" "/claude-code-hermit:session-close --shutdown" 2>/dev/null || true
      sleep 0.5
      cd "$PROJECT_DIR" && "${DC[@]}" exec -T "$SERVICE" \
        tmux send-keys -t "$TMUX_SESSION:0.0" Enter 2>/dev/null || true

      echo "[hermit] Waiting for session to close (up to 60s)..."
      CLOSED=false
      for i in $(seq 1 60); do
        CUR_STATE=$(jq -r '.session_state // "unknown"' "$RUNTIME_FILE" 2>/dev/null || echo "unknown")
        SHUTDOWN_DONE=$(jq -r '.shutdown_completed_at // empty' "$RUNTIME_FILE" 2>/dev/null)
        if [ "$CUR_STATE" = "idle" ] || [ -n "$SHUTDOWN_DONE" ]; then
          echo "[hermit] Session closed after ${i}s."
          CLOSED=true
          break
        fi
        sleep 1
      done
      if [ "$CLOSED" = false ]; then
        echo "[hermit] Timed out waiting for graceful close; forcing stop." >&2
      fi
    fi

    echo "[hermit] Stopping Docker container..."
    cd "$PROJECT_DIR" && "${DC[@]}" down
    echo "[hermit] Container stopped."
    ;;

  attach)
    _require_running
    echo "[hermit] Attaching to tmux session: $TMUX_SESSION"
    echo "[hermit] Detach with: Ctrl+B then D"
    cd "$PROJECT_DIR" && "${DC[@]}" exec "$SERVICE" tmux attach -t "$TMUX_SESSION"
    ;;

  bash)
    _require_running
    cd "$PROJECT_DIR" && "${DC[@]}" exec -it "$SERVICE" /bin/bash "$@"
    ;;

  login)
    _require_running
    AUTH_STATUS=$(cd "$PROJECT_DIR" && "${DC[@]}" exec -T "$SERVICE" claude auth status --json 2>/dev/null || echo '{}')
    PARSED=$(echo "$AUTH_STATUS" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('loggedIn','false')); print(d.get('email','unknown'))" 2>/dev/null || printf 'false\nunknown')
    LOGGED_IN="${PARSED%%$'\n'*}"
    EMAIL="${PARSED#*$'\n'}"
    if [ "$LOGGED_IN" = "True" ] || [ "$LOGGED_IN" = "true" ]; then
      echo "[hermit] Already authenticated as ${EMAIL}."
      echo "[hermit] To switch accounts: hermit-docker bash → claude auth logout, then re-run hermit-docker login."
    else
      echo "[hermit] Opening Claude Code REPL for login..."
      echo "[hermit] → Type /login and press Enter"
      echo "[hermit] → Open the URL in your browser and complete OAuth"
      echo "[hermit] → Paste the code back into this terminal when prompted"
      echo "[hermit] → Type /exit when done — the hermit will start automatically"
      echo ""
      cd "$PROJECT_DIR" && "${DC[@]}" exec -it "$SERVICE" claude
      if ! cd "$PROJECT_DIR" && "${DC[@]}" exec -T "$SERVICE" \
          sh -c 'test -f "${CLAUDE_CONFIG_DIR:-/home/claude/.claude}/.credentials.json"' 2>/dev/null; then
        echo "" >&2
        echo "[hermit] Login may not have completed — .credentials.json not found." >&2
        echo "[hermit] Re-run hermit-docker login to try again." >&2
        exit 1
      fi
      echo "[hermit] Credentials saved. The hermit will start shortly."
    fi
    ;;

  logs)
    cd "$PROJECT_DIR" && "${DC[@]}" logs "$@"
    ;;

  restart)
    _require_running
    if [ $# -gt 0 ]; then
      echo "[hermit] ERROR: 'restart' no longer accepts service args. Use 'hermit-docker restart' to restart everything." >&2
      exit 2
    fi
    echo "[hermit] Restarting container..."
    cd "$PROJECT_DIR" && "${DC[@]}" down && "${DC[@]}" up -d
    echo "[hermit] Container restarted."
    _oauth_hint
    ;;

  update)
    DRY_RUN=false; CC_ONLY=false; PLUGINS_ONLY=false; ASSUME_YES=false; CC_BUILD_VERSION=""; RELOAD_PLUGINS_SENT=false
    while [ $# -gt 0 ]; do
      case "$1" in
        --dry-run) DRY_RUN=true ;;
        --cc-only) CC_ONLY=true ;;
        --plugins-only) PLUGINS_ONLY=true ;;
        --yes|-y) ASSUME_YES=true ;;
        *) echo "[hermit] Unknown flag: $1" >&2; exit 2 ;;
      esac
      shift
    done
    [ "$CC_ONLY" = true ] && [ "$PLUGINS_ONLY" = true ] && \
      { echo "[hermit] --cc-only and --plugins-only are mutually exclusive" >&2; exit 2; }

    _require_running

    CC_BEFORE=$(cd "$PROJECT_DIR" && "${DC[@]}" exec -T "$SERVICE" claude --version 2>/dev/null \
      | grep -oP '[\d.]+' | head -1 || echo "unknown")
    CC_LATEST="(skipped)"
    [ "$PLUGINS_ONLY" = false ] && CC_LATEST=$(cd "$PROJECT_DIR" && "${DC[@]}" exec -T "$SERVICE" \
      npm view @anthropic-ai/claude-code version 2>/dev/null || echo "unknown") || true
    PLUGINS_COUNT_BEFORE=0
    [ "$CC_ONLY" = false ] && PLUGINS_COUNT_BEFORE=$(cd "$PROJECT_DIR" && "${DC[@]}" exec -T "$SERVICE" \
      claude plugin list --json 2>/dev/null \
      | python3 -c 'import json,sys; print(len(json.load(sys.stdin)))' || echo "0") || true
    MARKETPLACES=$(cd "$PROJECT_DIR" && "${DC[@]}" exec -T "$SERVICE" \
      claude plugin marketplace list --json 2>/dev/null \
      | python3 -c "import json,sys; d=json.load(sys.stdin); print('\n'.join(m.get('name','') for m in d if m.get('name')))" \
      2>/dev/null || echo "")

    echo "[hermit] Update plan:"
    [ "$PLUGINS_ONLY" = false ] && \
      echo "  Claude Code:  ${CC_BEFORE} → ${CC_LATEST} (image rebuild, container bounces)" || true
    if [ "$CC_ONLY" = false ]; then
      echo "  Marketplaces to refresh:"
      [ -z "$MARKETPLACES" ] && echo "    (none)" || echo "$MARKETPLACES" | sed 's/^/    - /'
      [ "$PLUGINS_ONLY" = true ] && \
        echo "  Apply method: /reload-plugins sent into tmux once claude prompt is detected" || true
    fi

    if [ "$DRY_RUN" = true ]; then
      echo "[hermit] --dry-run: no changes made."
      exit 0
    fi

    if [ "$ASSUME_YES" = false ]; then
      read -rp "Continue? [y/N] " ANS
      case "$ANS" in [yY]|[yY][eE][sS]) ;; *) echo "[hermit] Aborted."; exit 0 ;; esac
    fi

    # Graceful session close is handled by the entrypoint's SIGTERM trap (cleanup()
    # in docker-entrypoint.hermit.sh) — stop_grace_period: 60s in the compose template
    # gives it enough headroom. No need to duplicate that logic here.
    if [ "$PLUGINS_ONLY" = false ]; then
      CC_BUILD_VERSION="${CC_LATEST}"
      if [ -z "$CC_BUILD_VERSION" ] || [ "$CC_BUILD_VERSION" = "unknown" ] || [ "$CC_BUILD_VERSION" = "(skipped)" ]; then
        CC_BUILD_VERSION="latest"
      fi
      echo "[hermit] Rebuilding image with latest Claude Code (${CC_BUILD_VERSION})..."
      if ! (cd "$PROJECT_DIR" && "${DC[@]}" build --pull --build-arg "CLAUDE_CODE_VERSION=${CC_BUILD_VERSION}"); then
        echo "[hermit] Image build failed — old container still running, no changes applied." >&2
        exit 1
      fi
      echo "[hermit] Bouncing container..."
      cd "$PROJECT_DIR" && "${DC[@]}" down
      cd "$PROJECT_DIR" && "${DC[@]}" up -d

      RUNNING=""
      for _i in $(seq 1 5); do
        RUNNING=$(cd "$PROJECT_DIR" && "${DC[@]}" ps --status running --format '{{.Service}}' 2>/dev/null | grep -x "$SERVICE" || true)
        [ -n "$RUNNING" ] && break
        sleep 2
      done
      if [ -z "$RUNNING" ]; then
        echo "[hermit] Container failed to come back up." >&2
        cd "$PROJECT_DIR" && "${DC[@]}" logs --tail=30 "$SERVICE" >&2
        exit 1
      fi

      echo "[hermit] Waiting for tmux session to appear (up to 30s)..."
      for _i in $(seq 1 6); do
        (cd "$PROJECT_DIR" && "${DC[@]}" exec -T "$SERVICE" \
          tmux has-session -t "$TMUX_SESSION" 2>/dev/null) && break
        sleep 5
      done
    fi

    MARKETPLACES_OK=()
    MARKETPLACES_FAIL=()
    if [ "$CC_ONLY" = false ] && [ -n "$MARKETPLACES" ]; then
      while IFS= read -r slug; do
        [ -z "$slug" ] && continue
        echo "[hermit] Refreshing marketplace: $slug"
        if (cd "$PROJECT_DIR" && "${DC[@]}" exec -T "$SERVICE" \
            claude plugin marketplace update "$slug"); then
          MARKETPLACES_OK+=("$slug")
        else
          MARKETPLACES_FAIL+=("$slug")
          echo "[hermit] Warning: marketplace update failed for $slug" >&2
        fi
      done <<< "$MARKETPLACES"

      # Wait for the Claude Code interactive prompt before sending — tmux send-keys
      # on a pane that hasn't reached the REPL yet goes to bash, not to the skill queue.
      echo "[hermit] Waiting for Claude Code prompt (up to 60s)..."
      if _wait_for_claude_prompt; then
        echo "[hermit] Sending /reload-plugins..."
        cd "$PROJECT_DIR" && "${DC[@]}" exec -T "$SERVICE" \
          tmux send-keys -t "$TMUX_SESSION:0.0" "/reload-plugins" 2>/dev/null || \
          echo "[hermit] Warning: could not reach tmux session — /reload-plugins not sent" >&2
        sleep 0.5
        cd "$PROJECT_DIR" && "${DC[@]}" exec -T "$SERVICE" \
          tmux send-keys -t "$TMUX_SESSION:0.0" Enter 2>/dev/null || true
        RELOAD_PLUGINS_SENT=true
      else
        echo "[hermit] Warning: claude prompt not detected after 60s — /reload-plugins was not sent." >&2
        echo "[hermit]          Run it manually: hermit-docker attach, then type /reload-plugins" >&2
      fi
    fi

    if [ -n "$CC_BUILD_VERSION" ] && [ "$CC_BUILD_VERSION" != "latest" ]; then
      CC_AFTER="$CC_BUILD_VERSION"
    else
      CC_AFTER=$(cd "$PROJECT_DIR" && "${DC[@]}" exec -T "$SERVICE" claude --version 2>/dev/null \
        | grep -oP '[\d.]+' | head -1 || echo "unknown")
    fi
    PLUGINS_COUNT_AFTER=0
    [ "$CC_ONLY" = false ] && PLUGINS_COUNT_AFTER=$(cd "$PROJECT_DIR" && "${DC[@]}" exec -T "$SERVICE" \
      claude plugin list --json 2>/dev/null \
      | python3 -c 'import json,sys; print(len(json.load(sys.stdin)))' || echo "0") || true
    LOG_FILE="$PROJECT_DIR/.claude-code-hermit/state/update-history.jsonl"
    mkdir -p "$(dirname "$LOG_FILE")"
    UPDATE_MODE="full"
    [ "$CC_ONLY" = true ] && UPDATE_MODE="cc-only"
    [ "$PLUGINS_ONLY" = true ] && UPDATE_MODE="plugins-only"
    if [ ${#MARKETPLACES_OK[@]} -gt 0 ]; then
      OK_JSON=$(python3 -c 'import json,sys; print(json.dumps(sys.argv[1:]))' -- "${MARKETPLACES_OK[@]}")
    else
      OK_JSON='[]'
    fi
    if [ ${#MARKETPLACES_FAIL[@]} -gt 0 ]; then
      FAIL_JSON=$(python3 -c 'import json,sys; print(json.dumps(sys.argv[1:]))' -- "${MARKETPLACES_FAIL[@]}")
    else
      FAIL_JSON='[]'
    fi
    python3 - "$UPDATE_MODE" "$CC_BEFORE" "$CC_AFTER" \
              "$OK_JSON" "$FAIL_JSON" \
              "$PLUGINS_COUNT_BEFORE" "$PLUGINS_COUNT_AFTER" "$LOG_FILE" "$RELOAD_PLUGINS_SENT" <<'PYEOF' || \
      echo "[hermit] Warning: could not write update log" >&2
import json, sys, time
mode, cc_before, cc_after, ok_json, fail_json, pb_count, pa_count, log_file, reload_sent = sys.argv[1:]
entry = {
    'ts': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
    'trigger': 'hermit-docker update',
    'mode': mode,
    'cc_before': cc_before, 'cc_after': cc_after,
    'marketplaces_refreshed': json.loads(ok_json),
    'marketplaces_failed': json.loads(fail_json),
    'plugins_before_count': int(pb_count),
    'plugins_after_count': int(pa_count),
    'reload_plugins_sent': reload_sent == 'true',
}
with open(log_file, 'a') as f:
    f.write(json.dumps(entry) + '\n')
PYEOF

    echo ""
    echo "[hermit] Update complete."
    [ "$PLUGINS_ONLY" = false ] && echo "  Claude Code: ${CC_BEFORE} → ${CC_AFTER}" || true
    [ ${#MARKETPLACES_OK[@]} -gt 0 ] && echo "  Marketplaces refreshed: ${MARKETPLACES_OK[*]}" || true
    [ ${#MARKETPLACES_FAIL[@]} -gt 0 ] && echo "  Marketplaces failed:    ${MARKETPLACES_FAIL[*]}" || true
    if [ "$CC_ONLY" = false ]; then
      if [ "$RELOAD_PLUGINS_SENT" = true ]; then
        echo "  /reload-plugins sent — plugin updates active."
      else
        echo "  /reload-plugins not sent — attach and run it manually: hermit-docker attach"
      fi
    fi
    echo ""
    echo "Verify with:  .claude-code-hermit/bin/hermit-status"
    echo "Attach with:  .claude-code-hermit/bin/hermit-docker attach"
    echo "If the hermit plugin version bumped, run: /claude-code-hermit:hermit-evolve"
    _oauth_hint
    ;;

  *)
    echo "Usage: hermit-docker <up|down|attach|bash|login|logs|restart|update> [args...]" >&2
    exit 1
    ;;
esac
