#!/usr/bin/bash

#
# rgoon-ctl - RepoGoon Control Tool
# 
# CLI tool for managing RepoGoon server configuration and operations.
#
# Usage:
#   ./rgoon-ctl --setup              Initial installation wizard
#   ./rgoon-ctl --generate-internal-secret
#                                   Generate server.internalSecret if missing
#   ./rgoon-ctl start|stop|restart   Service management
#                                   Uses container process handling in containers
#   ./rgoon-ctl show [key]           Show configuration or a dot-path value
#   ./rgoon-ctl show --json [key]    Show configuration as JSON
#   ./rgoon-ctl set <key> <value>    Set a scalar configuration value
#   ./rgoon-ctl set --json <key> <json>
#                                   Set an array or object value
#   ./rgoon-ctl validate             Validate configuration
#   ./rgoon-ctl test-db              Test database connection
#   ./rgoon-ctl init-db              Initialize database schema
#   ./rgoon-ctl --help               Show help
#

set -e

# Script directory detection
# Detect if running from installed location (/usr/bin) or development
SCRIPT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

if [ "$SCRIPT_PATH" = "/usr/bin" ] || [ "$SCRIPT_PATH" = "/bin" ]; then
    # Installed via RPM - use FHS paths
    APP_DIR="/usr/share/repogoon"
    CONFIG_FILE="/etc/repogoon/config.yml"
    CONFIG_TEMPLATE_FILE="/etc/repogoon/config.yml.default"
    PID_FILE="/run/repogoon/repogoon.pid"
    LOG_FILE="/var/log/repogoon/repogoon.log"
    DATA_DIR="/var/lib/repogoon"
else
    # Development mode - use script directory
    APP_DIR="$SCRIPT_PATH"
    CONFIG_FILE="${SCRIPT_PATH}/config.yml"
    CONFIG_TEMPLATE_FILE="${SCRIPT_PATH}/config.yml.example"
    PID_FILE="${SCRIPT_PATH}/.repogoon.pid"
    LOG_FILE="${SCRIPT_PATH}/repogoon.log"
    DATA_DIR="${SCRIPT_PATH}"
fi

# Legacy compatibility
SCRIPT_DIR="$APP_DIR"
TSX_IMPORT="${SCRIPT_DIR}/node_modules/tsx/dist/loader.mjs"

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m' # No Color

# Print functions
print_header() {
    echo -e "\n${BOLD}${CYAN}═══════════════════════════════════════════════════════${NC}"
    echo -e "${BOLD}${CYAN}  $1${NC}"
    echo -e "${BOLD}${CYAN}═══════════════════════════════════════════════════════${NC}\n"
}

print_section() {
    echo -e "\n${BOLD}$1${NC}"
    echo -e "${CYAN}────────────────────${NC}"
}

print_success() {
    echo -e "${GREEN}✓${NC} $1"
}

print_error() {
    echo -e "${RED}✗${NC} $1" >&2
}

print_warning() {
    echo -e "${YELLOW}⚠${NC} $1"
}

print_info() {
    echo -e "${BLUE}ℹ${NC} $1"
}

command_exists() {
    command -v "$1" >/dev/null 2>&1
}

is_systemd_install() {
    [ "$SCRIPT_PATH" = "/usr/bin" ] || [ "$SCRIPT_PATH" = "/bin" ]
}

is_container_install() {
    [ "${REPOGOON_CONTAINER:-}" = "true" ] || [ -f "/.dockerenv" ] || [ -f "/run/.containerenv" ]
}

uses_systemd_service() {
    is_systemd_install && ! is_container_install
}

get_server_port() {
    local port
    port=$(yaml_get '.server.port' "$CONFIG_FILE" 2>/dev/null)
    echo "${port:-3001}"
}

is_port_listening() {
    local port="${1:-3001}"

    if command_exists ss; then
        ss -tulnH 2>/dev/null | awk -v port="$port" '
            {
                n = split($5, parts, ":");
                if (parts[n] == port) {
                    found = 1;
                }
            }
            END {
                exit found ? 0 : 1;
            }
        '
        return $?
    fi

    if command_exists curl; then
        curl -fsS "http://127.0.0.1:${port}/api/health" >/dev/null 2>&1
        return $?
    fi

    return 1
}

wait_for_server_port() {
    local port="${1:-3001}"
    local attempts="${2:-10}"

    while [ "$attempts" -gt 0 ]; do
        if is_port_listening "$port"; then
            return 0
        fi

        sleep 1
        attempts=$((attempts - 1))
    done

    return 1
}

get_local_server_url() {
    local port
    port=$(get_server_port)
    echo "http://localhost:${port}"
}

get_public_server_url() {
    local domain
    domain=$(yaml_get '.server.domain' "$CONFIG_FILE" 2>/dev/null)

    if [ -z "$domain" ] || [ "$domain" = "null" ]; then
        return 0
    fi

    local ssl_enabled
    ssl_enabled=$(yaml_get '.ssl.enabled' "$CONFIG_FILE" 2>/dev/null)
    local protocol="http"
    [ "$ssl_enabled" = "true" ] && protocol="https"
    echo "${protocol}://${domain}"
}

# Prompt for input with default value
prompt() {
    local prompt_text="$1"
    local default_value="$2"
    local var_name="$3"
    local is_password="$4"
    
    if [ -n "$default_value" ]; then
        prompt_text="${prompt_text} [${default_value}]"
    fi
    
    if [ "$is_password" = "true" ]; then
        read -s -p "${prompt_text}: " input
        echo
    else
        read -p "${prompt_text}: " input
    fi
    
    if [ -z "$input" ]; then
        input="$default_value"
    fi
    
    eval "$var_name='$input'"
}

# Prompt for yes/no
prompt_yn() {
    local prompt_text="$1"
    local default="$2"  # 'y' or 'n'
    local var_name="$3"
    
    if [ "$default" = "y" ]; then
        prompt_text="${prompt_text} [Y/n]"
    else
        prompt_text="${prompt_text} [y/N]"
    fi
    
    read -p "${prompt_text}: " input
    input=$(echo "$input" | tr '[:upper:]' '[:lower:]')
    
    if [ -z "$input" ]; then
        input="$default"
    fi
    
    if [ "$input" = "y" ] || [ "$input" = "yes" ]; then
        eval "$var_name=true"
    else
        eval "$var_name=false"
    fi
}

# Prompt for selection
prompt_select() {
    local prompt_text="$1"
    local options="$2"  # Space-separated list
    local default="$3"
    local var_name="$4"
    
    IFS=' ' read -ra opt_array <<< "$options"
    
    local i=1
    for opt in "${opt_array[@]}"; do
        echo "  $i) $opt"
        ((i++))
    done
    
    read -p "${prompt_text} [${default}]: " input
    
    if [ -z "$input" ]; then
        input="$default"
    fi
    
    # Convert number to option name
    if [[ "$input" =~ ^[0-9]+$ ]]; then
        local idx=$((input - 1))
        if [ $idx -ge 0 ] && [ $idx -lt ${#opt_array[@]} ]; then
            input="${opt_array[$idx]}"
        fi
    fi
    
    eval "$var_name='$input'"
}

# Generate random password
generate_password() {
    local length="${1:-16}"
    cat /dev/urandom | tr -dc 'A-Za-z0-9!@#$%^&*' | head -c "$length"
}

# Generate random string (alphanumeric only)
generate_secret() {
    local length="${1:-64}"
    cat /dev/urandom | tr -dc 'A-Za-z0-9' | head -c "$length"
}

# Run the structured config helper. This keeps yq and YAML fallback behavior
# in one place instead of duplicating edit logic in Bash.
run_tsx_helper() {
    if [ -f "$TSX_IMPORT" ]; then
        (
            cd "$SCRIPT_DIR"
            node --import "$TSX_IMPORT" "$@"
        )
        return $?
    fi

    (
        cd "$SCRIPT_DIR"
        node --import tsx "$@"
    )
}

config_cli() {
    local command="$1"
    shift

    REPOGOON_CTL_CONFIG_FILE="$CONFIG_FILE" \
    run_tsx_helper server/scripts/config-cli.ts "$command" "$@"
}

# Set YAML value using the shared config helper
yaml_set() {
    local key="$1"
    local value="$2"
    local file="$3"

    REPOGOON_CTL_CONFIG_FILE="$file" \
    run_tsx_helper server/scripts/config-cli.ts set "$key" "$value"
}

yaml_set_json() {
    local key="$1"
    local value="$2"
    local file="$3"

    REPOGOON_CTL_CONFIG_FILE="$file" \
    run_tsx_helper server/scripts/config-cli.ts set --json "$key" "$value"
}

csv_to_json_array() {
    local raw_value="$1"

    node --input-type=module -e '
        const raw = process.argv[1] ?? "";
        const items = raw
            .split(",")
            .map(value => value.trim())
            .filter(Boolean);
        process.stdout.write(JSON.stringify(items));
    ' "$raw_value"
}

trust_proxy_to_json() {
    local raw_value="$1"

    node --input-type=module -e '
        const raw = (process.argv[1] ?? "").trim();
        const normalized = raw.toLowerCase();
        if (["true", "yes", "y", "on"].includes(normalized)) {
            process.stdout.write("true");
            process.exit(0);
        }
        if (["false", "no", "n", "off"].includes(normalized)) {
            process.stdout.write("false");
            process.exit(0);
        }
        if (/^-?\d+$/.test(raw)) {
            process.stdout.write(raw);
            process.exit(0);
        }
        if (raw.includes(",")) {
            const values = raw.split(",").map(value => value.trim()).filter(Boolean);
            process.stdout.write(JSON.stringify(values));
            process.exit(0);
        }
        process.stdout.write(JSON.stringify(raw));
    ' "$raw_value"
}

normalize_acme_server() {
    local raw_value
    raw_value="$(echo "${1:-}" | xargs)"

    if [ -z "$raw_value" ]; then
        echo ""
        return 0
    fi

    if [[ "$raw_value" == http://* ]] || [[ "$raw_value" == https://* ]]; then
        echo "$raw_value"
        return 0
    fi

    echo "https://${raw_value}/acme/acme/directory"
}

ensure_httpd_network_connect() {
    local selinux_mode

    if ! command_exists getenforce; then
        print_info "getenforce not found; skipping SELinux HTTP proxy configuration."
        return 1
    fi

    selinux_mode="$(getenforce 2>/dev/null || echo unknown)"
    case "$selinux_mode" in
        Disabled|disabled|unknown)
            print_info "SELinux is not enforcing; skipping HTTP proxy SELinux changes."
            return 1
            ;;
    esac

    if ! command_exists setsebool; then
        print_info "setsebool not found; skipping SELinux HTTP proxy configuration."
        return 1
    fi

    if sudo setsebool -P httpd_can_network_connect 1 >/dev/null 2>&1; then
        print_success "Enabled SELinux httpd_can_network_connect for nginx upstream access"
        return 0
    fi

    print_warning "Failed to enable SELinux httpd_can_network_connect"
    return 1
}

ensure_firewalld_services() {
    local desired_services=("$@")
    local changed_services=()
    local service

    if [ ${#desired_services[@]} -eq 0 ]; then
        return 0
    fi

    if ! command_exists firewall-cmd; then
        print_info "firewalld CLI not found; skipping automatic firewall changes."
        return 1
    fi

    if ! firewall-cmd --state >/dev/null 2>&1; then
        print_info "firewalld is not running; skipping automatic firewall changes."
        return 1
    fi

    for service in "${desired_services[@]}"; do
        if firewall-cmd --quiet --query-service="$service" >/dev/null 2>&1; then
            continue
        fi

        if firewall-cmd --permanent --add-service="$service" >/dev/null 2>&1; then
            changed_services+=("$service")
        else
            print_warning "Failed to open firewalld service: $service"
        fi
    done

    if [ ${#changed_services[@]} -gt 0 ]; then
        if firewall-cmd --reload >/dev/null 2>&1; then
            print_success "Opened firewalld services: ${changed_services[*]}"
        else
            print_warning "Updated firewalld rules but failed to reload firewalld"
            return 1
        fi
    fi

    return 0
}

copy_config_template() {
    local template_source="$CONFIG_TEMPLATE_FILE"
    if [ ! -f "$template_source" ]; then
        template_source="${SCRIPT_DIR}/config.yml"
    fi

    cp "$template_source" "$CONFIG_FILE"
    
    # Fix permissions after modification if running as root
    if [ "$(id -u)" -eq 0 ] && [ -f "$CONFIG_FILE" ]; then
        # Check if repogoon group exists
        if getent group repogoon >/dev/null; then
            chown root:repogoon "$CONFIG_FILE" 2>/dev/null || true
            chmod 640 "$CONFIG_FILE" 2>/dev/null || true
        fi
    fi
}

# Get YAML value
yaml_get() {
    local key="$1"
    local file="$2"

    local value
    if value=$(
        REPOGOON_CTL_CONFIG_FILE="$file" \
        run_tsx_helper server/scripts/config-cli.ts get "$key" 2>/dev/null
    ); then
        echo "$value"
        return 0
    fi

    (
        cd "$SCRIPT_DIR"
        node --input-type=module - "$file" "$key" <<'EOF'
import { readFileSync } from 'node:fs';
import { parse } from 'yaml';

const [, , configPath, rawPath = ''] = process.argv;
const pathParts = rawPath.replace(/^\./, '').split('.').filter(Boolean);
const config = parse(readFileSync(configPath, 'utf8')) ?? {};
let value = config;

for (const part of pathParts) {
    value = value?.[part];
}

if (value === undefined || value === null) {
    process.stdout.write('null\n');
} else if (typeof value === 'object') {
    process.stdout.write(`${JSON.stringify(value)}\n`);
} else {
    process.stdout.write(`${String(value)}\n`);
}
EOF
    ) 2>/dev/null
}

# Show help
show_help() {
    cat << 'EOF'
rgoon-ctl - RepoGoon Control Tool

Usage:
  rgoon-ctl [command] [options]

Commands:
  --setup              Run initial installation wizard
  --generate-internal-secret
                       Generate server.internalSecret if it is missing
  --help, -h           Show this help message

Service Management:
  start                Start the RepoGoon server
  stop                 Stop the RepoGoon server
  restart              Restart the RepoGoon server
  status               Show server status
                       In containers, these operate on the in-container
                       RepoGoon process instead of systemd

Configuration:
  show [key]           Show current configuration or a dot-path value
                       Add --json for machine-readable output
  set <key> <value>    Set a scalar configuration value
  set --json <key> <json>
                       Set a structured value such as an array or object
                       Example: rgoon-ctl set server.port 3002
                       Example: rgoon-ctl set --json auth.oidc.scopes '["openid","profile","email"]'
                       Auto-generated defaults: server.internalSecret,
                       session.secret, cors.origin, session.secure
  validate             Validate configuration file

Database:
  test-db              Test database connection
  init-db              Initialize database schema
  create-db-user       Create application database user (interactive)
  wipe-db --db <type>  Wipe all data from a specified database backend
                       Supported: sqlite, postgresql, mysql, oracle, convex
  migrate-db           Migrate data between database backends
                       Example: rgoon-ctl migrate-db --from sqlite --to convex
                       Supported: sqlite, postgresql, mysql, oracle, convex

Rate Limits:
  ratelimit list       List all rate limits
  ratelimit set <endpoint> <max> <window>
                       Set rate limit for endpoint
  ratelimit enable <endpoint>
                       Enable rate limit for endpoint
  ratelimit disable <endpoint>
                       Disable rate limit for endpoint

SSL:
  ssl-renew            Renew certbot-managed certificates

Nginx:
  nginx-config         Generate/update nginx configuration
  nginx-reload         Reload nginx configuration

Backup & Restore:
  backup [path]        Create backup of config, database, and repos
                       Default path: /var/backups/repogoon
  restore <file>       Restore from backup archive

Diagnostics:
  doctor               Run health checks on the installation

Maintenance:
  install-hooks        Install Git hooks on all repositories
                       Required for branch protection enforcement
  ssh-apply            Apply SSH firewall/SELinux policy for the configured port
                       Adds new-port rules, verifies cutover, then removes old managed rules
  reconcile-releases   Detect release/tag drift and optionally repair tags
                       Options: --apply --namespace <ns> --repo <name> --recreate-from-head

Examples:
  rgoon-ctl --setup
  rgoon-ctl --generate-internal-secret
  rgoon-ctl start
  rgoon-ctl set database.type postgresql
  rgoon-ctl set server.trustProxy true
  rgoon-ctl set --json auth.oidc.scopes '["openid","profile","email"]'
  rgoon-ctl show --json auth.oidc.scopes
  rgoon-ctl set storage.s3.endpoint https://s3.us-east-1.amazonaws.com
  rgoon-ctl set ssl.acme.caServer ca.intern
  rgoon-ctl set database.convex.selfHosted true
  rgoon-ctl set database.convex.selfHostedUrl http://localhost:3210
  rgoon-ctl wipe-db --db sqlite
  rgoon-ctl ratelimit set search 50 60000
  rgoon-ctl backup /tmp/my-backup
  rgoon-ctl doctor
  rgoon-ctl install-hooks
  rgoon-ctl reconcile-releases --namespace repogoon-org --repo repogoon
EOF
}

# Show configuration
cmd_show() {
    local output_json=false
    local key="$1"

    if [ "$key" = "--json" ]; then
        output_json=true
        key="$2"
    fi
    
    if [ ! -f "$CONFIG_FILE" ]; then
        print_error "Configuration file not found: $CONFIG_FILE"
        print_info "Run './rgoon-ctl --setup' to create one."
        exit 1
    fi

    if [ "$output_json" = "true" ]; then
        config_cli show --json "$key"
    else
        config_cli show "$key"
    fi
}

# Generate internal hook secret if missing
cmd_generate_internal_secret() {
    if [ ! -f "$CONFIG_FILE" ]; then
        print_error "Configuration file not found: $CONFIG_FILE"
        print_info "Run './rgoon-ctl --setup' to create one."
        exit 1
    fi

    local existing_secret
    existing_secret="$(yaml_get '.server.internalSecret' "$CONFIG_FILE" 2>/dev/null | tr -d '\r')"

    if [ -n "$existing_secret" ] && [ "$existing_secret" != "null" ]; then
        print_info "server.internalSecret already exists in $CONFIG_FILE"
        return 0
    fi

    local internal_secret
    internal_secret="$(generate_secret 64)"
    yaml_set '.server.internalSecret' "$internal_secret" "$CONFIG_FILE"
    print_success "Generated server.internalSecret in $CONFIG_FILE"
}

# Set configuration value
cmd_set() {
    local json_mode=false
    local key="$1"
    local value="$2"

    if [ "$key" = "--json" ]; then
        json_mode=true
        key="$2"
        value="$3"
    fi
    
    if [ -z "$key" ] || [ -z "$value" ]; then
        print_error "Usage: rgoon-ctl set [--json] <key> <value>"
        exit 1
    fi
    
    if [ ! -f "$CONFIG_FILE" ]; then
        print_error "Configuration file not found: $CONFIG_FILE"
        exit 1
    fi

    if [ "$json_mode" = "true" ]; then
        yaml_set_json ".${key}" "$value" "$CONFIG_FILE"
    else
        yaml_set ".${key}" "$value" "$CONFIG_FILE"
    fi

    print_success "Set ${key}"
}

# Validate configuration
cmd_validate() {
    if [ ! -f "$CONFIG_FILE" ]; then
        print_error "Configuration file not found: $CONFIG_FILE"
        exit 1
    fi
    
    print_info "Validating configuration..."
    
    # Run Node.js validation
    cd "$SCRIPT_DIR"
    node --import tsx -e "
        import('./server/config.ts').then(config => {
            console.log('Configuration loaded successfully');
            console.log('Database type:', config.default.database.type);
            console.log('Server port:', config.default.server.port);
            process.exit(0);
        }).catch(err => {
            console.error('Validation failed:', err.message);
            process.exit(1);
        });
    "
    
    if [ $? -eq 0 ]; then
        print_success "Configuration is valid"
    else
        print_error "Configuration validation failed"
        exit 1
    fi
}

# Test database connection
cmd_test_db() {
    print_info "Testing database connection..."
    
    cd "$SCRIPT_DIR"
    node --import tsx server/db/test-connection.ts
}

# Initialize database
cmd_init_db() {
    print_info "Initializing database schema..."
    
    cd "$SCRIPT_DIR"
    node --import tsx server/db/init-schema.ts
}

# Deploy Convex functions if database type is convex
_run_convex_deploy() {
    local db_type=$(yaml_get '.database.type' "$CONFIG_FILE" 2>/dev/null)
    
    if [ "$db_type" = "convex" ]; then
        print_info "Database type is Convex, deploying functions..."
        
        local convex_dir
        if [ "$SCRIPT_PATH" = "/usr/bin" ] || [ "$SCRIPT_PATH" = "/bin" ]; then
            convex_dir="/usr/share/repogoon"
        else
            convex_dir="$SCRIPT_DIR"
        fi
        
        if [ ! -d "$convex_dir/convex" ]; then
            print_warning "Convex directory not found, skipping deploy"
            return 0
        fi
        
        cd "$convex_dir"
        
        # Get deployment URL and admin key from config
        local convex_url=$(yaml_get '.database.convex.deploymentUrl' "$CONFIG_FILE" 2>/dev/null)
        local convex_key=$(yaml_get '.database.convex.adminKey' "$CONFIG_FILE" 2>/dev/null)
        local convex_self_hosted=$(yaml_get '.database.convex.selfHosted' "$CONFIG_FILE" 2>/dev/null)
        local convex_self_url=$(yaml_get '.database.convex.selfHostedUrl' "$CONFIG_FILE" 2>/dev/null)
        local convex_self_key=$(yaml_get '.database.convex.selfHostedAdminKey' "$CONFIG_FILE" 2>/dev/null)
        
        # Determine which URL and key to use
        local deploy_url deploy_key
        if [ "$convex_self_hosted" = "true" ] && [ -n "$convex_self_url" ]; then
            deploy_url="$convex_self_url"
            deploy_key="${convex_self_key:-$convex_key}"
        else
            deploy_url="$convex_url"
            deploy_key="$convex_key"
        fi
        
        if [ -z "$deploy_url" ] || [ -z "$deploy_key" ]; then
            print_warning "Convex deployment URL or admin key not configured, skipping deploy"
            return 0
        fi
        
        # Run convex deploy with the admin key
        if CONVEX_DEPLOY_KEY="$deploy_key" npx convex deploy --url "$deploy_url" --yes 2>&1; then
            print_success "Convex functions deployed successfully"
        else
            print_warning "Convex deploy failed, server may not function correctly"
        fi
    fi
}

container_server_entrypoint() {
    if [ -f "${SCRIPT_DIR}/dist/server/index.js" ]; then
        echo "${SCRIPT_DIR}/dist/server/index.js"
        return 0
    fi

    echo "${SCRIPT_DIR}/server/index.ts"
}

find_container_server_pid() {
    if [ -f "$PID_FILE" ]; then
        local pid
        pid=$(cat "$PID_FILE" 2>/dev/null || true)
        if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
            echo "$pid"
            return 0
        fi
    fi

    if command_exists ps; then
        ps -eo pid=,args= 2>/dev/null | awk -v self="$$" '
            $1 != self && /node/ && (/dist\/server\/index\.js/ || /server\/index\.ts/) {
                print $1;
                exit 0;
            }
        '
    fi
}

cmd_container_start() {
    local port
    port=$(get_server_port)

    if is_port_listening "$port"; then
        print_warning "RepoGoon is already running in this container"
        _show_server_url
        return 0
    fi

    _run_convex_deploy
    cmd_install_hooks

    print_info "Starting RepoGoon inside this container..."

    local server_entry
    server_entry=$(container_server_entrypoint)
    local node_env="${NODE_ENV:-production}"

    if ! mkdir -p "$(dirname "$PID_FILE")" "$(dirname "$LOG_FILE")" 2>/dev/null; then
        PID_FILE="/tmp/repogoon.pid"
        LOG_FILE="/tmp/repogoon.log"
        mkdir -p "$(dirname "$PID_FILE")" "$(dirname "$LOG_FILE")"
    fi
    cd "$SCRIPT_DIR"

    if [[ "$server_entry" == *.ts ]]; then
        if [ "$(id -u)" -eq 0 ] && getent passwd repogoon >/dev/null; then
            runuser -u repogoon -- env NODE_ENV="$node_env" node --import tsx "$server_entry" >> "$LOG_FILE" 2>&1 &
        else
            env NODE_ENV="$node_env" node --import tsx "$server_entry" >> "$LOG_FILE" 2>&1 &
        fi
    else
        if [ "$(id -u)" -eq 0 ] && getent passwd repogoon >/dev/null; then
            runuser -u repogoon -- env NODE_ENV="$node_env" node "$server_entry" >> "$LOG_FILE" 2>&1 &
        else
            env NODE_ENV="$node_env" node "$server_entry" >> "$LOG_FILE" 2>&1 &
        fi
    fi

    local pid=$!
    echo "$pid" > "$PID_FILE"

    if kill -0 "$pid" 2>/dev/null && wait_for_server_port "$port" 10; then
        print_success "RepoGoon started in this container (PID $pid)"
        _show_server_url
    else
        print_error "RepoGoon did not start listening on port ${port}. Check $LOG_FILE for details."
        rm -f "$PID_FILE"
        exit 1
    fi
}

cmd_container_stop() {
    local port
    port=$(get_server_port)

    if ! is_port_listening "$port"; then
        print_warning "RepoGoon is not listening on port ${port}"
        return 0
    fi

    local pid
    pid=$(find_container_server_pid)

    if [ -n "$pid" ]; then
        print_info "Stopping RepoGoon in this container (PID $pid)..."
        kill "$pid"
        rm -f "$PID_FILE"
        print_success "Stop signal sent"
        return 0
    fi

    print_info "Stopping the container main process..."
    kill -TERM 1
    print_success "Stop signal sent to PID 1"
}

cmd_container_restart() {
    if [ -f "$PID_FILE" ]; then
        cmd_container_stop
        sleep 1
        cmd_container_start
        return
    fi

    print_info "Signaling the container-managed RepoGoon process to restart."
    print_info "Docker or Podman will apply the container restart policy if one is configured."
    cmd_container_stop
}

cmd_container_status() {
    local port
    port=$(get_server_port)

    if is_port_listening "$port"; then
        echo -e "${GREEN}RepoGoon is running in this container${NC}"
        local pid
        pid=$(find_container_server_pid)
        if [ -n "$pid" ]; then
            echo "PID: $pid"
        fi
        _show_server_url
        return 0
    fi

    echo -e "${YELLOW}RepoGoon is not listening in this container${NC}"
    return 1
}

# Start server
cmd_start() {
    local port
    port=$(get_server_port)

    if is_container_install; then
        cmd_container_start
        return
    fi

    # Deploy Convex functions if needed
    _run_convex_deploy
    # Install/update Git hooks for branch protection
    cmd_install_hooks
    # Use systemd if installed via RPM
    if uses_systemd_service; then
        print_info "Starting RepoGoon service..."
        sudo systemctl start repogoon
        if systemctl is-active --quiet repogoon; then
            if wait_for_server_port "$port" 10; then
                print_success "RepoGoon service started"
                _show_server_url
            else
                print_warning "RepoGoon service is active, but port ${port} is not accepting connections yet"
                echo "Check logs with: journalctl -u repogoon -e"
                return 1
            fi
        else
            print_error "Failed to start RepoGoon service"
            echo "Check logs with: journalctl -u repogoon -e"
            exit 1
        fi
        return
    fi
    
    # Development mode - manual PID management
    if [ -f "$PID_FILE" ]; then
        local pid=$(cat "$PID_FILE")
        if kill -0 "$pid" 2>/dev/null; then
            print_warning "RepoGoon is already running (PID $pid)"
            return 0
        fi
    fi
    
    print_info "Starting RepoGoon..."
    
    cd "$SCRIPT_DIR"
    NODE_ENV=production nohup node --import tsx server/index.ts > "$LOG_FILE" 2>&1 &
    local pid=$!
    echo "$pid" > "$PID_FILE"
    
    if kill -0 "$pid" 2>/dev/null && wait_for_server_port "$port" 10; then
        print_success "RepoGoon started (PID $pid)"
        _show_server_url
    else
        print_error "RepoGoon did not start listening on port ${port}. Check $LOG_FILE for details."
        rm -f "$PID_FILE"
        exit 1
    fi
}

# Stop server
cmd_stop() {
    if is_container_install; then
        cmd_container_stop
        return
    fi

    # Use systemd if installed via RPM
    if uses_systemd_service; then
        print_info "Stopping RepoGoon service..."
        sudo systemctl stop repogoon
        print_success "RepoGoon service stopped"
        return
    fi
    
    # Development mode - manual PID management
    if [ ! -f "$PID_FILE" ]; then
        print_warning "RepoGoon is not running (no PID file)"
        return 0
    fi
    
    local pid=$(cat "$PID_FILE")
    
    if ! kill -0 "$pid" 2>/dev/null; then
        print_warning "RepoGoon is not running (stale PID file)"
        rm -f "$PID_FILE"
        return 0
    fi
    
    print_info "Stopping RepoGoon (PID $pid)..."
    kill "$pid"
    
    # Wait for process to stop
    local count=0
    while kill -0 "$pid" 2>/dev/null && [ $count -lt 10 ]; do
        sleep 1
        ((count++))
    done
    
    if kill -0 "$pid" 2>/dev/null; then
        print_warning "Process didn't stop gracefully, forcing..."
        kill -9 "$pid" 2>/dev/null
    fi
    
    rm -f "$PID_FILE"
    print_success "RepoGoon stopped"
}

# Restart server
cmd_restart() {
    local port
    port=$(get_server_port)

    if is_container_install; then
        cmd_container_restart
        return
    fi

    # Deploy Convex functions if needed (for systemd path which doesn't use cmd_start)
    _run_convex_deploy
    # Install/update Git hooks for branch protection
    cmd_install_hooks
    
    # Use systemd if installed via RPM
    if uses_systemd_service; then
        print_info "Restarting RepoGoon service..."
        sudo systemctl restart repogoon
        if systemctl is-active --quiet repogoon; then
            if wait_for_server_port "$port" 10; then
                print_success "RepoGoon service restarted"
                _show_server_url
            else
                print_warning "RepoGoon service restarted, but port ${port} is not accepting connections yet"
                echo "Check logs with: journalctl -u repogoon -e"
                return 1
            fi
        else
            print_error "Failed to restart RepoGoon service"
            exit 1
        fi
        return
    fi
    
    cmd_stop
    sleep 1
    cmd_start
}

# Server status
cmd_status() {
    local port
    port=$(get_server_port)

    if is_container_install; then
        cmd_container_status
        return
    fi

    # Use systemd if installed via RPM
    if uses_systemd_service; then
        if systemctl is-active --quiet repogoon; then
            echo -e "${GREEN}RepoGoon service is running${NC}"
            systemctl status repogoon --no-pager | head -10
        else
            echo -e "${YELLOW}RepoGoon service is not running${NC}"
            return 1
        fi

        if ! is_port_listening "$port"; then
            print_warning "RepoGoon service is active, but port ${port} is not listening"
            echo "Check logs with: journalctl -u repogoon -e"
            return 1
        fi

        _show_server_url
        return
    fi
    
    # Development mode - manual PID check
    if [ ! -f "$PID_FILE" ]; then
        echo -e "${YELLOW}RepoGoon is not running${NC}"
        return 1
    fi
    
    local pid=$(cat "$PID_FILE")
    
    if ! kill -0 "$pid" 2>/dev/null; then
        echo -e "${YELLOW}RepoGoon is not running (stale PID file)${NC}"
        rm -f "$PID_FILE"
        return 1
    fi
    
    echo -e "${GREEN}RepoGoon is running${NC} (PID $pid)"
    
    # Get uptime
    local start_time=$(ps -o lstart= -p "$pid" 2>/dev/null)
    if [ -n "$start_time" ]; then
        echo "Started: $start_time"
    fi
    
    # Get memory usage
    local mem=$(ps -o rss= -p "$pid" 2>/dev/null)
    if [ -n "$mem" ]; then
        local mem_mb=$((mem / 1024))
        echo "Memory: ${mem_mb} MB"
    fi

    if ! is_port_listening "$port"; then
        print_warning "RepoGoon process is running, but port ${port} is not listening"
        echo "Check logs with: tail -f $LOG_FILE"
        return 1
    fi
    
    _show_server_url
}

# Helper to show server URLs
_show_server_url() {
    local public_url
    public_url=$(get_public_server_url)
    local local_url=$(get_local_server_url)

    if [ -n "$public_url" ]; then
        echo "Configured public URL: ${public_url}"
        echo "Local app URL: ${local_url}"
    else
        echo "App URL: ${local_url}"
    fi
}

# Rate limit commands
cmd_ratelimit() {
    local subcmd="$1"
    shift
    
    cd "$SCRIPT_DIR"
    
    case "$subcmd" in
        list)
            node --import tsx server/db/rate-limits.ts list
            ;;
        set)
            if [ $# -lt 3 ]; then
                print_error "Usage: rgoon-ctl ratelimit set <endpoint> <max_requests> <window_ms>"
                exit 1
            fi
            node --import tsx server/db/rate-limits.ts set "$1" "$2" "$3"
            ;;
        enable)
            if [ -z "$1" ]; then
                print_error "Usage: rgoon-ctl ratelimit enable <endpoint>"
                exit 1
            fi
            node --import tsx server/db/rate-limits.ts enable "$1"
            ;;
        disable)
            if [ -z "$1" ]; then
                print_error "Usage: rgoon-ctl ratelimit disable <endpoint>"
                exit 1
            fi
            node --import tsx server/db/rate-limits.ts disable "$1"
            ;;
        *)
            print_error "Unknown ratelimit command: $subcmd"
            print_info "Available: list, set, enable, disable"
            exit 1
            ;;
    esac
}

# Generate nginx configuration
cmd_nginx_config() {
    print_info "Generating nginx configuration..."
    
    local port=$(yaml_get '.server.port' "$CONFIG_FILE")
    local domain=$(yaml_get '.server.domain' "$CONFIG_FILE")
    local ssl_enabled=$(yaml_get '.ssl.enabled' "$CONFIG_FILE")
    local ssl_mode=$(yaml_get '.ssl.mode' "$CONFIG_FILE")
    local cert_path=$(yaml_get '.ssl.custom.certPath' "$CONFIG_FILE")
    local key_path=$(yaml_get '.ssl.custom.keyPath' "$CONFIG_FILE")
    local nginx_path=$(yaml_get '.nginx.configPath' "$CONFIG_FILE")
    
    if [ -z "$domain" ]; then
        print_error "Domain not configured. Set server.domain first."
        return 1
    fi
    
    local template="${SCRIPT_DIR}/nginx/repogoon.conf.template"
    
    # Determine the correct nginx config path based on the system
    # CentOS/RHEL uses conf.d, Debian/Ubuntu uses sites-available
    if [ -z "$nginx_path" ] || [ "$nginx_path" = "null" ]; then
        if [ -d "/etc/nginx/sites-available" ]; then
            # Debian/Ubuntu style
            nginx_path="/etc/nginx/sites-available/repogoon"
        elif [ -d "/etc/nginx/conf.d" ]; then
            # CentOS/RHEL style
            nginx_path="/etc/nginx/conf.d/repogoon.conf"
        else
            nginx_path="/etc/nginx/sites-available/repogoon"
        fi
    fi
    local output="$nginx_path"
    
    if [ ! -f "$template" ]; then
        print_error "Nginx template not found: $template"
        return 1
    fi
    
    # Copy template and replace variables
    local temp_file=$(mktemp)
    cp "$template" "$temp_file"
    
    sed -i "s/{{PORT}}/${port:-3001}/g" "$temp_file"
    sed -i "s/{{DOMAIN}}/${domain}/g" "$temp_file"
    
    if [ "$ssl_enabled" = "true" ]; then
        if [ "$ssl_mode" = "letsencrypt" ] || [ "$ssl_mode" = "acme" ]; then
            cert_path="/etc/letsencrypt/live/${domain}/fullchain.pem"
            key_path="/etc/letsencrypt/live/${domain}/privkey.pem"
        elif { [ -z "$cert_path" ] || [ "$cert_path" = "null" ]; } || { [ -z "$key_path" ] || [ "$key_path" = "null" ]; }; then
            cert_path="/etc/letsencrypt/live/${domain}/fullchain.pem"
            key_path="/etc/letsencrypt/live/${domain}/privkey.pem"
        fi

        if [ -z "$cert_path" ] || [ "$cert_path" = "null" ] || [ -z "$key_path" ] || [ "$key_path" = "null" ]; then
            print_error "HTTPS is enabled but certificate paths are not configured."
            print_info "Set ssl.custom.certPath and ssl.custom.keyPath, or use ssl.mode letsencrypt/acme."
            rm "$temp_file"
            return 1
        fi

        sed -i "s|{{CERT_PATH}}|${cert_path}|g" "$temp_file"
        sed -i "s|{{KEY_PATH}}|${key_path}|g" "$temp_file"
        # Enable SSL sections
        sed -i '/{{SSL_REDIRECT_START}}/d; /{{SSL_REDIRECT_END}}/d' "$temp_file"
        sed -i '/{{SSL_SERVER_START}}/d; /{{SSL_SERVER_END}}/d' "$temp_file"
        # Comment out no-SSL section
        sed -i '/{{NO_SSL_START}}/,/{{NO_SSL_END}}/d' "$temp_file"
    else
        # Disable SSL sections
        sed -i '/{{SSL_REDIRECT_START}}/,/{{SSL_REDIRECT_END}}/d' "$temp_file"
        sed -i '/{{SSL_SERVER_START}}/,/{{SSL_SERVER_END}}/d' "$temp_file"
        # Enable no-SSL section
        sed -i '/{{NO_SSL_START}}/d; /{{NO_SSL_END}}/d' "$temp_file"
        sed -i 's/^    # /    /g' "$temp_file"
    fi
    
    # Write output
    sudo cp "$temp_file" "$output"
    rm "$temp_file"
    
    print_success "Nginx configuration written to: $output"
    
    # Create symlink if using sites-available style (Debian/Ubuntu)
    if [ -d "/etc/nginx/sites-enabled" ] && [[ "$output" == *"sites-available"* ]] && [ ! -L "/etc/nginx/sites-enabled/repogoon" ]; then
        sudo ln -sf "$output" "/etc/nginx/sites-enabled/repogoon"
        print_success "Created symlink in sites-enabled"
    fi
}

# Reload nginx
cmd_nginx_reload() {
    print_info "Enabling and starting nginx..."
    
    if sudo nginx -t; then
        sudo systemctl enable --now nginx
        print_success "Nginx enabled and started"
    else
        print_error "Nginx configuration test failed"
        exit 1
    fi
}

# SSL renewal
cmd_ssl_renew() {
    local ssl_mode=$(yaml_get '.ssl.mode' "$CONFIG_FILE")
    
    if [ "$ssl_mode" != "letsencrypt" ] && [ "$ssl_mode" != "acme" ]; then
        print_error "SSL mode is not 'letsencrypt' or 'acme'. Cannot renew."
        exit 1
    fi
    
    print_info "Renewing certbot-managed certificates..."
    sudo certbot renew
    
    print_success "Certificates renewed"
    cmd_nginx_reload
}

# Initial setup wizard
cmd_setup() {
    print_header "RepoGoon Initial Setup"
    
    # Check if config exists
    if [ -f "$CONFIG_FILE" ]; then
        prompt_yn "Configuration already exists. Overwrite?" "n" overwrite
        if [ "$overwrite" != "true" ]; then
            print_info "Setup cancelled."
            exit 0
        fi
        cp "$CONFIG_FILE" "${CONFIG_FILE}.bak" 2>/dev/null || true
    fi
    
    # Copy default config
    copy_config_template

    # ─── Data Paths ─────────────────────────────────────────────────────────────
    print_section "Data Paths"

    local default_homedir="$DATA_DIR"
    if [ "$SCRIPT_PATH" = "/usr/bin" ] || [ "$SCRIPT_PATH" = "/bin" ]; then
        default_homedir="/var/lib/repogoon"
    fi

    prompt "Writable data directory" "$default_homedir" repogoon_homedir
    yaml_set '.homedir' "$repogoon_homedir" "$CONFIG_FILE"

    local default_repos_path="${repogoon_homedir}/repos"
    prompt "Storage path for repositories" "$default_repos_path" repos_path
    yaml_set '.repositories.storagePath' "$repos_path" "$CONFIG_FILE"

    prompt_yn "Enable embedded Git-over-SSH listener?" "n" ssh_enabled
    yaml_set '.ssh.enabled' "$ssh_enabled" "$CONFIG_FILE"
    if [ "$ssh_enabled" = "true" ]; then
        prompt "SSH bind address" "0.0.0.0" ssh_bind_host
        prompt "SSH host key path" "${repogoon_homedir}/ssh/host_key.pem" ssh_host_key_path
        yaml_set '.ssh.bindHost' "$ssh_bind_host" "$CONFIG_FILE"
        yaml_set '.ssh.hostKeyPath' "$ssh_host_key_path" "$CONFIG_FILE"
    fi

    # ─── Server Configuration ───────────────────────────────────────────────────
    print_section "Server Configuration"
    
    prompt "Server port" "3001" server_port
    prompt "Server bind address" "127.0.0.1" server_host
    prompt "Domain name (e.g., git.example.com)" "" server_domain
    prompt "trustProxy value (false, true, hop count, subnet, or comma-separated list)" "false" server_trust_proxy
    prompt "Maximum JSON request body size (e.g., 1mb, 500kb)" "1mb" server_json_body_limit
    
    yaml_set '.server.port' "$server_port" "$CONFIG_FILE"
    yaml_set '.server.host' "$server_host" "$CONFIG_FILE"
    yaml_set '.server.domain' "$server_domain" "$CONFIG_FILE"
    local trust_proxy_json
    trust_proxy_json="$(trust_proxy_to_json "$server_trust_proxy")"
    yaml_set_json '.server.trustProxy' "$trust_proxy_json" "$CONFIG_FILE"
    yaml_set '.server.jsonBodyLimit' "$server_json_body_limit" "$CONFIG_FILE"
    
    # ─── Repository Configuration ───────────────────────────────────────────────
    print_section "Repository Configuration"
    prompt "Default branch for new repositories" "main" default_branch
    yaml_set '.repositories.defaultBranch' "$default_branch" "$CONFIG_FILE"

    # ─── Git Identity ───────────────────────────────────────────────────────────
    print_section "Git Identity"
    prompt "Release tagger name" "RepoGoon" git_tagger_name
    prompt "Release tagger email" "noreply@${server_domain:-localhost}" git_tagger_email
    yaml_set '.git.taggerName' "$git_tagger_name" "$CONFIG_FILE"
    yaml_set '.git.taggerEmail' "$git_tagger_email" "$CONFIG_FILE"

    # ─── Logging Configuration ──────────────────────────────────────────────────
    print_section "Logging Configuration"
    
    echo -e "\nLog Level:"
    prompt_select "Select log verbosity" "error warn info debug" "3" log_level
    yaml_set '.logging.level' "$log_level" "$CONFIG_FILE"
    
    local default_log_file="./repogoon.log"
    if [ "$SCRIPT_PATH" = "/usr/bin" ] || [ "$SCRIPT_PATH" = "/bin" ]; then
        default_log_file="/var/log/repogoon/server-log-{date}-{version}.log"
    fi
    
    prompt "Log file path" "$default_log_file" log_file
    yaml_set '.logging.file' "$log_file" "$CONFIG_FILE"

    # ─── Rate Limit Configuration ───────────────────────────────────────────────
    print_section "Rate Limit Configuration"
    
    prompt_yn "Enable global rate limiting?" "y" rate_limit_enabled
    yaml_set '.rateLimit.enabled' "$rate_limit_enabled" "$CONFIG_FILE"
    
    if [ "$rate_limit_enabled" = "true" ]; then
        prompt "Max requests per window" "100" rate_limit_max
        prompt "Window size (ms)" "60000" rate_limit_window
        
        yaml_set '.rateLimit.max' "$rate_limit_max" "$CONFIG_FILE"
        yaml_set '.rateLimit.windowMs' "$rate_limit_window" "$CONFIG_FILE"
    fi

    # ─── Storage Configuration ──────────────────────────────────────────────────
    print_section "Avatar Storage"

    prompt_yn "Configure S3-compatible avatar storage?" "n" s3_enabled
    yaml_set '.storage.s3.enabled' "$s3_enabled" "$CONFIG_FILE"

    if [ "$s3_enabled" = "true" ]; then
        prompt "S3 endpoint" "" s3_endpoint
        prompt "S3 region" "us-east-1" s3_region
        prompt "S3 bucket" "" s3_bucket
        prompt "S3 access key ID" "" s3_access_key
        prompt "S3 secret access key" "" s3_secret_key true
        prompt "S3 public URL (optional)" "" s3_public_url
        prompt_yn "Use forcePathStyle for the S3 client?" "n" s3_force_path_style

        yaml_set '.storage.s3.endpoint' "$s3_endpoint" "$CONFIG_FILE"
        yaml_set '.storage.s3.region' "$s3_region" "$CONFIG_FILE"
        yaml_set '.storage.s3.bucket' "$s3_bucket" "$CONFIG_FILE"
        yaml_set '.storage.s3.accessKeyId' "$s3_access_key" "$CONFIG_FILE"
        yaml_set '.storage.s3.secretAccessKey' "$s3_secret_key" "$CONFIG_FILE"
        yaml_set '.storage.s3.publicUrl' "$s3_public_url" "$CONFIG_FILE"
        yaml_set '.storage.s3.forcePathStyle' "$s3_force_path_style" "$CONFIG_FILE"
    fi

    
    # ─── SSL Configuration ──────────────────────────────────────────────────────
    print_section "SSL/TLS Configuration"
    
    if [ -n "$server_domain" ]; then
        prompt_yn "Enable public HTTPS via nginx?" "y" ssl_enabled
    else
        ssl_enabled=false
        print_info "Skipping SSL (no domain configured)"
    fi
    
    if [ "$ssl_enabled" = "true" ]; then
        yaml_set '.ssl.enabled' 'true' "$CONFIG_FILE"
        
        echo -e "\nSSL Mode:"
        prompt_select "Select SSL mode" "letsencrypt acme custom none" "1" ssl_mode
        
        yaml_set '.ssl.mode' "$ssl_mode" "$CONFIG_FILE"
        
        if [ "$ssl_mode" = "letsencrypt" ]; then
            prompt "Let's Encrypt email for notifications" "" le_email
            yaml_set '.ssl.letsencrypt.email' "$le_email" "$CONFIG_FILE"
            
            # Check if certificate already exists
            local cert_exists=false
            if [ -f "/etc/letsencrypt/live/${server_domain}/fullchain.pem" ]; then
                cert_exists=true
                print_success "Certificate already exists for ${server_domain}"
            fi
            
            # Offer to obtain certificate if it doesn't exist
            if [ "$cert_exists" = "false" ]; then
                prompt_yn "Obtain Let's Encrypt certificate now?" "y" obtain_cert
                
                if [ "$obtain_cert" = "true" ]; then
                    # Check if certbot is installed
                    if ! command -v certbot &> /dev/null; then
                        print_error "certbot is not installed. Please install it first:"
                        echo "  Fedora/RHEL: sudo dnf install certbot python3-certbot-nginx"
                        echo "  Debian/Ubuntu: sudo apt install certbot python3-certbot-nginx"
                        print_info "Skipping certificate issuance..."
                    else
                        ensure_firewalld_services http || true
                        print_info "Obtaining Let's Encrypt certificate for ${server_domain}..."
                        
                        # Choose method based on whether nginx is running
                        local certbot_method="--standalone"
                        if systemctl is-active --quiet nginx 2>/dev/null; then
                            certbot_method="--nginx"
                            print_info "Using nginx plugin (nginx is running)"
                        else
                            print_info "Using standalone mode (nginx not running)"
                            print_warning "Make sure port 80 is available and open in your firewall"
                        fi

                        local certbot_args=(
                            certonly
                            "$certbot_method"
                            -d "${server_domain}"
                            --email "${le_email}"
                            --agree-tos
                            --non-interactive
                        )
                        
                        if sudo certbot "${certbot_args[@]}"; then
                            print_success "Certificate obtained successfully!"
                        else
                            print_error "Failed to obtain certificate. You can try again later with:"
                            echo "  sudo certbot certonly --nginx -d ${server_domain} --email ${le_email}"
                        fi
                    fi
                fi
            fi
            
            prompt_yn "Setup automatic certificate renewal (cron job)?" "y" auto_renew
            yaml_set '.ssl.letsencrypt.autoRenew' "$auto_renew" "$CONFIG_FILE"
            
            if [ "$auto_renew" = "true" ]; then
                prompt "Renewal cron schedule" "0 3 * * *" renew_schedule
                yaml_set '.ssl.letsencrypt.renewSchedule' "$renew_schedule" "$CONFIG_FILE"

                # Setup certbot renewal cron job
                print_info "Setting up automatic certificate renewal..."
                
                # Create renewal script
                local renew_script="/etc/cron.daily/repogoon-cert-renew"
                sudo tee "$renew_script" > /dev/null << 'RENEW_EOF'
#!/bin/bash
# RepoGoon Let's Encrypt certificate renewal
certbot renew --quiet --post-hook "systemctl reload nginx"
RENEW_EOF
                sudo chmod +x "$renew_script"
                print_success "Created daily renewal cron job: $renew_script"
            fi
        elif [ "$ssl_mode" = "acme" ]; then
            prompt "ACME contact email for notifications" "" acme_email
            yaml_set '.ssl.acme.email' "$acme_email" "$CONFIG_FILE"
            prompt "ACME CA server or directory URL" "ca.intern" acme_ca_server
            yaml_set '.ssl.acme.caServer' "$acme_ca_server" "$CONFIG_FILE"

            local acme_server_url
            acme_server_url="$(normalize_acme_server "$acme_ca_server")"
            
            local cert_exists=false
            if [ -f "/etc/letsencrypt/live/${server_domain}/fullchain.pem" ]; then
                cert_exists=true
                print_success "Certificate already exists for ${server_domain}"
            fi

            if [ "$cert_exists" = "false" ]; then
                prompt_yn "Obtain ACME certificate now?" "y" obtain_cert

                if [ "$obtain_cert" = "true" ]; then
                    if ! command -v certbot &> /dev/null; then
                        print_error "certbot is not installed. Please install it first:"
                        echo "  Fedora/RHEL: sudo dnf install certbot python3-certbot-nginx"
                        echo "  Debian/Ubuntu: sudo apt install certbot python3-certbot-nginx"
                        print_info "Skipping certificate issuance..."
                    else
                        ensure_firewalld_services http || true
                        print_info "Obtaining ACME certificate for ${server_domain}..."

                        local certbot_method="--standalone"
                        if systemctl is-active --quiet nginx 2>/dev/null; then
                            certbot_method="--nginx"
                            print_info "Using nginx plugin (nginx is running)"
                        else
                            print_info "Using standalone mode (nginx not running)"
                            print_warning "Make sure port 80 is available and open in your firewall"
                        fi

                        local certbot_args=(
                            certonly
                            "$certbot_method"
                            -d "${server_domain}"
                            --email "${acme_email}"
                            --agree-tos
                            --non-interactive
                        )
                        if [ -n "$acme_server_url" ]; then
                            print_info "Using ACME CA server: ${acme_server_url}"
                            certbot_args+=(--server "${acme_server_url}")
                        fi

                        if sudo certbot "${certbot_args[@]}"; then
                            print_success "Certificate obtained successfully!"
                        else
                            print_error "Failed to obtain certificate. You can try again later with:"
                            echo "  sudo certbot certonly --nginx -d ${server_domain} --email ${acme_email} --server ${acme_server_url:-https://ca.intern/acme/acme/directory}"
                        fi
                    fi
                fi
            fi

            prompt_yn "Setup automatic certificate renewal (cron job)?" "y" auto_renew
            yaml_set '.ssl.acme.autoRenew' "$auto_renew" "$CONFIG_FILE"

            if [ "$auto_renew" = "true" ]; then
                prompt "Renewal cron schedule" "0 3 * * *" renew_schedule
                yaml_set '.ssl.acme.renewSchedule' "$renew_schedule" "$CONFIG_FILE"

                print_info "Setting up automatic certificate renewal..."

                local renew_script="/etc/cron.daily/repogoon-cert-renew"
                sudo tee "$renew_script" > /dev/null << 'RENEW_EOF'
#!/bin/bash
# RepoGoon ACME certificate renewal
certbot renew --quiet --post-hook "systemctl reload nginx"
RENEW_EOF
                sudo chmod +x "$renew_script"
                print_success "Created daily renewal cron job: $renew_script"
            fi
        elif [ "$ssl_mode" = "custom" ]; then
            prompt "Path to SSL certificate" "" cert_path
            prompt "Path to SSL private key" "" key_path
            prompt "Path to CA bundle (optional)" "" ca_path
            
            yaml_set '.ssl.custom.certPath' "$cert_path" "$CONFIG_FILE"
            yaml_set '.ssl.custom.keyPath' "$key_path" "$CONFIG_FILE"
            [ -n "$ca_path" ] && yaml_set '.ssl.custom.caPath' "$ca_path" "$CONFIG_FILE"
        fi
    else
        yaml_set '.ssl.enabled' 'false' "$CONFIG_FILE"
        yaml_set '.ssl.mode' 'none' "$CONFIG_FILE"
    fi
    
    # ─── Nginx Configuration ────────────────────────────────────────────────────
    print_section "Nginx Reverse Proxy"
    
    prompt_yn "Configure nginx as reverse proxy?" "y" nginx_enabled
    
    if [ "$nginx_enabled" = "true" ]; then
        yaml_set '.nginx.enabled' 'true' "$CONFIG_FILE"
        prompt "Nginx config path" "/etc/nginx/conf.d/repogoon.conf" nginx_path
        yaml_set '.nginx.configPath' "$nginx_path" "$CONFIG_FILE"
        prompt_yn "Auto-reload nginx on config changes?" "y" nginx_reload
        yaml_set '.nginx.autoReload' "$nginx_reload" "$CONFIG_FILE"

        local configured_trust_proxy
        configured_trust_proxy="$(yaml_get '.server.trustProxy' "$CONFIG_FILE")"
        case "$(echo "${configured_trust_proxy}" | tr '[:upper:]' '[:lower:]' | xargs)" in
            false|"")
                yaml_set_json '.server.trustProxy' 'true' "$CONFIG_FILE"
                print_info "Enabled server.trustProxy for nginx reverse proxy deployments"
                ;;
        esac

        local web_firewall_services=(http)
        if [ "$ssl_enabled" = "true" ]; then
            web_firewall_services+=(https)
        fi
        ensure_firewalld_services "${web_firewall_services[@]}" || true
        ensure_httpd_network_connect || true
    else
        yaml_set '.nginx.enabled' 'false' "$CONFIG_FILE"
    fi

    # ─── Browser Session Settings ───────────────────────────────────────────────
    print_section "Browser Session Settings"

    prompt_yn "Allow browser credentials in CORS responses?" "y" cors_credentials
    prompt "Session max age (ms)" "86400000" session_max_age
    prompt_yn "Mark session cookies HttpOnly?" "y" session_http_only

    yaml_set '.cors.credentials' "$cors_credentials" "$CONFIG_FILE"
    yaml_set '.session.maxAge' "$session_max_age" "$CONFIG_FILE"
    yaml_set '.session.httpOnly' "$session_http_only" "$CONFIG_FILE"
    
    # ─── Authentication Configuration ────────────────────────────────────────────
    print_section "Authentication Configuration"
    
    # Local auth
    prompt_yn "Enable local (database) authentication?" "y" local_auth
    yaml_set '.auth.local.enabled' "$local_auth" "$CONFIG_FILE"
    
    if [ "$local_auth" = "true" ]; then
        prompt_yn "Allow new user registration?" "y" allow_reg
        yaml_set '.auth.local.allowRegistration' "$allow_reg" "$CONFIG_FILE"
        
        if [ "$allow_reg" = "true" ]; then
            prompt_yn "Require email verification for new users?" "n" email_verify
            yaml_set '.auth.local.emailVerification' "$email_verify" "$CONFIG_FILE"
            
            if [ "$email_verify" = "true" ]; then
                # ─── SMTP Configuration ──────────────────────────────────────────────────────
                print_section "SMTP Configuration (for email verification)"
                
                yaml_set '.smtp.enabled' 'true' "$CONFIG_FILE"
                
                prompt "SMTP server hostname (e.g., smtp.gmail.com)" "" smtp_host
                prompt "SMTP port (587 for TLS, 465 for SSL)" "587" smtp_port
                
                smtp_secure="false"
                if [ "$smtp_port" = "465" ]; then
                    smtp_secure="true"
                fi
                
                prompt "SMTP username/email" "" smtp_user
                prompt "SMTP password/key" "" smtp_pass true
                prompt "From address (e.g., RepoGoon <noreply@example.com>)" "RepoGoon <noreply@${server_domain:-localhost}>" smtp_from
                
                yaml_set '.smtp.host' "$smtp_host" "$CONFIG_FILE"
                yaml_set '.smtp.port' "$smtp_port" "$CONFIG_FILE"
                yaml_set '.smtp.secure' "$smtp_secure" "$CONFIG_FILE"
                yaml_set '.smtp.user' "$smtp_user" "$CONFIG_FILE"
                yaml_set '.smtp.pass' "$smtp_pass" "$CONFIG_FILE"
                yaml_set '.smtp.from' "$smtp_from" "$CONFIG_FILE"
                
                print_success "SMTP configuration saved"
            else
                yaml_set '.smtp.enabled' 'false' "$CONFIG_FILE"
            fi
        else
            yaml_set '.auth.local.emailVerification' 'false' "$CONFIG_FILE"
            yaml_set '.smtp.enabled' 'false' "$CONFIG_FILE"
        fi
    fi
    
    # LDAP
    prompt_yn "Configure LDAP/Active Directory authentication?" "n" ldap_enabled
    
    if [ "$ldap_enabled" = "true" ]; then
        yaml_set '.auth.ldap.enabled' 'true' "$CONFIG_FILE"
        
        prompt "LDAP URL (e.g., ldap://ldap.example.com:389)" "" ldap_url
        prompt "Bind DN (e.g., cn=admin,dc=example,dc=com)" "" ldap_bind_dn
        prompt "Bind Password" "" ldap_bind_pass true
        prompt "Search Base (e.g., ou=users,dc=example,dc=com)" "" ldap_search_base
        prompt "Search Filter (e.g., (uid={{username}}))" "(uid={{username}})" ldap_filter
        prompt "Username attribute" "uid" ldap_username_attribute
        prompt "Email attribute" "mail" ldap_email_attribute
        prompt "Display name attribute" "cn" ldap_display_name_attribute
        prompt_yn "Reject untrusted LDAP TLS certificates?" "y" ldap_reject_unauthorized
        
        yaml_set '.auth.ldap.url' "$ldap_url" "$CONFIG_FILE"
        yaml_set '.auth.ldap.bindDN' "$ldap_bind_dn" "$CONFIG_FILE"
        yaml_set '.auth.ldap.bindPassword' "$ldap_bind_pass" "$CONFIG_FILE"
        yaml_set '.auth.ldap.searchBase' "$ldap_search_base" "$CONFIG_FILE"
        yaml_set '.auth.ldap.searchFilter' "$ldap_filter" "$CONFIG_FILE"
        yaml_set '.auth.ldap.usernameAttribute' "$ldap_username_attribute" "$CONFIG_FILE"
        yaml_set '.auth.ldap.emailAttribute' "$ldap_email_attribute" "$CONFIG_FILE"
        yaml_set '.auth.ldap.displayNameAttribute' "$ldap_display_name_attribute" "$CONFIG_FILE"
        yaml_set '.auth.ldap.tlsOptions.rejectUnauthorized' "$ldap_reject_unauthorized" "$CONFIG_FILE"
        
        prompt_yn "Configure LDAP group-based access?" "n" ldap_groups
        if [ "$ldap_groups" = "true" ]; then
            prompt "Group Search Base" "" ldap_group_base
            prompt "Group Search Filter" "(member={{dn}})" ldap_group_filter
            prompt "Required Group (leave empty for no requirement)" "" ldap_required_group
            prompt "Admin Group (members get admin role)" "" ldap_admin_group
            
            yaml_set '.auth.ldap.groupSearchBase' "$ldap_group_base" "$CONFIG_FILE"
            yaml_set '.auth.ldap.groupSearchFilter' "$ldap_group_filter" "$CONFIG_FILE"
            yaml_set '.auth.ldap.requiredGroup' "$ldap_required_group" "$CONFIG_FILE"
            yaml_set '.auth.ldap.adminGroup' "$ldap_admin_group" "$CONFIG_FILE"
        fi
    else
        yaml_set '.auth.ldap.enabled' 'false' "$CONFIG_FILE"
    fi
    
    # OIDC
    prompt_yn "Configure OpenID Connect (OIDC) authentication?" "n" oidc_enabled
    
    if [ "$oidc_enabled" = "true" ]; then
        yaml_set '.auth.oidc.enabled' 'true' "$CONFIG_FILE"
        
        prompt "Provider name (displayed to users)" "SSO" oidc_name
        prompt "Issuer URL (OIDC Discovery endpoint)" "" oidc_issuer
        prompt "Client ID" "" oidc_client_id
        prompt "Client Secret" "" oidc_client_secret true
        prompt "Callback URL (leave blank to auto-generate)" "" oidc_callback_url
        prompt "OIDC scopes (comma-separated)" "openid,profile,email" oidc_scopes
        prompt "Username claim" "preferred_username" oidc_claim_username
        prompt "Email claim" "email" oidc_claim_email
        prompt "Display name claim" "name" oidc_claim_display_name
        
        yaml_set '.auth.oidc.providerName' "$oidc_name" "$CONFIG_FILE"
        yaml_set '.auth.oidc.issuerUrl' "$oidc_issuer" "$CONFIG_FILE"
        yaml_set '.auth.oidc.clientId' "$oidc_client_id" "$CONFIG_FILE"
        yaml_set '.auth.oidc.clientSecret' "$oidc_client_secret" "$CONFIG_FILE"
        yaml_set '.auth.oidc.callbackUrl' "$oidc_callback_url" "$CONFIG_FILE"
        yaml_set_json '.auth.oidc.scopes' "$(csv_to_json_array "$oidc_scopes")" "$CONFIG_FILE"
        yaml_set '.auth.oidc.claims.username' "$oidc_claim_username" "$CONFIG_FILE"
        yaml_set '.auth.oidc.claims.email' "$oidc_claim_email" "$CONFIG_FILE"
        yaml_set '.auth.oidc.claims.displayName' "$oidc_claim_display_name" "$CONFIG_FILE"
        
        prompt_yn "Auto-create users on first OIDC login?" "y" oidc_auto_create
        prompt "Default role for auto-created users" "user" oidc_default_role
        yaml_set '.auth.oidc.autoCreateUsers' "$oidc_auto_create" "$CONFIG_FILE"
        yaml_set '.auth.oidc.defaultRole' "$oidc_default_role" "$CONFIG_FILE"
        
        prompt_yn "Configure OIDC admin role detection?" "n" oidc_admin
        if [ "$oidc_admin" = "true" ]; then
            prompt "Admin claim name (e.g., groups, roles)" "groups" oidc_admin_claim
            prompt "Admin claim value (e.g., repogoon-admins)" "" oidc_admin_value
            
            yaml_set '.auth.oidc.adminClaim' "$oidc_admin_claim" "$CONFIG_FILE"
            yaml_set '.auth.oidc.adminClaimValue' "$oidc_admin_value" "$CONFIG_FILE"
        fi
        
        print_info "Note: Install openid-client package: npm install openid-client"
    else
        yaml_set '.auth.oidc.enabled' 'false' "$CONFIG_FILE"
    fi

    prompt_yn "Enable Shoo authentication?" "n" shoo_enabled
    yaml_set '.auth.shoo.enabled' "$shoo_enabled" "$CONFIG_FILE"
    
    # ─── Database Configuration ─────────────────────────────────────────────────
    print_section "Database Configuration"
    
    echo -e "\nDatabase type:"
    prompt_select "Select database type" "sqlite postgresql mysql oracle convex" "1" db_type
    
    yaml_set '.database.type' "$db_type" "$CONFIG_FILE"
    
    case "$db_type" in
        sqlite)
            local default_sqlite_path="./server/db/repogoon.db"
            
            # Use /var/lib/repogoon default for RPM installs
            if [ "$SCRIPT_PATH" = "/usr/bin" ] || [ "$SCRIPT_PATH" = "/bin" ]; then
                default_sqlite_path="/var/lib/repogoon/repogoon.db"
            fi
            
            prompt "SQLite database path" "$default_sqlite_path" sqlite_path
            yaml_set '.database.sqlite.path' "$sqlite_path" "$CONFIG_FILE"
            
            # Remove existing database to start fresh
            local abs_sqlite_path="$sqlite_path"
            # Convert relative path to absolute if needed
            if [[ "$sqlite_path" != /* ]]; then
                abs_sqlite_path="${SCRIPT_DIR}/${sqlite_path}"
            fi
            if [ -f "$abs_sqlite_path" ]; then
                print_info "Removing existing database to start fresh..."
                rm -f "$abs_sqlite_path"
            fi
            # Ensure the directory exists
            local db_dir
            db_dir="$(dirname "$abs_sqlite_path")"
            mkdir -p "$db_dir"
            
            if getent group repogoon >/dev/null; then
                 chown -R repogoon:repogoon "$db_dir" 2>/dev/null || true
                 chmod 750 "$db_dir" 2>/dev/null || true
            fi
            
            # Update systemd service to allow writing to the database directory
            local service_file="/usr/lib/systemd/system/repogoon.service"
            if [ -f "$service_file" ]; then
                # Check if this path is already in ReadWritePaths
                if ! grep -q "ReadWritePaths=$db_dir" "$service_file" 2>/dev/null; then
                    # Add the database directory to ReadWritePaths
                    if grep -q "ReadWritePaths=" "$service_file"; then
                        # Add after the last ReadWritePaths line
                        sed -i "/ReadWritePaths=/a ReadWritePaths=$db_dir" "$service_file"
                        print_info "Added $db_dir to systemd service ReadWritePaths"
                        systemctl daemon-reload 2>/dev/null || true
                    fi
                fi
            fi
            ;;
        postgresql|mysql)
            prompt "${db_type^} Host" "localhost" db_host
            
            local default_port="5432"
            [ "$db_type" = "mysql" ] && default_port="3306"
            prompt "${db_type^} Port" "$default_port" db_port
            
            prompt "${db_type^} Database name" "repogoon" db_name
            
            prompt_yn "Are you using the database root/admin user?" "n" is_root
            
            if [ "$is_root" = "true" ]; then
                prompt "${db_type^} admin username" "postgres" db_admin_user
                prompt "${db_type^} admin password" "" db_admin_pass true
                
                prompt_yn "Would you like to create a dedicated 'repogoon' user?" "y" create_user
                
                if [ "$create_user" = "true" ]; then
                    local app_password=$(generate_password 16)
                    
                    print_info "Creating database user..."
                    
                    # Create user using admin credentials
                    if [ "$db_type" = "postgresql" ]; then
                        PGPASSWORD="$db_admin_pass" psql -h "$db_host" -p "$db_port" -U "$db_admin_user" -d postgres << EOF
CREATE USER repogoon WITH PASSWORD '${app_password}';
CREATE DATABASE repogoon OWNER repogoon;
GRANT ALL PRIVILEGES ON DATABASE repogoon TO repogoon;
EOF
                    else
                        mysql -h "$db_host" -P "$db_port" -u "$db_admin_user" -p"$db_admin_pass" << EOF
CREATE USER IF NOT EXISTS 'repogoon'@'%' IDENTIFIED BY '${app_password}';
CREATE DATABASE IF NOT EXISTS repogoon;
GRANT ALL PRIVILEGES ON repogoon.* TO 'repogoon'@'%';
FLUSH PRIVILEGES;
EOF
                    fi
                    
                    print_success "Created user 'repogoon' with password: ${app_password}"
                    print_success "Created database 'repogoon'"
                    
                    db_user="repogoon"
                    db_pass="$app_password"
                fi
            else
                prompt "${db_type^} username" "repogoon" db_user
                prompt "${db_type^} password" "" db_pass true
            fi
            
            prompt_yn "Enable SSL for database connection?" "n" db_ssl
            
            yaml_set ".database.${db_type}.host" "$db_host" "$CONFIG_FILE"
            yaml_set ".database.${db_type}.port" "$db_port" "$CONFIG_FILE"
            yaml_set ".database.${db_type}.database" "$db_name" "$CONFIG_FILE"
            yaml_set ".database.${db_type}.username" "$db_user" "$CONFIG_FILE"
            yaml_set ".database.${db_type}.password" "$db_pass" "$CONFIG_FILE"
            yaml_set ".database.${db_type}.ssl" "$db_ssl" "$CONFIG_FILE"
            ;;
        oracle)
            prompt "Oracle Host" "localhost" db_host
            prompt "Oracle Port" "1521" db_port
            prompt "Oracle Service Name" "ORCL" db_service
            prompt "Oracle username" "repogoon" db_user
            prompt "Oracle password" "" db_pass true
            
            yaml_set '.database.oracle.host' "$db_host" "$CONFIG_FILE"
            yaml_set '.database.oracle.port' "$db_port" "$CONFIG_FILE"
            yaml_set '.database.oracle.serviceName' "$db_service" "$CONFIG_FILE"
            yaml_set '.database.oracle.username' "$db_user" "$CONFIG_FILE"
            yaml_set '.database.oracle.password' "$db_pass" "$CONFIG_FILE"
            ;;
        convex)
            prompt_yn "Use self-hosted Convex?" "n" convex_self_hosted
            yaml_set '.database.convex.selfHosted' "$convex_self_hosted" "$CONFIG_FILE"

            if [ "$convex_self_hosted" = "true" ]; then
                prompt "Self-hosted Convex URL" "" convex_self_url
                prompt "Self-hosted Convex admin key" "" convex_self_key true

                yaml_set '.database.convex.selfHostedUrl' "$convex_self_url" "$CONFIG_FILE"
                yaml_set '.database.convex.selfHostedAdminKey' "$convex_self_key" "$CONFIG_FILE"
            else
                prompt "Convex Deployment URL" "" convex_url
                prompt "Convex Admin Key" "" convex_key true
                
                yaml_set '.database.convex.deploymentUrl' "$convex_url" "$CONFIG_FILE"
                yaml_set '.database.convex.adminKey' "$convex_key" "$CONFIG_FILE"
            fi
            ;;
    esac
    
    # ─── Generate Session/Internal Secrets ──────────────────────────────────────
    print_section "Session Configuration"
    
    local session_secret=$(generate_secret 64)
    yaml_set '.session.secret' "$session_secret" "$CONFIG_FILE"
    print_success "Generated session secret"
    local internal_secret=$(generate_secret 64)
    yaml_set '.server.internalSecret' "$internal_secret" "$CONFIG_FILE"
    print_success "Generated internal hook secret"
    
    # Set CORS origin based on domain
    if [ -n "$server_domain" ]; then
        local protocol="http"
        [ "$ssl_enabled" = "true" ] && protocol="https"
        yaml_set '.cors.origin' "${protocol}://${server_domain}" "$CONFIG_FILE"
    else
        yaml_set '.cors.origin' "http://localhost:5173" "$CONFIG_FILE"
    fi
    yaml_set '.session.secure' "$ssl_enabled" "$CONFIG_FILE"
    
    # ─── Initialize Database ────────────────────────────────────────────────────
    print_section "Finalizing Setup"
    
    echo -n "Testing database connection... "
    if cmd_test_db > /dev/null 2>&1; then
        print_success ""
    else
        print_error ""
        print_warning "Database connection failed. You may need to check your configuration."
    fi
    
    echo -n "Initializing database schema... "
    if cmd_init_db > /dev/null 2>&1; then
        print_success ""
    else
        print_error ""
    fi
    
    # Create admin user
    echo -n "Creating admin user... "
    local admin_password=$(generate_password 16)
    local admin_email="admin@${server_domain:-localhost}"
    
    cd "$SCRIPT_DIR"
    node --import tsx server/db/create-admin.ts "$admin_email" "$admin_password" > /dev/null 2>&1 || true
    print_success ""
    
    # Generate nginx config if enabled
    if [ "$nginx_enabled" = "true" ] && [ -n "$server_domain" ]; then
        echo -n "Generating nginx configuration... "
        if cmd_nginx_config > /dev/null 2>&1; then
            print_success ""
            
            echo -n "Reloading nginx... "
            if cmd_nginx_reload > /dev/null 2>&1; then
                print_success ""
            else
                print_warning "Could not reload nginx automatically"
            fi
        else
            print_warning "Could not generate nginx config"
        fi
    fi
    
    # ─── Setup Complete ─────────────────────────────────────────────────────────
    print_header "Setup Complete!"
    
    echo "Configuration saved to: $CONFIG_FILE"
    echo
    
    echo -e "${BOLD}┌─────────────────────────────────────────────────────┐${NC}"
    echo -e "${BOLD}│  ${YELLOW}ADMIN CREDENTIALS (SAVE THESE!)${NC}${BOLD}                    │${NC}"
    echo -e "${BOLD}│                                                     │${NC}"
    echo -e "${BOLD}│  Username: ${GREEN}admin${NC}${BOLD}                                    │${NC}"
    echo -e "${BOLD}│  Password: ${GREEN}${admin_password}${NC}${BOLD}                        │${NC}"
    echo -e "${BOLD}│  Email:    ${GREEN}${admin_email}${NC}${BOLD}                   │${NC}"
    echo -e "${BOLD}│                                                     │${NC}"
    echo -e "${BOLD}│  ${RED}⚠️  These credentials will NOT be shown again!${NC}${BOLD}    │${NC}"
    echo -e "${BOLD}└─────────────────────────────────────────────────────┘${NC}"
    echo
    
    # Start the server
    echo -n "Starting RepoGoon... "
    cmd_start > /dev/null 2>&1 && print_success "" || print_warning "Could not start automatically"
    
    echo
    if [ -n "$server_domain" ]; then
        local protocol="http"
        [ "$ssl_enabled" = "true" ] && protocol="https"
        print_info "Configured public URL: ${protocol}://${server_domain}"
        print_info "Local app URL: http://localhost:${server_port}"
    else
        print_info "App URL: http://localhost:${server_port}"
    fi
    
    echo
    echo "To manage the service:"
    echo "  ./rgoon-ctl status"
    echo "  ./rgoon-ctl stop"
    echo "  ./rgoon-ctl restart"
}

# ─── Database Migration ──────────────────────────────────────────────────────

cmd_wipe_db() {
    local db_type=""

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --db)
                db_type=$(echo "$2" | tr '[:upper:]' '[:lower:]')
                shift 2
                ;;
            *)
                shift
                ;;
        esac
    done

    if [ -z "$db_type" ]; then
        print_error "Usage: rgoon-ctl wipe-db --db <db-type>"
        print_info "Supported types: sqlite, postgresql, mysql, oracle, convex"
        exit 1
    fi

    local valid_dbs="sqlite postgresql mysql oracle convex"
    if ! echo "$valid_dbs" | grep -qw "$db_type"; then
        print_error "Unknown database type: $db_type"
        print_info "Supported types: $valid_dbs"
        exit 1
    fi

    local configured_db
    configured_db=$(yaml_get '.database.type' "$CONFIG_FILE" 2>/dev/null)

    print_header "Database Wipe: $db_type"
    print_warning "This will permanently delete ALL data from '$db_type'."
    print_warning "This action cannot be undone."

    if [ -n "$configured_db" ] && [ "$configured_db" != "null" ] && [ "$configured_db" != "$db_type" ]; then
        print_warning "Configured active database.type is '$configured_db'."
        print_warning "You requested wipe for '$db_type'."
    fi

    echo
    prompt_yn "Continue with wipe?" "n" confirm_wipe
    if [ "$confirm_wipe" != "true" ]; then
        print_info "Wipe cancelled."
        exit 0
    fi

    cd "$SCRIPT_DIR"
    node --import tsx server/db/wipe-db.ts --db "$db_type"
}

cmd_migrate_db() {
    local from_db=""
    local to_db=""
    
    # Parse arguments
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --from)
                from_db="$2"
                shift 2
                ;;
            --to)
                to_db="$2"
                shift 2
                ;;
            *)
                shift
                ;;
        esac
    done
    
    if [ -z "$from_db" ] || [ -z "$to_db" ]; then
        print_error "Usage: rgoon-ctl migrate-db --from <db-type> --to <db-type>"
        print_info "Supported types: sqlite, postgresql, mysql, oracle, convex"
        print_info "Example: rgoon-ctl migrate-db --from sqlite --to convex"
        exit 1
    fi
    
    # Validate database types
    local valid_dbs="sqlite postgresql mysql oracle convex"
    if ! echo "$valid_dbs" | grep -qw "$from_db"; then
        print_error "Unknown source database type: $from_db"
        print_info "Supported types: $valid_dbs"
        exit 1
    fi
    
    if ! echo "$valid_dbs" | grep -qw "$to_db"; then
        print_error "Unknown target database type: $to_db"
        print_info "Supported types: $valid_dbs"
        exit 1
    fi
    
    if [ "$from_db" = "$to_db" ]; then
        print_error "Source and target database types must be different"
        exit 1
    fi
    
    print_header "Database Migration: $from_db → $to_db"
    
    print_warning "This will migrate all data from $from_db to $to_db."
    print_warning "Make sure you have a backup before proceeding!"
    echo
    
    prompt_yn "Continue with migration?" "n" continue_migration
    if [ "$continue_migration" != "true" ]; then
        print_info "Migration cancelled."
        exit 0
    fi
    
    cd "$SCRIPT_DIR"
    node --import tsx server/db/migrate-db.ts --from "$from_db" --to "$to_db"
}

# ─── Backup & Restore ────────────────────────────────────────────────────────

cmd_backup() {
    print_header "RepoGoon Backup"
    
    local timestamp=$(date +%Y%m%d-%H%M%S)
    local backup_dir="${1:-/var/backups/repogoon}"
    local backup_file="${backup_dir}/repogoon-${timestamp}.tar.gz"
    
    # Create backup directory if it doesn't exist
    if [ ! -d "$backup_dir" ]; then
        sudo mkdir -p "$backup_dir"
        sudo chown "$(whoami)" "$backup_dir"
        sudo chmod 750 "$backup_dir"
    fi
    
    print_info "Creating backup: $backup_file"
    
    # Determine what to back up based on installation type
    local items_to_backup=()
    local temp_backup_dir=$(mktemp -d)
    
    # Copy config file
    if [ -f "$CONFIG_FILE" ]; then
        cp "$CONFIG_FILE" "$temp_backup_dir/config.yml"
        print_success "Config file included"
    fi
    
    # Get database path from config
    local db_path
    if [ "$SCRIPT_PATH" = "/usr/bin" ] || [ "$SCRIPT_PATH" = "/bin" ]; then
        db_path=$(yaml_get '.database.sqlite.path' "$CONFIG_FILE")
        [ -z "$db_path" ] && db_path="/var/lib/repogoon/repogoon.db"
    else
        db_path="${SCRIPT_DIR}/server/db/repogoon.db"
    fi
    
    # Copy database if SQLite
    local db_type=$(yaml_get '.database.type' "$CONFIG_FILE")
    if [ "$db_type" = "sqlite" ] && [ -f "$db_path" ]; then
        # Use SQLite backup command for safe copy
        sqlite3 "$db_path" ".backup '$temp_backup_dir/repogoon.db'"
        print_success "SQLite database backed up"
    elif [ "$db_type" != "sqlite" ]; then
        print_warning "Non-SQLite database detected. Database not included in backup."
        print_info "Please use $db_type native backup tools."
    fi
    
    # Get repos path
    local repos_path
    if [ "$SCRIPT_PATH" = "/usr/bin" ] || [ "$SCRIPT_PATH" = "/bin" ]; then
        repos_path="/var/lib/repogoon/repos"
    else
        repos_path="${SCRIPT_DIR}/repos"
    fi
    
    # Copy repos directory
    if [ -d "$repos_path" ]; then
        cp -r "$repos_path" "$temp_backup_dir/repos"
        print_success "Git repositories backed up"
    fi
    
    # Copy LFS objects if they exist
    local lfs_path
    if [ "$SCRIPT_PATH" = "/usr/bin" ] || [ "$SCRIPT_PATH" = "/bin" ]; then
        lfs_path="/var/lib/repogoon/lfs-objects"
    else
        lfs_path="${SCRIPT_DIR}/lfs-objects"
    fi
    
    if [ -d "$lfs_path" ]; then
        cp -r "$lfs_path" "$temp_backup_dir/lfs-objects"
        print_success "LFS objects backed up"
    fi
    
    # Create tarball
    tar -czf "$backup_file" -C "$temp_backup_dir" .
    rm -rf "$temp_backup_dir"
    
    local size=$(du -h "$backup_file" | cut -f1)
    print_success "Backup complete: $backup_file ($size)"
    
    # Show recent backups
    echo
    print_info "Recent backups:"
    ls -lht "$backup_dir"/repogoon-*.tar.gz 2>/dev/null | head -5
}

cmd_restore() {
    local backup_file="$1"
    
    if [ -z "$backup_file" ]; then
        print_error "Usage: rgoon-ctl restore <backup-file.tar.gz>"
        exit 1
    fi
    
    if [ ! -f "$backup_file" ]; then
        print_error "Backup file not found: $backup_file"
        exit 1
    fi
    
    print_header "RepoGoon Restore"
    print_warning "This will overwrite current data!"
    prompt_yn "Are you sure you want to continue?" "n" confirm
    
    if [ "$confirm" != "true" ]; then
        print_info "Restore cancelled."
        exit 0
    fi
    
    # Stop service
    print_info "Stopping RepoGoon service..."
    cmd_stop 2>/dev/null || true
    
    # Extract to temp directory
    local temp_restore_dir=$(mktemp -d)
    tar -xzf "$backup_file" -C "$temp_restore_dir"
    
    # Restore config
    if [ -f "$temp_restore_dir/config.yml" ]; then
        cp "$temp_restore_dir/config.yml" "$CONFIG_FILE"
        print_success "Config file restored"
    fi
    
    # Determine paths
    local db_path repos_path lfs_path
    if [ "$SCRIPT_PATH" = "/usr/bin" ] || [ "$SCRIPT_PATH" = "/bin" ]; then
        db_path="/var/lib/repogoon/repogoon.db"
        repos_path="/var/lib/repogoon/repos"
        lfs_path="/var/lib/repogoon/lfs-objects"
    else
        db_path="${SCRIPT_DIR}/server/db/repogoon.db"
        repos_path="${SCRIPT_DIR}/repos"
        lfs_path="${SCRIPT_DIR}/lfs-objects"
    fi
    
    # Restore database
    if [ -f "$temp_restore_dir/repogoon.db" ]; then
        # Remove WAL files if they exist
        rm -f "${db_path}-wal" "${db_path}-shm" 2>/dev/null || true
        cp "$temp_restore_dir/repogoon.db" "$db_path"
        print_success "Database restored"
    fi
    
    # Restore repos
    if [ -d "$temp_restore_dir/repos" ]; then
        rm -rf "$repos_path"
        cp -r "$temp_restore_dir/repos" "$repos_path"
        print_success "Git repositories restored"
    fi
    
    # Restore LFS objects
    if [ -d "$temp_restore_dir/lfs-objects" ]; then
        rm -rf "$lfs_path"
        cp -r "$temp_restore_dir/lfs-objects" "$lfs_path"
        print_success "LFS objects restored"
    fi
    
    rm -rf "$temp_restore_dir"
    
    # Fix permissions if needed
    if [ "$SCRIPT_PATH" = "/usr/bin" ] || [ "$SCRIPT_PATH" = "/bin" ]; then
        chown -R repogoon:repogoon /var/lib/repogoon 2>/dev/null || true
    fi
    
    print_success "Restore complete!"
    
    # Restart service
    print_info "Starting RepoGoon..."
    cmd_start
}

# ─── Doctor / Health Check ───────────────────────────────────────────────────

run_db_connection_probe() {
    (
        cd "$SCRIPT_DIR"
        node --import tsx -e "
            import('./server/db/index.ts').then(async db => {
                const result = await db.testConnection();
                if (!result.success) {
                    console.error(result.message || 'connection failed');
                    process.exit(1);
                }
                process.exit(0);
            }).catch(error => {
                console.error(error instanceof Error ? error.message : String(error));
                process.exit(1);
            });
        "
    )
}

format_probe_error() {
    local probe_output="$1"
    local detail
    detail=$(printf '%s\n' "$probe_output" | sed '/^[[:space:]]*$/d' | tail -1)

    if [ -n "$detail" ]; then
        echo ": $detail"
    fi
}

docker_registry_latest_container_version() {
    local image="${REPOGOON_CONTAINER_IMAGE:-burningpho3nix/repogoon}"

    if ! command_exists curl || ! command_exists node; then
        return 1
    fi

    local token_response
    token_response=$(curl -fsS --connect-timeout 3 \
        "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${image}:pull" 2>/dev/null) || return 1

    local token
    token=$(node --input-type=module -e '
        const response = JSON.parse(process.argv[1] || "{}");
        process.stdout.write(response.token || "");
    ' "$token_response") || return 1

    if [ -z "$token" ]; then
        return 1
    fi

    local tags_response
    tags_response=$(curl -fsS --connect-timeout 3 \
        -H "Authorization: Bearer ${token}" \
        "https://registry-1.docker.io/v2/${image}/tags/list" 2>/dev/null) || return 1

    node --input-type=module -e '
        const response = JSON.parse(process.argv[1] || "{}");
        const parseVersion = tag => {
            const match = /^v?(\d+)\.(\d+)\.(\d+)$/.exec(tag);
            if (!match) return null;
            return {
                tag: match[0].replace(/^v/, ""),
                parts: match.slice(1).map(Number),
            };
        };
        const versions = (Array.isArray(response.tags) ? response.tags : [])
            .map(parseVersion)
            .filter(Boolean)
            .sort((a, b) => {
                for (let i = 0; i < 3; i += 1) {
                    if (a.parts[i] !== b.parts[i]) return b.parts[i] - a.parts[i];
                }
                return 0;
            });
        if (versions[0]) {
            process.stdout.write(versions[0].tag);
        }
    ' "$tags_response"
}

version_gt() {
    local left="$1"
    local right="$2"

    [ "$left" != "$right" ] && [ "$(printf '%s\n%s\n' "$right" "$left" | sort -V | tail -1)" = "$left" ]
}

cmd_doctor() {
    print_header "RepoGoon Health Check"
    
    local issues=0
    
    # Check for updates
    echo -n "Version check... "
    local installed_version=""
    # Containers are not RPM installs; the image carries package.json.
    if is_container_install && [ -f "${SCRIPT_DIR}/package.json" ]; then
        installed_version=$(grep '"version"' "${SCRIPT_DIR}/package.json" | sed 's/.*"version": *"\([^"]*\)".*/\1/')
    fi
    # Get installed version from RPM
    if [ -z "$installed_version" ] && command -v rpm &> /dev/null; then
        installed_version=$(rpm -q --queryformat "%{VERSION}-%{RELEASE}" repogoon 2>/dev/null | grep -E "^[0-9]" || true)
    fi
    # Fallback to package.json
    if [ -z "$installed_version" ]; then
        if [ -f "${SCRIPT_DIR}/package.json" ]; then
            installed_version=$(grep '"version"' "${SCRIPT_DIR}/package.json" | sed 's/.*"version": *"\([^"]*\)".*/\1/')
        elif [ -f "/usr/share/repogoon/package.json" ]; then
            installed_version=$(grep '"version"' "/usr/share/repogoon/package.json" | sed 's/.*"version": *"\([^"]*\)".*/\1/')
        fi
    fi
    
    if [ -n "$installed_version" ]; then
        if is_container_install; then
            local latest_container_version=""
            latest_container_version=$(docker_registry_latest_container_version 2>/dev/null || true)

            if [ -n "$latest_container_version" ]; then
                if [ "$installed_version" = "$latest_container_version" ]; then
                    print_success "v$installed_version (container image up to date)"
                elif version_gt "$latest_container_version" "$installed_version"; then
                    print_warning "v$installed_version (container image update available: v$latest_container_version)"
                else
                    print_success "v$installed_version (newer than published container tag)"
                fi
            else
                print_info "v$installed_version (could not check container registry for updates)"
            fi
        elif command -v dnf &> /dev/null; then
            # Use dnf to check for updates
            # dnf check-update returns 100 if updates are available, 0 if not, 1 on error
            if dnf check-update --refresh repogoon &>/dev/null; then
                # Exit code 0 means up to date
                print_success "v$installed_version (up to date)"
            elif [ $? -eq 100 ]; then
                # Exit code 100 means update available
                latest_version=$(dnf list updates repogoon -q | grep "^repogoon\." | awk '{print $2}')
                if [ -n "$latest_version" ]; then
                    print_warning "v$installed_version (update available: v$latest_version)"
                else
                    print_warning "v$installed_version (update available)"
                fi
            else
                print_info "v$installed_version (could not check for updates via dnf)"
            fi
        elif command -v curl &> /dev/null; then
            # Fallback to Copr API if dnf is not available (e.g. on Debian)
            local latest_version=""
            # COPR API - get latest successful build for the package
            local copr_response=$(curl -s --connect-timeout 3 "https://copr.fedorainfracloud.org/api_3/build/list?ownername=burningpho3nix&projectname=private-projects&packagename=repogoon&status=succeeded&limit=1" 2>/dev/null)
            if [ -n "$copr_response" ]; then
                # Extract full version including release (format: "0.4.20260112-5")
                latest_version=$(echo "$copr_response" | grep -o '"version": *"[0-9][^"]*"' | head -1 | sed 's/"version": *"\([^"]*\)"/\1/')
            fi
            
            if [ -n "$latest_version" ]; then
                # Compare versions including release number
                if [ "$installed_version" = "$latest_version" ]; then
                    print_success "v$installed_version (up to date)"
                elif [[ "$installed_version" > "$latest_version" ]]; then
                    print_success "v$installed_version (newer than release)"
                else
                    print_warning "v$installed_version (update available: v$latest_version)"
                fi
            else
                print_info "v$installed_version (could not check for updates)"
            fi
        else
            print_info "v$installed_version (could not check for updates)"
        fi
    else
        print_warning "unknown version"
    fi
    
    # Check Node.js version
    echo -n "Node.js version... "
    if command -v node &> /dev/null; then
        local node_version=$(node -v | sed 's/v//')
        local major_version=$(echo "$node_version" | cut -d. -f1)
        if [ "$major_version" -ge 18 ]; then
            print_success "v$node_version"
        else
            print_error "v$node_version (requires >= 18)"
            issues=$((issues + 1))
        fi
    else
        print_error "not found"
        issues=$((issues + 1))
    fi
    
    # Check Git version
    echo -n "Git version... "
    if command -v git &> /dev/null; then
        local git_version=$(git --version | sed 's/git version //')
        local git_major=$(echo "$git_version" | cut -d. -f1)
        if [ "$git_major" -ge 2 ]; then
            print_success "v$git_version"
        else
            print_error "v$git_version (requires >= 2.0)"
            issues=$((issues + 1))
        fi
    else
        print_error "not found"
        issues=$((issues + 1))
    fi
    
    # Check config file
    echo -n "Config file... "
    if [ -f "$CONFIG_FILE" ]; then
        print_success "$CONFIG_FILE"
    else
        print_error "not found"
        issues=$((issues + 1))
    fi
    
    # Check database
    echo -n "Database... "
    local db_type=$(yaml_get '.database.type' "$CONFIG_FILE" 2>/dev/null)
    if [ "$db_type" = "sqlite" ]; then
        local db_path
        if [ "$SCRIPT_PATH" = "/usr/bin" ] || [ "$SCRIPT_PATH" = "/bin" ]; then
            db_path=$(yaml_get '.database.sqlite.path' "$CONFIG_FILE")
            [ -z "$db_path" ] && db_path="/var/lib/repogoon/repogoon.db"
        else
            db_path="${SCRIPT_DIR}/server/db/repogoon.db"
        fi
        
        if [ -f "$db_path" ]; then
            # Check if sqlite3 CLI is available
            if ! command -v sqlite3 &> /dev/null; then
                print_warning "SQLite ($db_path) - sqlite3 CLI not installed, skipping integrity check"
            else
                # Check integrity (case-insensitive comparison)
                local integrity=$(sqlite3 "$db_path" "PRAGMA integrity_check;" 2>&1)
                if echo "$integrity" | grep -iq "^ok$"; then
                    print_success "SQLite ($db_path) - integrity OK"
                else
                    print_error "SQLite integrity check failed: $integrity"
                    issues=$((issues + 1))
                fi
            fi
        else
            print_warning "SQLite database not found (will be created on first run)"
        fi
    elif [ "$db_type" = "postgresql" ]; then
        local pg_host=$(yaml_get '.database.postgresql.host' "$CONFIG_FILE" 2>/dev/null)
        local pg_port=$(yaml_get '.database.postgresql.port' "$CONFIG_FILE" 2>/dev/null)
        local pg_db=$(yaml_get '.database.postgresql.database' "$CONFIG_FILE" 2>/dev/null)
        local pg_user=$(yaml_get '.database.postgresql.username' "$CONFIG_FILE" 2>/dev/null)
        local pg_pass=$(yaml_get '.database.postgresql.password' "$CONFIG_FILE" 2>/dev/null)
        
        pg_host=${pg_host:-localhost}
        pg_port=${pg_port:-5432}
        pg_db=${pg_db:-repogoon}
        
        if command -v psql &> /dev/null; then
            # Use psql with timeout to test connection
            if PGPASSWORD="$pg_pass" psql -h "$pg_host" -p "$pg_port" -U "$pg_user" -d "$pg_db" -c "SELECT 1" &> /dev/null; then
                print_success "PostgreSQL ($pg_host:$pg_port/$pg_db) - connected"
            else
                print_error "PostgreSQL ($pg_host:$pg_port/$pg_db) - connection failed"
                issues=$((issues + 1))
            fi
        else
            # Fallback to Node.js test
            local probe_output
            if probe_output=$(run_db_connection_probe 2>&1); then
                print_success "PostgreSQL ($pg_host:$pg_port/$pg_db) - connected"
            else
                print_error "PostgreSQL ($pg_host:$pg_port/$pg_db) - connection failed$(format_probe_error "$probe_output")"
                issues=$((issues + 1))
            fi
        fi
    elif [ "$db_type" = "mysql" ]; then
        local mysql_host=$(yaml_get '.database.mysql.host' "$CONFIG_FILE" 2>/dev/null)
        local mysql_port=$(yaml_get '.database.mysql.port' "$CONFIG_FILE" 2>/dev/null)
        local mysql_db=$(yaml_get '.database.mysql.database' "$CONFIG_FILE" 2>/dev/null)
        local mysql_user=$(yaml_get '.database.mysql.username' "$CONFIG_FILE" 2>/dev/null)
        local mysql_pass=$(yaml_get '.database.mysql.password' "$CONFIG_FILE" 2>/dev/null)
        
        mysql_host=${mysql_host:-localhost}
        mysql_port=${mysql_port:-3306}
        mysql_db=${mysql_db:-repogoon}
        
        if command -v mysql &> /dev/null; then
            # Use mysql with timeout to test connection
            if mysql -h "$mysql_host" -P "$mysql_port" -u "$mysql_user" -p"$mysql_pass" -e "SELECT 1" "$mysql_db" &> /dev/null; then
                print_success "MySQL ($mysql_host:$mysql_port/$mysql_db) - connected"
            else
                print_error "MySQL ($mysql_host:$mysql_port/$mysql_db) - connection failed"
                issues=$((issues + 1))
            fi
        else
            # Fallback to Node.js test
            local probe_output
            if probe_output=$(run_db_connection_probe 2>&1); then
                print_success "MySQL ($mysql_host:$mysql_port/$mysql_db) - connected"
            else
                print_error "MySQL ($mysql_host:$mysql_port/$mysql_db) - connection failed$(format_probe_error "$probe_output")"
                issues=$((issues + 1))
            fi
        fi
    elif [ "$db_type" = "oracle" ]; then
        local ora_host=$(yaml_get '.database.oracle.host' "$CONFIG_FILE" 2>/dev/null)
        local ora_port=$(yaml_get '.database.oracle.port' "$CONFIG_FILE" 2>/dev/null)
        local ora_service=$(yaml_get '.database.oracle.serviceName' "$CONFIG_FILE" 2>/dev/null)
        
        ora_host=${ora_host:-localhost}
        ora_port=${ora_port:-1521}
        ora_service=${ora_service:-ORCL}
        
        # Oracle requires Node.js test (no simple CLI like psql/mysql)
        local probe_output
        if probe_output=$(run_db_connection_probe 2>&1); then
            print_success "Oracle ($ora_host:$ora_port/$ora_service) - connected"
        else
            print_error "Oracle ($ora_host:$ora_port/$ora_service) - connection failed$(format_probe_error "$probe_output")"
            issues=$((issues + 1))
        fi
    elif [ "$db_type" = "convex" ]; then
        local convex_url=$(yaml_get '.database.convex.deploymentUrl' "$CONFIG_FILE" 2>/dev/null)
        local convex_self_hosted=$(yaml_get '.database.convex.selfHosted' "$CONFIG_FILE" 2>/dev/null)
        local convex_self_url=$(yaml_get '.database.convex.selfHostedUrl' "$CONFIG_FILE" 2>/dev/null)
        
        # Determine which URL to display
        local display_url="$convex_url"
        if [ "$convex_self_hosted" = "true" ] && [ -n "$convex_self_url" ]; then
            display_url="$convex_self_url (self-hosted)"
        fi
        
        # Test Convex connection via Node.js
        local probe_output
        if probe_output=$(run_db_connection_probe 2>&1); then
            print_success "Convex ($display_url) - connected"
        else
            print_error "Convex ($display_url) - connection failed$(format_probe_error "$probe_output")"
            issues=$((issues + 1))
        fi
    else
        print_info "$db_type configured (external database)"
    fi
    
    # Check repos directory
    echo -n "Repos directory... "
    local repos_path
    if [ "$SCRIPT_PATH" = "/usr/bin" ] || [ "$SCRIPT_PATH" = "/bin" ]; then
        repos_path=$(yaml_get '.repositories.storagePath' "$CONFIG_FILE" 2>/dev/null || true)
        if [ -z "$repos_path" ] || [ "$repos_path" = "null" ]; then
            repos_path="/var/lib/repogoon/repos"
        fi
    else
        repos_path="${SCRIPT_DIR}/repos"
    fi
    
    if [ -d "$repos_path" ]; then
        local repo_count
        repo_count=$(timeout 5s find "$repos_path" -maxdepth 2 -type d -name "*.git" 2>/dev/null | wc -l)
        print_success "$repos_path ($repo_count repositories)"
    else
        print_warning "$repos_path (not created yet)"
    fi
    
    # Check disk space
    echo -n "Disk space... "
    local disk_free=$(df -h "$DATA_DIR" 2>/dev/null | tail -1 | awk '{print $4}')
    local disk_percent=$(df "$DATA_DIR" 2>/dev/null | tail -1 | awk '{print $5}' | sed 's/%//')
    if [ -n "$disk_percent" ] && [ "$disk_percent" -lt 90 ]; then
        print_success "$disk_free available"
    elif [ -n "$disk_percent" ]; then
        print_warning "$disk_free available (${disk_percent}% used)"
        issues=$((issues + 1))
    else
        print_warning "unable to check"
    fi
    
    local port
    port=$(get_server_port)
    local systemd_running=false

    if uses_systemd_service && systemctl is-active --quiet repogoon 2>/dev/null; then
        systemd_running=true
    fi

    # Check port availability
    echo -n "Server port... "
    if is_port_listening "$port"; then
        print_success "Port $port in use (server running)"
    elif [ "$systemd_running" = "true" ]; then
        print_warning "Port $port is not listening even though the systemd service is active"
        issues=$((issues + 1))
    else
        print_info "Port $port available"
    fi
    
    if is_container_install; then
        echo -n "Container process... "
        if is_port_listening "$port"; then
            print_success "running"
        else
            print_info "not running"
        fi
    # Check service status (if using systemd)
    elif uses_systemd_service; then
        echo -n "Systemd service... "
        if [ "$systemd_running" = "true" ]; then
            print_success "running"
        elif systemctl is-enabled --quiet repogoon 2>/dev/null; then
            print_warning "enabled but not running"
        else
            print_info "not enabled"
        fi
    fi
    
    # Summary
    echo
    if [ $issues -eq 0 ]; then
        print_success "All checks passed!"
    else
        print_warning "$issues issue(s) found"
    fi
    
    return $issues
}

# Install Git hooks on all repositories
cmd_install_hooks() {
    print_header "Installing Git Hooks"
    
    # Get repository storage path from config
    local repos_path=$(yaml_get '.repositories.storagePath' "$CONFIG_FILE")
    
    if [ -z "$repos_path" ] || [ "$repos_path" = "null" ]; then
        # Default paths based on installation type
        if [ "$SCRIPT_PATH" = "/usr/bin" ] || [ "$SCRIPT_PATH" = "/bin" ]; then
            repos_path="/var/lib/repogoon/repositories"
        else
            repos_path="${SCRIPT_DIR}/repositories"
        fi
    fi
    
    # Get hooks source directory
    local hooks_src
    if [ "$SCRIPT_PATH" = "/usr/bin" ] || [ "$SCRIPT_PATH" = "/bin" ]; then
        hooks_src="/usr/share/repogoon/server/hooks"
    else
        hooks_src="${SCRIPT_DIR}/server/hooks"
    fi
    
    if [ ! -d "$hooks_src" ]; then
        print_error "Hooks source directory not found: $hooks_src"
        exit 1
    fi
    
    if [ ! -d "$repos_path" ]; then
        print_warning "Repository storage path not found: $repos_path"
        print_info "No repositories to process."
        return 0
    fi
    
    print_info "Repository storage path: $repos_path"
    print_info "Hooks source: $hooks_src"
    echo
    
    local count=0
    local errors=0
    
    # Iterate through all namespace directories
    for ns_dir in "$repos_path"/*/; do
        [ -d "$ns_dir" ] || continue
        
        local namespace=$(basename "$ns_dir")
        
        # Iterate through all repository directories
        for repo_dir in "$ns_dir"*.git/; do
            [ -d "$repo_dir" ] || continue
            
            local repo_name=$(basename "$repo_dir" .git)
            local hooks_dest="${repo_dir}hooks"
            
            echo -n "Installing hooks for ${namespace}/${repo_name}... "
            
            # Ensure hooks directory exists
            mkdir -p "$hooks_dest"
            
            # Install pre-receive hook
            if [ -f "${hooks_src}/pre-receive" ]; then
                if cp "${hooks_src}/pre-receive" "${hooks_dest}/pre-receive" 2>/dev/null; then
                    chmod 755 "${hooks_dest}/pre-receive"
                    print_success "done"
                    ((count++)) || true
                else
                    print_error "failed"
                    ((errors++)) || true
                fi
            else
                print_warning "pre-receive hook not found"
            fi
        done
    done
    
    echo
    if [ $count -gt 0 ]; then
        print_success "Installed hooks on $count repository(ies)"
    fi
    if [ $errors -gt 0 ]; then
        print_error "Failed to install hooks on $errors repository(ies)"
    fi
    if [ $count -eq 0 ] && [ $errors -eq 0 ]; then
        print_info "No repositories found"
    fi
}

cmd_reconcile_releases() {
    local apply=0
    local namespace=""
    local repo_name=""
    local recreate_from_head=0

    while [ $# -gt 0 ]; do
        case "$1" in
            --apply)
                apply=1
                ;;
            --namespace)
                shift
                namespace="$1"
                if [ -z "$namespace" ]; then
                    print_error "--namespace requires a value"
                    exit 1
                fi
                ;;
            --repo)
                shift
                repo_name="$1"
                if [ -z "$repo_name" ]; then
                    print_error "--repo requires a value"
                    exit 1
                fi
                ;;
            --recreate-from-head)
                recreate_from_head=1
                ;;
            --help|-h)
                echo "Usage: rgoon-ctl reconcile-releases [--apply] [--namespace <ns>] [--repo <name>] [--recreate-from-head]"
                return 0
                ;;
            *)
                print_error "Unknown reconcile-releases option: $1"
                echo "Usage: rgoon-ctl reconcile-releases [--apply] [--namespace <ns>] [--repo <name>] [--recreate-from-head]"
                exit 1
                ;;
        esac
        shift
    done

    print_header "Reconcile Releases"

    cd "$SCRIPT_DIR"
    local args=()
    if [ $apply -eq 1 ]; then
        args+=("--apply")
    fi
    if [ -n "$namespace" ]; then
        args+=("--namespace" "$namespace")
    fi
    if [ -n "$repo_name" ]; then
        args+=("--repo" "$repo_name")
    fi
    if [ $recreate_from_head -eq 1 ]; then
        args+=("--recreate-from-head")
    fi

    node --import tsx server/scripts/reconcile-releases.ts "${args[@]}"
}

cmd_ssh_apply() {
    if [ "$(id -u)" -ne 0 ]; then
        print_error "ssh-apply must be run as root"
        exit 1
    fi

    print_header "Apply SSH Host Policy"

    cd "$SCRIPT_DIR"
    REPOGOON_CTL_MODE="$([ "$SCRIPT_PATH" = "/usr/bin" ] || [ "$SCRIPT_PATH" = "/bin" ] && echo systemd || echo dev)" \
    REPOGOON_CTL_APP_DIR="$SCRIPT_DIR" \
    REPOGOON_CTL_PID_FILE="$PID_FILE" \
    REPOGOON_CTL_LOG_FILE="$LOG_FILE" \
    node --import tsx server/scripts/ssh-apply.ts "$@"
}


# ─── Main ───────────────────────────────────────────────────────────────────────

case "$1" in
    --setup)
        cmd_setup
        ;;
    --generate-internal-secret)
        cmd_generate_internal_secret
        ;;
    --help|-h)
        show_help
        ;;
    start)
        cmd_start
        ;;
    stop)
        cmd_stop
        ;;
    restart)
        cmd_restart
        ;;
    status)
        cmd_status
        ;;
    show)
        shift
        cmd_show "$@"
        ;;
    set)
        shift
        cmd_set "$@"
        ;;
    validate)
        cmd_validate
        ;;
    test-db)
        cmd_test_db
        ;;
    init-db)
        cmd_init_db
        ;;
    wipe-db)
        shift
        cmd_wipe_db "$@"
        ;;
    create-db-user)
        echo "Interactive database user creation - run --setup instead"
        ;;
    ratelimit)
        shift
        cmd_ratelimit "$@"
        ;;
    ssl-renew)
        cmd_ssl_renew
        ;;
    nginx-config)
        cmd_nginx_config
        ;;
    nginx-reload)
        cmd_nginx_reload
        ;;
    backup)
        cmd_backup "$2"
        ;;
    restore)
        cmd_restore "$2"
        ;;
    migrate-db)
        shift
        cmd_migrate_db "$@"
        ;;
    doctor)
        cmd_doctor
        ;;
    install-hooks)
        cmd_install_hooks
        ;;
    ssh-apply)
        shift
        cmd_ssh_apply "$@"
        ;;
    reconcile-releases)
        shift
        cmd_reconcile_releases "$@"
        ;;
    "")
        show_help
        ;;
    *)
        print_error "Unknown command: $1"
        echo
        show_help
        exit 1
        ;;
esac
