#!/bin/bash
# services/garage-webui.sh — Web UI for browsing/managing an existing Garage
# instance's buckets and objects (folders/files view, the same kind of thing
# Backblaze's own web console gives you for a B2 bucket).
# Part of the modular post-install system (sourced by setup.sh).
#
# Can also be run standalone on any machine:
#   sudo bash garage-webui.sh
# (Docker must already be installed when run standalone; requires
# services/garage.sh already installed on the SAME machine — this reads
# that install's admin API port/token straight out of its .env)

# ── Standalone bootstrap ──────────────────────────────────────────────────────
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    [[ "$(id -u)" == "0" ]] || { echo "Run with sudo: sudo bash $0"; exit 1; }

    _SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
    _COMMON="$_SELF_DIR/../lib/common.sh"

    if [[ -f "$_COMMON" ]]; then
        # shellcheck source=../lib/common.sh
        source "$_COMMON"
    else
        log_info()    { echo -e "\033[0;34m[INFO]\033[0m $*"; }
        log_success() { echo -e "\033[0;32m[OK]\033[0m $*"; }
        log_warning() { echo -e "\033[1;33m[WARN]\033[0m $*"; }
        log_error()   { echo -e "\033[0;31m[ERROR]\033[0m $*" >&2; }

        require_docker() {
            command -v docker &>/dev/null || {
                log_error "Docker not found. Install it first:"
                log_error "  curl -fsSL https://get.docker.com | sudo sh"
                return 1
            }
            docker compose version &>/dev/null || {
                log_error "Docker Compose plugin missing:"
                log_error "  sudo apt-get install -y docker-compose-plugin"
                return 1
            }
        }

        ensure_docker_dir_ownership() {
            chown -R "$ACTUAL_USER:$ACTUAL_USER" "$@" 2>/dev/null || true
        }

        port_in_use() {
            local _port="$1" _proto="${2:-tcp}"
            local _flag="-tlnH"
            [ "$_proto" = "udp" ] && _flag="-ulnH"
            ss "$_flag" "sport = :${_port}" 2>/dev/null | grep -q .
        }

        find_free_port() {
            local _varname="$1" _port="$2" _proto="${3:-tcp}"
            while port_in_use "$_port" "$_proto"; do
                _port=$((_port + 1))
            done
            eval "$_varname='$_port'"
        }

        generate_password() {
            local _len="${1:-32}"
            tr -dc 'A-Za-z0-9' < /dev/urandom | head -c "$_len"
        }

        prompt_text() {
            local _q="$1" _def="$2" _var="$3" _r
            [[ "${UNATTENDED:-false}" == "true" ]] && { eval "$_var='$_def'"; return; }
            read -r -p "  $_q " _r
            eval "$_var='${_r:-$_def}'"
        }

        prompt_reinstall_mode() {
            local _var="$1" _r
            [[ "${UNATTENDED:-false}" == "true" ]] && { eval "$_var='cancel'"; return; }
            echo ""
            echo "  1) Update   — refresh the image only, leave config/data as-is"
            echo "  2) Full reinstall — wipe and reconfigure from scratch"
            echo "  3) Cancel   — leave the existing install untouched"
            read -r -p "  Choice [3]: " _r
            case "$_r" in
                1) eval "$_var='update'" ;;
                2) eval "$_var='fresh'" ;;
                *) eval "$_var='cancel'" ;;
            esac
        }

        configure_caddy_for_service() { CADDY_SERVICE_CONFIGURED=false; }

        write_readme() {
            local _dir="$1"; shift
            mkdir -p "$_dir"
            cat > "$_dir/README.md"
        }
        backup_if_exists() {
            local _file="$1"
            [ -f "$_file" ] || return 0
            cp -p "$_file" "${_file}.bak.$(date +%Y%m%d-%H%M%S)" 2>/dev/null
        }
    fi

    ACTUAL_USER="${ACTUAL_USER:-${SUDO_USER:-$USER}}"
    ACTUAL_HOME="$(getent passwd "$ACTUAL_USER" 2>/dev/null | cut -d: -f6 || echo "${HOME:-/root}")"
    DOCKER_DIR="${DOCKER_DIR:-$ACTUAL_HOME/docker}"
    DRY_RUN="${DRY_RUN:-false}"
    UNATTENDED="${UNATTENDED:-false}"
    SITE_TZ="${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}"

    register_service() { :; }
    _RUN_STANDALONE=1
fi
# ─────────────────────────────────────────────────────────────────────────────

register_service garage-webui utilities "Web UI for browsing/managing an existing Garage instance's buckets and objects" 3909

install_garage-webui() {
    require_docker || return 1

    local GARAGE_DIR="$DOCKER_DIR/garage"
    local DIR="$DOCKER_DIR/garage-webui"

    if [ "$DRY_RUN" = true ]; then
        echo "[DRY-RUN] Would check for an existing services/garage.sh install at $GARAGE_DIR"
        echo "[DRY-RUN] Would create $DIR with docker-compose.yml + .env"
        echo "[DRY-RUN] Would auto-scan for a free web UI port"
        echo "[DRY-RUN] Would generate an admin username/password and bcrypt-hash it (requires Docker)"
        return 0
    fi

    if [[ ! -f "$GARAGE_DIR/.env" ]]; then
        log_error "No Garage install found at $GARAGE_DIR — this is a browser for an"
        log_error "existing Garage instance, not a replacement for it. Install Garage"
        log_error "first: sudo ./setup.sh garage"
        return 1
    fi

    local _garage_env
    _garage_env="$(cat "$GARAGE_DIR/.env")"
    local GARAGE_ADMIN_PORT GARAGE_ADMIN_TOKEN GARAGE_S3_API_PORT
    GARAGE_ADMIN_PORT="$(echo "$_garage_env" | sed -nE "s/^GARAGE_ADMIN_PORT=([0-9]+)\$/\1/p")"
    GARAGE_ADMIN_TOKEN="$(echo "$_garage_env" | sed -nE "s/^GARAGE_ADMIN_TOKEN='?([^']*)'?\$/\1/p")"
    GARAGE_S3_API_PORT="$(echo "$_garage_env" | sed -nE "s/^GARAGE_S3_API_PORT=([0-9]+)\$/\1/p")"

    if [ -z "$GARAGE_ADMIN_PORT" ] || [ -z "$GARAGE_ADMIN_TOKEN" ] || [ -z "$GARAGE_S3_API_PORT" ]; then
        log_error "Garage is installed at $GARAGE_DIR but its .env is missing the admin"
        log_error "API port/token this needs (added in a newer version of services/garage.sh)."
        log_error "Re-run Garage's own installer first to backfill it, then rerun this:"
        log_error "  sudo ./setup.sh garage   (choose \"Update\" when prompted)"
        return 1
    fi

    if [[ -f "$DIR/docker-compose.yml" && -f "$DIR/.env" ]]; then
        local MODE=""
        prompt_reinstall_mode MODE
        case "$MODE" in
            update)
                log_info "Refreshing the Garage Web UI image only — existing login is left as-is."
                ( cd "$DIR" && docker compose pull && docker compose up -d ) \
                    && log_success "Garage Web UI refreshed" \
                    || log_warning "Refresh failed — check: docker compose -f $DIR/docker-compose.yml logs"
                return 0
                ;;
            cancel)
                log_info "Leaving the existing install as-is."
                return 0
                ;;
            fresh) ;;  # fall through to the full install flow below
        esac
    fi

    echo ""
    echo "═══════════════════════════════════════════════════════"
    echo "  GARAGE WEB UI — browse/manage Garage's buckets and objects"
    echo "═══════════════════════════════════════════════════════"
    echo ""
    echo "  Connecting to the Garage instance at $GARAGE_DIR."
    echo ""

    local WEB_PORT="3909"
    find_free_port WEB_PORT "$WEB_PORT"

    local WEBUI_USER=""
    prompt_text "  Admin username:" "admin" WEBUI_USER
    WEBUI_USER="${WEBUI_USER:-admin}"

    local WEBUI_PASSWORD
    WEBUI_PASSWORD="$(generate_password 24)"

    log_info "Generating bcrypt password hash (requires Docker)..."
    local _auth_line
    _auth_line="$(docker run --rm httpd:alpine htpasswd -nbB "$WEBUI_USER" "$WEBUI_PASSWORD" 2>/dev/null)"
    if [ -z "$_auth_line" ]; then
        log_error "Couldn't generate a bcrypt hash (needs to pull httpd:alpine) — aborting."
        return 1
    fi
    # Compose re-parses its own YAML for $VAR/${VAR} interpolation, so a
    # literal $ from the bcrypt hash (embedded directly into
    # docker-compose.yml below) has to be doubled or Compose treats it as
    # the start of a variable reference. Same fix services/wg-easy.sh uses
    # for its own bcrypt PASSWORD_HASH.
    local _auth_escaped="${_auth_line//\$/\$\$}"

    mkdir -p "$DIR"
    ensure_docker_dir_ownership "$DIR"
    cd "$DIR" || return 1

    backup_if_exists docker-compose.yml
    cat > docker-compose.yml << COMPOSE
name: garage-webui

services:
  garage-webui:
    image: khairul169/garage-webui:1.1.0
    container_name: garage-webui
    restart: unless-stopped
    env_file: .env
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      API_BASE_URL: "http://host.docker.internal:${GARAGE_ADMIN_PORT}"
      S3_ENDPOINT_URL: "http://host.docker.internal:${GARAGE_S3_API_PORT}"
      API_ADMIN_KEY: "${GARAGE_ADMIN_TOKEN}"
      AUTH_USER_PASS: "${_auth_escaped}"
    ports:
      - "${WEB_PORT}:3909"
COMPOSE

    backup_if_exists .env
    cat > .env << ENV
TZ=${SITE_TZ:-$(cat /etc/timezone 2>/dev/null || echo UTC)}

# Plain-text password — only for your own reference (the container itself
# is only ever given the bcrypt hash, baked into docker-compose.yml).
GARAGE_WEBUI_USER='${WEBUI_USER}'
GARAGE_WEBUI_PASSWORD='${WEBUI_PASSWORD}'
ENV
    chmod 600 .env

    chown -R "$ACTUAL_USER:$ACTUAL_USER" "$DIR"

    log_info "Starting Garage Web UI..."
    docker compose up -d || { log_error "Failed to start Garage Web UI — check: docker compose logs"; return 1; }

    configure_caddy_for_service "Garage Web UI" "$WEB_PORT" "garage-admin"

    write_readme "$DIR" << MD
# Garage Web UI

Browser-based admin UI for an existing Garage instance ($GARAGE_DIR) — bucket
and object browser (see folders/files the way Backblaze's own web console
shows a B2 bucket), cluster health, and key management.

## Login
- URL: http://localhost:${WEB_PORT}
- Username: \`$WEBUI_USER\`
- Password: see \`.env\` (\`GARAGE_WEBUI_PASSWORD\`)

## Manage
\`\`\`bash
docker compose up -d
docker compose down
docker compose logs -f
docker compose pull && docker compose up -d
\`\`\`

This talks to Garage's admin API (\`GARAGE_ADMIN_PORT\`/\`GARAGE_ADMIN_TOKEN\` in
$GARAGE_DIR/.env) and S3 API over \`host.docker.internal\`, both on this same
machine — nothing here is sent over the network to any other box.
MD

    echo ""
    log_success "Garage Web UI ready."
    echo "  URL:      http://localhost:${WEB_PORT}"
    echo "  Username: $WEBUI_USER"
    echo "  Password: $WEBUI_PASSWORD  (saved in $DIR/.env)"
    echo ""
}

[[ "${_RUN_STANDALONE:-0}" == 1 ]] && install_garage-webui
