#!/bin/bash
# @version 0.25.2

# Repository information used for downloading files and images from GitHub
REPO_OWNER="aliasvault"
REPO_NAME="aliasvault"
GITHUB_RAW_URL_REPO="https://raw.githubusercontent.com/${REPO_OWNER}/${REPO_NAME}"
GITHUB_CONTAINER_REGISTRY="ghcr.io/$(echo "$REPO_OWNER" | tr '[:upper:]' '[:lower:]')"

# Required files and directories
REQUIRED_DIRS=(
    "certificates/ssl"
    "certificates/letsencrypt"
    "certificates/letsencrypt/www"
    "database"
    "database/postgres"
    "logs"
    "logs/msbuild"
    "secrets"
)

# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
MAGENTA='\033[0;35m'
ORANGE='\033[0;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
DIM='\033[2m'
NC='\033[0m'

# File paths
ENV_FILE=".env"
ENV_EXAMPLE_FILE=".env.example"
SECRETS_DIR="./secrets"

# Minimum required versions
MIN_DOCKER_VERSION="20.10.0"
MIN_COMPOSE_VERSION="2.0.0"
MIN_DISK_SPACE_GB=5

# Global cache for latest version to avoid rate limiting
CACHED_LATEST_VERSION=""

# Function to show usage
show_usage() {
    print_logo
    printf "Usage: $0 [COMMAND] [OPTIONS]\n"
    printf "\n"
    printf "Commands:\n"
    printf "  install                   Install AliasVault by pulling pre-built images from GitHub Container Registry (recommended)\n"
    printf "  build                     Build AliasVault containers locally from source (takes longer and requires sufficient specs)\n"
    printf "  start                     Start AliasVault containers\n"
    printf "  restart                   Restart AliasVault containers\n"
    printf "  stop                      Stop AliasVault containers\n"
    printf "\n"
    printf "  configure-hostname        Configure the hostname where AliasVault can be accessed from\n"
    printf "  configure-ssl             Configure SSL certificates (Let's Encrypt or self-signed)\n"
    printf "  configure-email           Configure email domains for receiving emails\n"
    printf "  configure-registration    Configure new account registration (enable or disable)\n"
    printf "  configure-ip-logging      Configure IP address logging (enable or disable)\n"
    printf "  reset-admin-password      Reset admin password\n"
    printf "  uninstall                 Uninstall AliasVault\n"
    printf "\n"
    printf "  update                    Update AliasVault including install.sh script to the latest version\n"
    printf "  update-installer          Update install.sh script if newer version is available\n"
    printf "\n"
    printf "  db-export                 Export database to file\n"
    printf "  db-import                 Import database from file\n"
    printf "\n"
    printf "  configure-dev-db          Enable/disable development database (for local development only)\n"
    printf "\n"
    printf "Options:\n"
    printf "  --verbose                Show detailed output\n"
    printf "  -y, --yes                Automatic yes to prompts\n"
    printf "  --dev                    Target development database for db import/export operations\n"
    printf "  --parallel=N             Use pigz with N threads for faster compression (db-export only, max: 32)\n"
    printf "\n"
}

# Function to print the logo
print_logo() {
    printf "${MAGENTA}" >&2
    printf "==================================================\n" >&2
    printf "    _    _ _           __      __         _ _   \n" >&2
    printf "   / \  | (_) __ _ ___ \ \    / /_ _ _   _| | |_\n" >&2
    printf "  / _ \ | | |/ _\` / __| \ \/\/ / _\` | | | | | __|\n" >&2
    printf " / ___ \| | | (_| \__ \  \  / / (_| | |_| | | |_ \n" >&2
    printf "/_/   \_\_|_|\__,_|___/   \/  \__,__|\__,_|_|\__|\n" >&2
    printf "\n" >&2
    printf "==================================================\n${NC}" >&2
}

# Function to parse command line arguments
parse_args() {
    COMMAND=""
    VERBOSE=false
    FORCE_YES=false
    COMMAND_ARG=""
    DEV_DB=false
    PARALLEL_JOBS=0         # 0 = use standard gzip, >0 = use pigz with N threads

    if [ $# -eq 0 ]; then
        show_usage
        exit 0
    fi

    # First argument is always the command
    case $1 in
        install|i)
            COMMAND="install"
            shift
            # Check for version argument
            if [ $# -gt 0 ] && [[ ! "$1" =~ ^- ]]; then
                COMMAND_ARG="$1"
                shift
            fi
            ;;
        build|b)
            COMMAND="build"
            shift
            # Check for additional operation argument
            if [ $# -gt 0 ] && [[ ! "$1" =~ ^- ]]; then
                case $1 in
                    start|stop|restart)
                        COMMAND_ARG="$1"
                        shift
                        ;;
                    *)
                        echo "Invalid build operation: $1"
                        echo "Valid operations are: start, stop, restart"
                        exit 1
                        ;;
                esac
            fi
            ;;
        uninstall|u)
            COMMAND="uninstall"
            shift
            ;;
        reset-password|reset-admin-password|rp)
            COMMAND="reset-admin-password"
            shift
            ;;
        configure-hostname|hostname)
            COMMAND="configure-hostname"
            shift
            ;;
        configure-ssl|ssl)
            COMMAND="configure-ssl"
            shift
            ;;
        configure-email|email)
            COMMAND="configure-email"
            shift
            ;;
        configure-registration|registration)
            COMMAND="configure-registration"
            shift
            ;;
        configure-ip-logging|ip-logging)
            COMMAND="configure-ip-logging"
            shift
            ;;
        start|s)
            COMMAND="start"
            shift
            ;;
        stop|st)
            COMMAND="stop"
            shift
            ;;
        restart|r)
            COMMAND="restart"
            shift
            ;;
        update|up)
            COMMAND="update"
            shift
            ;;
        update-installer|cs)
            COMMAND="update-installer"
            shift
            ;;
        configure-dev-db|dev-db)
            COMMAND="configure-dev-db"
            shift
            # Check for direct option argument
            if [ $# -gt 0 ] && [[ ! "$1" =~ ^- ]]; then
                COMMAND_ARG="$1"
                shift
            fi
            ;;
        migrate-db|migrate)
            COMMAND="migrate-db"
            shift
            ;;
        db-export)
            COMMAND="db-export"
            shift
            ;;
        db-import)
            COMMAND="db-import"
            shift
            ;;
        --help)
            show_usage
            exit 0
            ;;
        *)
            echo "Unknown option: $1"
            show_usage
            exit 1
            ;;
    esac

    # Parse remaining flags
    while [[ $# -gt 0 ]]; do
        case $1 in
            --verbose)
                VERBOSE=true
                shift
                ;;
            -y|--yes)
                FORCE_YES=true
                shift
                ;;
            --dev)
                DEV_DB=true
                shift
                ;;
            --parallel=*)
                PARALLEL_JOBS="${1#*=}"
                if ! [[ "$PARALLEL_JOBS" =~ ^[0-9]+$ ]] || [ "$PARALLEL_JOBS" -lt 1 ] || [ "$PARALLEL_JOBS" -gt 32 ]; then
                    echo "Error: Invalid --parallel value '$PARALLEL_JOBS'. Must be a number between 1 and 32"
                    exit 1
                fi
                shift
                ;;
            *)
                echo "Unknown option: $1"
                show_usage
                exit 1
                ;;
        esac
    done
}

# Progress and animation functions
show_spinner() {
    local pid=$1
    local message="$2"
    local delay=0.1
    local spinstr='|/-\\'
    local i=0

    printf "${CYAN}ℹ %s${NC} " "$message"

    while kill -0 "$pid" 2>/dev/null; do
        printf "\b%c" "${spinstr:$i:1}"
        sleep $delay
        ((i = (i + 1) % 4))
    done

    printf "\b ${GREEN}✓${NC}\n"
}

log_info() {
    printf "${CYAN}ℹ ${NC}%s\n" "$1"
}

log_warning() {
    printf "${YELLOW}⚠ ${NC}%s\n" "$1"
}

log_error() {
    printf "${RED}✗ ${NC}%s\n" "$1" >&2
}

# Version comparison function
version_ge() {
    test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"
}

# Network connectivity check
check_connectivity() {
    printf "${CYAN}ℹ Checking network connectivity...${NC} "

    local test_urls=(
        "https://api.github.com"
        "https://raw.githubusercontent.com"
        "https://ghcr.io"
    )

    local spinstr='|/-\\'
    local i=0

    for url in "${test_urls[@]}"; do
        printf "\b%c" "${spinstr:$i:1}"
        ((i = (i + 1) % 4))

        if ! curl -Ls --connect-timeout 10 --max-time 30 "$url" > /dev/null 2>&1; then
            printf "\b ${RED}✗${NC}\n"
            log_error "Cannot reach $url. Please check your internet connection."
            return 1
        fi
    done

    printf "\b ${GREEN}✓${NC}\n"
    return 0
}

# Disk space check
check_disk_space() {
    printf "${CYAN}ℹ Checking disk space...${NC} "

    local available_gb=""
    local spinstr='|/-\\'
    local i=0

    if command -v df >/dev/null 2>&1; then
        printf "\b%c" "${spinstr:$i:1}"
        ((i = (i + 1) % 4))

        # Use portable df command and parse available size in KB
        local available_kb
        available_kb=$(df -k . 2>/dev/null | awk 'NR==2 {print $4}')

        if [ -n "$available_kb" ] && [ "$available_kb" -gt 0 ] 2>/dev/null; then
            available_gb=$((available_kb / 1024 / 1024))
        fi

        if [ -n "$available_gb" ] && [ "$available_gb" -gt 0 ] 2>/dev/null; then
            if [ "$available_gb" -lt "$MIN_DISK_SPACE_GB" ]; then
                printf "\b ${RED}✗${NC}\n"
                log_error "Insufficient disk space. Required: ${MIN_DISK_SPACE_GB}GB, Available: ${available_gb}GB"
                return 1
            fi
            printf "\b ${GREEN}✓${NC}\n"
            printf "  ${GREEN}✓ Disk space verified (${available_gb}GB available)${NC}\n"
        else
            printf "\b ${YELLOW}⚠${NC}\n"
            log_warning "Cannot determine available disk space, skipping check"
        fi
    else
        printf "\b ${YELLOW}⚠${NC}\n"
        log_warning "Cannot check disk space (df command not available)"
    fi

    return 0
}

# Read port configuration from .env file with fallbacks
get_port_config() {
    local http_port=80
    local https_port=443
    local smtp_port=25

    if [ -f "$ENV_FILE" ]; then
        # Read ports from .env file if it exists
        local env_http_port
        local env_https_port
        local env_smtp_port

        env_http_port=$(grep -E "^HTTP_PORT=" "$ENV_FILE" 2>/dev/null | cut -d'=' -f2 | tr -d ' ')
        env_https_port=$(grep -E "^HTTPS_PORT=" "$ENV_FILE" 2>/dev/null | cut -d'=' -f2 | tr -d ' ')
        env_smtp_port=$(grep -E "^SMTP_PORT=" "$ENV_FILE" 2>/dev/null | cut -d'=' -f2 | tr -d ' ')

        # Use .env values if they're valid numbers
        if [[ "$env_http_port" =~ ^[0-9]+$ ]] && [ "$env_http_port" -gt 0 ] && [ "$env_http_port" -le 65535 ]; then
            http_port="$env_http_port"
        fi
        if [[ "$env_https_port" =~ ^[0-9]+$ ]] && [ "$env_https_port" -gt 0 ] && [ "$env_https_port" -le 65535 ]; then
            https_port="$env_https_port"
        fi
        if [[ "$env_smtp_port" =~ ^[0-9]+$ ]] && [ "$env_smtp_port" -gt 0 ] && [ "$env_smtp_port" -le 65535 ]; then
            smtp_port="$env_smtp_port"
        fi
    fi

    # Return the ports as space-separated values
    echo "$http_port $https_port $smtp_port"
}

# Check if ports are available (not in use by non-AliasVault processes)
check_port_availability() {
    create_env_file || { printf "${RED}> Failed to create .env file${NC}\n"; exit 1; }

    printf "${CYAN}ℹ Checking port availability...${NC} "

    local ports_config
    ports_config=$(get_port_config)
    read -r http_port https_port smtp_port <<< "$ports_config"

    local ports_to_check=("$http_port" "$https_port" "$smtp_port")
    local port_issues=()
    local has_issues=false
    local spinstr='|/-\\'
    local i=0

    # Get current directory name as potential project name
    local current_project_name
    current_project_name=$(basename "$(pwd)" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]//g')

    # Get list of running AliasVault containers to exclude from checks
    local aliasvault_containers=()
    local aliasvault_project_containers=()

    if command -v docker > /dev/null 2>&1 && docker info > /dev/null 2>&1; then
        printf "\b%c" "${spinstr:$i:1}"
        ((i = (i + 1) % 4))

        # Get all running containers
        while IFS= read -r container_info; do
            if [ -n "$container_info" ]; then
                # Parse container name and project
                local container_name
                local project_name
                container_name=$(echo "$container_info" | cut -d'|' -f1)
                project_name=$(echo "$container_info" | cut -d'|' -f2)

                # Check if it's an AliasVault container by name or project
                if [[ "$container_name" =~ aliasvault ]] || [[ "$project_name" =~ aliasvault ]] || [[ "$project_name" == "$current_project_name" ]]; then
                    aliasvault_containers+=("$container_name")
                    aliasvault_project_containers+=("$project_name")
                fi
            fi
        done < <(docker ps --format "{{.Names}}|{{.Label \"com.docker.compose.project\"}}" 2>/dev/null | grep -v "^$" || true)
    fi

    for port in "${ports_to_check[@]}"; do
        printf "\b%c" "${spinstr:$i:1}"
        ((i = (i + 1) % 4))

        local port_in_use=false
        local blocking_process=""
        local is_aliasvault_port=false

        # Check if port is in use using netstat/ss
        if command -v ss > /dev/null 2>&1; then
            # Use ss (more modern)
            local ss_output
            ss_output=$(ss -tuln 2>/dev/null | grep ":${port} " || true)
            if [ -n "$ss_output" ]; then
                port_in_use=true
                # Try to get process info
                local process_info
                process_info=$(ss -tulpn 2>/dev/null | grep ":${port} " | head -n1 || true)
                if [ -n "$process_info" ]; then
                    blocking_process=$(echo "$process_info" | sed -n 's/.*users:((\"\([^\"]*\)\".*/\1/p' || true)
                fi
            fi
        elif command -v netstat > /dev/null 2>&1; then
            # Fallback to netstat
            local netstat_output
            netstat_output=$(netstat -tuln 2>/dev/null | grep ":${port} " || true)
            if [ -n "$netstat_output" ]; then
                port_in_use=true
                # Try to get process info with netstat -tulpn if available
                local process_info
                process_info=$(netstat -tulpn 2>/dev/null | grep ":${port} " | head -n1 || true)
                if [ -n "$process_info" ]; then
                    blocking_process=$(echo "$process_info" | awk '{print $7}' | cut -d'/' -f2 || true)
                fi
            fi
        else
            # Last resort: try to bind to the port temporarily
            if command -v nc > /dev/null 2>&1; then
                if ! nc -z localhost "$port" 2>/dev/null; then
                    port_in_use=false
                else
                    port_in_use=true
                fi
            else
                log_warning "Cannot check port $port availability (no netstat, ss, or nc available)"
                continue
            fi
        fi

        # If port is in use, check if it's used by AliasVault containers
        if [ "$port_in_use" = true ]; then
            # First, check if any AliasVault containers are using this port directly
            for container in "${aliasvault_containers[@]}"; do
                if command -v docker > /dev/null 2>&1; then
                    local container_ports
                    container_ports=$(docker port "$container" 2>/dev/null || true)
                    if echo "$container_ports" | grep -q ":${port}$" || echo "$container_ports" | grep -q ":${port}->" ; then
                        is_aliasvault_port=true
                        break
                    fi
                fi
            done

            # If not found in direct container ports, check if it's docker-proxy for AliasVault
            if [ "$is_aliasvault_port" = false ] && [ "$blocking_process" = "docker-proxy" ]; then
                # Check if there are any AliasVault containers running
                if [ ${#aliasvault_containers[@]} -gt 0 ]; then
                    # If we have AliasVault containers and docker-proxy is using the port,
                    # it's likely for AliasVault (especially during installation/updates)
                    is_aliasvault_port=true
                fi
            fi

            # Additional check: if we're in an AliasVault directory and docker-proxy is using the port,
            # it's very likely for AliasVault
            if [ "$is_aliasvault_port" = false ] && [ "$blocking_process" = "docker-proxy" ]; then
                # Check if we're in an AliasVault project directory
                if [ -f "docker-compose.yml" ] || [ -f ".env" ]; then
                    # If we have docker-compose.yml or .env file, this is likely an AliasVault project
                    is_aliasvault_port=true
                fi
            fi

            # Only report as an issue if it's not used by AliasVault
            if [ "$is_aliasvault_port" = false ]; then
                has_issues=true
                local port_name=""
                case "$port" in
                    "$http_port") port_name="HTTP" ;;
                    "$https_port") port_name="HTTPS" ;;
                    "$smtp_port") port_name="SMTP" ;;
                esac

                if [ -n "$blocking_process" ]; then
                    port_issues+=("Port $port ($port_name) is in use by: $blocking_process")
                else
                    port_issues+=("Port $port ($port_name) is in use by an unknown process")
                fi
            fi
        fi
    done

    if [ "$has_issues" = true ]; then
        printf "\b ${RED}✗${NC}\n"
        log_error "Port availability issues detected:"
        for issue in "${port_issues[@]}"; do
            printf "  ${RED}•${NC} %s\n" "$issue"
        done

        printf "\n${YELLOW}Common solutions:${NC}\n"

        # Show specific help based on which ports are in use
        local smtp_in_use=false
        local http_https_in_use=false

        for issue in "${port_issues[@]}"; do
            if [[ "$issue" == *"SMTP"* ]]; then
                smtp_in_use=true
            fi
            if [[ "$issue" == *"HTTP"* ]] || [[ "$issue" == *"HTTPS"* ]]; then
                http_https_in_use=true
            fi
        done

        if [ "$smtp_in_use" = true ]; then
            printf "  ${YELLOW}•${NC} Try disabling the postfix service with 'sudo systemctl stop postfix && sudo systemctl disable postfix'\n"
        fi

        if [ "$http_https_in_use" = true ]; then
            printf "  ${YELLOW}•${NC} Try stopping the existing local webserver (e.g. nginx, apache, httpd etc.)\n"
            printf "  ${YELLOW}•${NC} Change the default AliasVault ports (80, 443) by editing the .env file\n"
        fi

        printf "\nIf this still doesn't work, try finding out which services are running on the specified ports and read documentation for your distribution on how to disable them.\n"
        printf "\n"

        return 1
    fi

    printf "\b ${GREEN}✓${NC}\n"
    printf "  ${GREEN}✓ Port availability verified (HTTP:$http_port, HTTPS:$https_port, SMTP:$smtp_port)${NC}\n"
    return 0
}

# Comprehensive dependency checks
check_dependencies() {
    printf "${CYAN}ℹ Checking dependencies...${NC} "

    local missing_deps=()
    local version_issues=()
    local has_issues=false
    local spinstr='|/-\\'
    local i=0

    # Check if OS is 64-bit
    printf "\b%c" "${spinstr:$i:1}"
    ((i = (i + 1) % 4))
    local arch=$(uname -m)
    case "$arch" in
        x86_64|amd64|arm64|aarch64)
            # 64-bit architecture - continue
            ;;
        *)
            printf "\b ${RED}✗${NC}\n"
            log_error "AliasVault requires a 64-bit operating system."
            printf "\n"
            printf "${RED}${BOLD}Unsupported architecture detected: $arch${NC}\n"
            printf "\n"
            printf "AliasVault only supports 64-bit operating systems. Your current architecture ($arch) is not supported.\n"
            printf "\n"
            printf "${CYAN}Supported architectures:${NC}\n"
            printf "  ${GREEN}✓${NC} x86_64 (Intel/AMD 64-bit)\n"
            printf "  ${GREEN}✓${NC} arm64/aarch64 (ARM 64-bit)\n"
            printf "\n"
            printf "${YELLOW}Please upgrade your operating system to a 64-bit version.${NC}\n"
            printf "\n"
            return 1
            ;;
    esac

    # Check Docker
    printf "\b%c" "${spinstr:$i:1}"
    ((i = (i + 1) % 4))
    if ! command -v docker > /dev/null 2>&1; then
        missing_deps+=("docker")
        has_issues=true
    else
        local docker_version=$(docker --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1)
        if [ -n "$docker_version" ]; then
            if ! version_ge "$docker_version" "$MIN_DOCKER_VERSION"; then
                version_issues+=("Docker version $docker_version is below minimum required $MIN_DOCKER_VERSION")
                has_issues=true
            fi
        fi

        # Check if Docker daemon is running
        printf "\b%c" "${spinstr:$i:1}"
        ((i = (i + 1) % 4))
        if ! docker info > /dev/null 2>&1; then
            printf "\b ${RED}✗${NC}\n"
            log_error "Docker daemon cannot be reached."
            printf "\n"
            printf "${CYAN}To resolve this issue:${NC}\n"
            printf "\n"
            printf "${YELLOW}Step 1:${NC} Manually test Docker daemon status:\n"
            printf "  ${DIM}docker info${NC}\n"
            printf "\n"
            printf "${YELLOW}Step 2:${NC} If Docker is not installed or not running, follow the official installation instructions:\n"
            printf "  ${CYAN}https://docs.docker.com/engine/install/${NC}\n"
            printf "\n"
            printf "Please install the latest version of Docker and ensure the Docker daemon is running.\n"
            printf "\n"
            return 1
        fi

        # Test if Docker can actually run containers (lightweight test)
        if [ "$has_issues" != true ]; then
            printf "\b%c" "${spinstr:$i:1}"
            ((i = (i + 1) % 4))
            local docker_test_output
            docker_test_output=$(docker run --rm alpine:latest echo "test" 2>&1 >/dev/null)

            if [ $? -ne 0 ]; then
                printf "\b ${RED}✗${NC}\n"
                log_error "Docker cannot run containers properly. Error output:"
                printf "  ${RED}%s${NC}\\n\\n" "$docker_test_output"

                printf "  Possible causes:\\n"
                printf "  ${YELLOW}•${NC} Docker daemon configuration issues\\n"
                printf "  ${YELLOW}•${NC} Insufficient permissions\\n"
                printf "  ${YELLOW}•${NC} SELinux/AppArmor restrictions\\n"
                printf "  ${YELLOW}•${NC} Storage driver problems\\n"
                printf "  ${YELLOW}•${NC} Kernel compatibility issues\\n"
                printf "\\n"
                printf "  ${CYAN}To debug, try running:${NC}\\n"
                printf "  ${DIM}docker run --rm alpine:latest echo \"test\"${NC}\\n"
                printf "\\n"
                return 1
            fi
        fi
    fi

    # Check Docker Compose
    printf "\b%c" "${spinstr:$i:1}"
    ((i = (i + 1) % 4))
    if docker compose version > /dev/null 2>&1; then
        local compose_version=$(docker compose version --short 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1)
        if [ -n "$compose_version" ]; then
            if ! version_ge "$compose_version" "$MIN_COMPOSE_VERSION"; then
                version_issues+=("Docker Compose version $compose_version is below minimum required $MIN_COMPOSE_VERSION")
                has_issues=true
            fi
        fi
    elif command -v docker-compose > /dev/null 2>&1; then
        printf "\b ${RED}✗${NC}\n"
        local compose_version=$(docker-compose --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1)
        log_error "Docker Compose v1 detected ($compose_version). AliasVault requires Docker Compose v2."
        printf "\n"
        printf "${RED}${BOLD}Docker Compose v1 is not supported.${NC}\n"
        printf "\n"
        printf "${CYAN}To upgrade to Docker Compose v2:${NC}\n"
        printf "  ${YELLOW}•${NC} Uninstall docker-compose v1: sudo apt remove docker-compose (Ubuntu/Debian)\n"
        printf "  ${YELLOW}•${NC} Install Docker Compose v2 plugin: https://docs.docker.com/compose/install/linux/#install-using-the-repository\n"
        printf "\n"
        printf "${CYAN}After installation, verify with:${NC}\n"
        printf "  docker compose version\n"
        printf "\n"
        return 1
    else
        missing_deps+=("docker-compose")
        has_issues=true
    fi

    # Check essential tools
    printf "\b%c" "${spinstr:$i:1}"
    ((i = (i + 1) % 4))
    for tool in curl openssl grep sed; do
        if ! command -v "$tool" > /dev/null 2>&1; then
            missing_deps+=("$tool")
            has_issues=true
        fi
    done

    # Show final result
    if [ "$has_issues" = true ]; then
        printf "\b ${RED}✗${NC}\n"
        printf "\n"

        if [ ${#missing_deps[@]} -gt 0 ]; then
            printf "${RED}${BOLD}Missing required dependencies:${NC}\n"
            for dep in "${missing_deps[@]}"; do
                case $dep in
                    "docker")
                        printf "  ${RED}✗${NC} %s (install manual: https://docs.docker.com/engine/install/)\n" "$dep"
                        ;;
                    "docker-compose")
                        printf "  ${RED}✗${NC} %s (install manual: https://docs.docker.com/compose/install/linux/#install-using-the-repository)\n" "$dep"
                        ;;
                    *)
                        printf "  ${RED}✗${NC} %s\n" "$dep"
                        ;;
                esac
            done
            printf "\n"
        fi

        if [ ${#version_issues[@]} -gt 0 ]; then
            printf "${YELLOW}${BOLD}Version compatibility warnings:${NC}\n"
            for issue in "${version_issues[@]}"; do
                printf "  ${YELLOW}⚠${NC} %s\n" "$issue"
            done
            printf "\n"
            printf "${YELLOW}AliasVault may still work, but upgrading is recommended.${NC}\n\n"
        fi

        if [ ${#missing_deps[@]} -gt 0 ]; then
            return 1
        fi
    else
        printf "\b ${GREEN}✓${NC}\n"
    fi

    return 0
}

# Enhanced error handling with retries
retry_command() {
    local max_attempts=$1
    local delay=$2
    shift 2
    local command=("$@")
    local attempt=1

    while [ $attempt -le $max_attempts ]; do
        if "${command[@]}"; then
            return 0
        fi

        if [ $attempt -lt $max_attempts ]; then
            log_warning "Attempt $attempt failed, retrying in ${delay}s..."
            sleep $delay
        fi

        ((attempt++))
    done

    log_error "Command failed after $max_attempts attempts: ${command[*]}"
    return 1
}

# Enhanced docker pull with progress
enhanced_docker_pull() {
    local image="$1"
    local image_name=$(basename "$image")

    if [ "$VERBOSE" = true ]; then
        docker pull "$image"
    else
        (
            docker pull "$image" > /tmp/docker_pull_${image_name//[:\/]/_}.log 2>&1 &
            local pull_pid=$!
            show_spinner $pull_pid "Pulling $image_name "
            wait $pull_pid
            local exit_code=$?

            if [ $exit_code -ne 0 ]; then
                cat /tmp/docker_pull_${image_name//[:\/]/_}.log >&2
            fi

            rm -f /tmp/docker_pull_${image_name//[:\/]/_}.log
            return $exit_code
        )
    fi
}

# Function to validate semver format
validate_semver() {
    local version="$1"

    # Check if version is "latest" (special case)
    if [ "$version" = "latest" ]; then
        return 0
    fi

    # Validate semver format: x.y.z where x, y, z are non-negative integers
    if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
        return 0
    else
        return 1
    fi
}

# Main function
main() {
    parse_args "$@"

    # Check if command is empty (should not happen with updated parse_args)
    if [ -z "$COMMAND" ]; then
        show_usage
        exit 1
    fi

    print_logo

    # Skip dependency checks for certain commands that don't require Docker
    case $COMMAND in
        "update-installer")
            # Only check basic network connectivity for installer updates
            if ! check_connectivity; then
                exit 1
            fi
            ;;
        "install"|"build"|"update"|"configure-dev-db")
            # Full dependency check for operations that require Docker
            if ! check_dependencies; then
                exit 1
            fi

            # Run migrations
            migrate_secrets_to_files # 0.22.0+
            migrate_docker_image_urls # 0.23.0+

            # Additional checks for installation/build operations
            if [[ "$COMMAND" == "install" || "$COMMAND" == "build" || "$COMMAND" == "update" ]]; then
                if ! check_connectivity; then
                    exit 1
                fi
                if ! check_disk_space; then
                    exit 1
                fi
                if ! check_port_availability; then
                    exit 1
                fi
            fi
            ;;
    esac

    case $COMMAND in
        "build")
            handle_build
            ;;
        "install")
            handle_install "$COMMAND_ARG"
            ;;
        "uninstall")
            handle_uninstall
            ;;
        "reset-admin-password")
            generate_admin_password
            if [ $? -eq 0 ]; then
                printf "${CYAN}> Restarting admin container...${NC}\n"
                if [ "$VERBOSE" = true ]; then
                    eval "$(get_docker_compose_command) up -d --force-recreate admin"
                else
                    eval "$(get_docker_compose_command) up -d --force-recreate admin" > /dev/null 2>&1
                fi
                print_password_reset_message
            fi
            ;;
        "configure-ssl")
            handle_ssl_configuration
            ;;
        "configure-email")
            handle_email_configuration
            ;;
        "configure-registration")
            handle_registration_configuration
            ;;
        "configure-hostname")
            handle_hostname_configuration
            ;;
        "configure-ip-logging")
            handle_ip_logging_configuration
            ;;
        "start")
            handle_start
            ;;
        "stop")
            handle_stop
            ;;
        "restart")
            handle_restart
            ;;
        "update")
            handle_update
            ;;
        "update-installer")
            check_install_script_update
            exit $?
            ;;
        "configure-dev-db")
            configure_dev_database
            ;;
        "db-export")
            handle_db_export
            ;;
        "db-import")
            handle_db_import
            ;;
    esac
}

# Function to get the latest release version from GitHub (with session caching)
get_latest_version() {
    # Check if we have a cached version for this session
    if [ -n "${CACHED_LATEST_VERSION:-}" ]; then
        echo "$CACHED_LATEST_VERSION"
        return 0
    fi

    local attempt=1
    local max_attempts=5
    local wait_time=1

    while [ $attempt -le $max_attempts ]; do
        local latest_version=$(curl -Ls "https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest" | grep -o '"tag_name": *"[^"]*"' | cut -d'"' -f4)

        if [ -n "$latest_version" ]; then
            # Cache the version for this session
            CACHED_LATEST_VERSION="$latest_version"
            echo "$latest_version"
            return 0
        fi

        printf "${YELLOW}> Attempt ${attempt}/${max_attempts}: Failed to get latest version from GitHub. Retrying in ${wait_time}s...${NC}\n" >&2
        sleep $wait_time

        # Exponential backoff - double the wait time for next attempt
        wait_time=$((wait_time * 2))
        attempt=$((attempt + 1))
    done

    printf "${RED}> Failed to get latest version from GitHub after ${max_attempts} attempts.${NC}\n" >&2
    return 1
}

# Function to initialize workspace and create required directories
initialize_workspace() {
    printf "${CYAN}ℹ Checking workspace...${NC} ${GREEN}✓${NC}\n"

    local dirs_needed=false
    for dir in "${REQUIRED_DIRS[@]}"; do
        if [ ! -d "$dir" ]; then
            if [ "$dirs_needed" = false ]; then
                printf "  ${GREEN}> Creating required directories...${NC}\n"
                dirs_needed=true
            fi
            mkdir -p "$dir"
            chmod -R 755 "$dir"
            if [ $? -ne 0 ]; then
                printf "  ${RED}> Failed to create directory: $dir${NC}\n"
                exit 1
            fi
        fi
    done
}

# Function to handle docker-compose.yml
handle_docker_compose() {
    local version_tag="$1"
    printf "${CYAN}ℹ Downloading docker-compose files for version ${version_tag}...${NC} "

    local files_to_download=(
        "docker-compose.yml"
        "docker-compose.letsencrypt.yml"
    )

    local spinstr='|/-\\'
    local i=0

    for file in "${files_to_download[@]}"; do
        printf "\b%c" "${spinstr:$i:1}"
        ((i = (i + 1) % 4))

        local temp_file="${file}.tmp"
        local download_url="${GITHUB_RAW_URL_REPO}/${version_tag}/${file}"

        # First, check if the file exists by making a HEAD request
        local http_status
        http_status=$(curl -Ls -o /dev/null -w "%{http_code}" "$download_url" 2>/dev/null)

        if [ "$http_status" = "404" ]; then
            printf "\b ${RED}✗${NC}\n"
            log_error "Version '${version_tag}' does not exist or is not available."
            log_error "The requested version may not have been released yet or may be invalid."
            printf "\n"
            printf "${CYAN}Available options:${NC}\n"
            printf "  ${YELLOW}•${NC} Check available versions at: https://github.com/${REPO_OWNER}/${REPO_NAME}/releases\n"
            printf "  ${YELLOW}•${NC} Use a different version: ./install.sh install <version>\n"
            printf "\n"
            rm -f "$temp_file"
            exit 1
        fi

        if ! retry_command 3 2 curl -LsSf "$download_url" -o "$temp_file"; then
            printf "\b ${RED}✗${NC}\n"
            log_error "Failed to download $file from $download_url"
            log_error "Please check your internet connection and try again."
            log_info "Alternatively, download manually and place in the current directory."
            rm -f "$temp_file"
            exit 1
        fi

        # Special handling for docker-compose.yml version replacement
        if [ "$file" = "docker-compose.yml" ]; then
            if [ -n "$version_tag" ] && [ "$version_tag" != "latest" ]; then
                sed "s/:latest/:$version_tag/g" "$temp_file" > "$file"
                rm "$temp_file"
            else
                mv "$temp_file" "$file"
            fi
        else
            mv "$temp_file" "$file"
        fi
    done

    printf "\b ${GREEN}✓${NC}\n"
    return 0
}

# Function to check and update install.sh for specific version
check_install_script_version() {
    local target_version="$1"
    printf "${CYAN}ℹ Checking install script version for ${target_version}...${NC} ${GREEN}✓${NC}\n"

    # First, check if the install.sh file exists for this version
    local install_url="${GITHUB_RAW_URL_REPO}/${target_version}/install.sh"
    local http_status
    http_status=$(curl -Ls -o /dev/null -w "%{http_code}" "$install_url" 2>/dev/null)

    if [ "$http_status" = "404" ]; then
        printf "   > Install script not found for version ${target_version}. Continuing with current version.\n"
        return 1
    fi

    # Get remote install.sh for target version
    if ! curl -LsSf "$install_url" -o "install.sh.tmp"; then
        printf "${RED}> Failed to check install script version. Continuing with current version.${NC}\n"
        rm -f install.sh.tmp
        return 1
    fi

    # Get versions
    local current_version=$(extract_version "install.sh")
    local target_script_version=$(extract_version "install.sh.tmp")

    # Check if versions could be extracted
    if [ -z "$current_version" ] || [ -z "$target_script_version" ]; then
        printf "\n${YELLOW}> Could not determine script versions. Falling back to file comparison...${NC}\n"
        if ! cmp -s "install.sh" "install.sh.tmp"; then
            printf "${YELLOW}> Install script needs updating to match version ${target_version}${NC}\n"
            printf " ${GREEN}✓${NC}\n"
            return 2
        fi
    else
        if [ "$current_version" != "$target_script_version" ]; then
            printf "${YELLOW}> Install script needs updating to match version ${target_version}${NC}\n"
            printf " ${GREEN}✓${NC}\n"
            return 2
        fi
    fi

    rm -f install.sh.tmp
    return 0
}

# Function to create .env file
create_env_file() {
    printf "${CYAN}ℹ Checking .env file...${NC} ${GREEN}✓${NC}\n"
    if [ ! -f "$ENV_FILE" ]; then
        if [ ! -f "$ENV_EXAMPLE_FILE" ]; then
            # Get latest release version
            local latest_version=$(get_latest_version) || {
                printf "\n  ${YELLOW}> Failed to check latest version. Creating blank .env file.${NC}\n"
                touch "$ENV_FILE"
                return 0
            }

            printf "  ${CYAN}> Downloading .env.example...${NC}"

            # Check if .env.example exists for this version
            local env_example_url="${GITHUB_RAW_URL_REPO}/${latest_version}/.env.example"
            local http_status
            http_status=$(curl -Ls -o /dev/null -w "%{http_code}" "$env_example_url" 2>/dev/null)

            if [ "$http_status" = "404" ]; then
                printf "\n  ${YELLOW}> .env.example not found for version ${latest_version}. Creating blank .env file.${NC}\n"
                touch "$ENV_FILE"
                return 0
            fi

            if curl -LsSf "$env_example_url" -o "$ENV_EXAMPLE_FILE" > /dev/null 2>&1; then
                printf "\n  ${GREEN}> .env.example downloaded successfully.${NC}\n"
            else
                printf "\n  ${YELLOW}> Failed to download .env.example. Creating blank .env file.${NC}\n"
                touch "$ENV_FILE"
                return 0
            fi
        fi

        cp "$ENV_EXAMPLE_FILE" "$ENV_FILE"
        printf "  ${GREEN}> New .env file created from .env.example.${NC}\n"
    else
        # Check if .env file contains POSTGRES_DB or POSTGRES_USER, remove if present and show confirmation
        local removed_vars=()
        if grep -q "^POSTGRES_DB=" "$ENV_FILE"; then
            sed -i.bak "/^POSTGRES_DB=/d" "$ENV_FILE" && rm -f "$ENV_FILE.bak"
            removed_vars+=("POSTGRES_DB")
        fi
        if grep -q "^POSTGRES_USER=" "$ENV_FILE"; then
            sed -i.bak "/^POSTGRES_USER=/d" "$ENV_FILE" && rm -f "$ENV_FILE.bak"
            removed_vars+=("POSTGRES_USER")
        fi
        if [ ${#removed_vars[@]} -gt 0 ]; then
            printf "  ${GREEN}> Removed obsolete variable(s) from .env: %s${NC}\n" "${removed_vars[*]}"
        fi

    fi
}

populate_hostname() {
    if ! grep -q "^HOSTNAME=" "$ENV_FILE" || [ -z "$(grep "^HOSTNAME=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
        while true; do
            read -p "Enter the hostname where this AliasVault server can be accessed (e.g. aliasvault.example.com): " USER_HOSTNAME
            if [ -n "$USER_HOSTNAME" ]; then
                HOSTNAME="$USER_HOSTNAME"
                break
            else
                printf "${YELLOW}> Hostname cannot be empty. Please enter a valid hostname.${NC}\n"
            fi
        done
        update_env_var "HOSTNAME" "$HOSTNAME"
    else
        HOSTNAME=$(grep "^HOSTNAME=" "$ENV_FILE" | cut -d '=' -f2)
    fi
}

# Function to generate admin password
generate_admin_password() {
    printf "${CYAN}ℹ Generating admin password...${NC} ${GREEN}✓${NC}\n"
    PASSWORD=$(openssl rand -base64 12)

    # Build locally if in build mode or if pre-built image is not available
    if grep -q "^DEPLOYMENT_MODE=build" "$ENV_FILE" 2>/dev/null || ! docker pull ${GITHUB_CONTAINER_REGISTRY}/installcli:latest > /dev/null 2>&1; then
        log_info "Building InstallCli locally..."
        if [ "$VERBOSE" = true ]; then
            if ! docker build -t installcli -f apps/server/Utilities/AliasVault.InstallCli/Dockerfile .; then
                log_error "Failed to build InstallCli Docker image"
                exit 1
            fi
        else
            (
                docker build -t installcli -f apps/server/Utilities/AliasVault.InstallCli/Dockerfile . > install_build_output.log 2>&1 &
                BUILD_PID=$!
                show_spinner $BUILD_PID "Building InstallCli image "
                wait $BUILD_PID
                BUILD_EXIT_CODE=$?

                if [ $BUILD_EXIT_CODE -ne 0 ]; then
                    log_error "Failed to build InstallCli Docker image. Build output:"
                    cat install_build_output.log >&2
                    exit $BUILD_EXIT_CODE
                fi

                rm -f install_build_output.log
            )
        fi
        HASH=$(docker run --rm installcli hash-password "$PASSWORD")
    else
        HASH=$(docker run --rm ${GITHUB_CONTAINER_REGISTRY}/installcli:latest hash-password "$PASSWORD")
    fi

    if [ -z "$HASH" ]; then
        printf "\n${RED}> Error: Failed to generate password hash${NC}\n"
        exit 1
    fi

    # Write admin password hash to secret file with timestamp
    TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
    write_secret_to_file "admin_password_hash" "${HASH}|${TIMESTAMP}"
}

# Helper function to update environment variables
update_env_var() {
    local key=$1
    local value=$2

    if [ -f "$ENV_FILE" ]; then
        # Check if key exists
        if grep -q "^${key}=" "$ENV_FILE"; then
            # Update existing key inline
            sed -i.bak "s|^${key}=.*|${key}=${value}|" "$ENV_FILE" && rm -f "$ENV_FILE.bak"
        else
            # Key doesn't exist, append it
            echo "$key=$value" >> "$ENV_FILE"
        fi
    else
        # File doesn't exist, create it with the key-value pair
        echo "$key=$value" > "$ENV_FILE"
    fi

    printf "  ${GREEN}> $key has been set in $ENV_FILE.${NC}\n"
}

# Helper function to write secrets to files instead of .env
write_secret_to_file() {
    local secret_name=$1
    local secret_value=$2
    local secret_file="${SECRETS_DIR}/${secret_name}"

    # Create secrets directory if it doesn't exist
    if [ ! -d "$SECRETS_DIR" ]; then
        mkdir -p "$SECRETS_DIR"
        chmod 700 "$SECRETS_DIR"
    fi

    # Write secret to file
    echo -n "$secret_value" > "$secret_file"
    chmod 600 "$secret_file"

    printf "  ${GREEN}> Secret $secret_name has been written to $secret_file${NC}\n"
}

# Helper function to read secret from file
read_secret_from_file() {
    local secret_name=$1
    local secret_file="${SECRETS_DIR}/${secret_name}"

    if [ -f "$secret_file" ]; then
        cat "$secret_file"
    else
        echo ""
    fi
}

# Function to migrate secrets from .env to files
# Used when migrating from 0.21.0 or lower to 0.22.0+
migrate_secrets_to_files() {
    # Check if migration is needed
    if [ ! -f "$ENV_FILE" ]; then
        return 0
    fi

    # Check if any secrets exist in .env file
    if ! grep -q "^JWT_KEY=\|^DATA_PROTECTION_CERT_PASS=\|^POSTGRES_PASSWORD=\|^ADMIN_PASSWORD_HASH=" "$ENV_FILE" 2>/dev/null; then
        return 0
    fi

    printf "${CYAN}ℹ Migrating secrets from .env to secret files...${NC}\n"

    local migrated=false
    local confirm_overwrite=false

    # Migrate PRIVATE_EMAIL_DOMAINS from DISABLED.TLD to empty string (v0.22.0+)
    if grep -q "^PRIVATE_EMAIL_DOMAINS=DISABLED.TLD" "$ENV_FILE"; then
        update_env_var "PRIVATE_EMAIL_DOMAINS" ""
        printf "  Migrated PRIVATE_EMAIL_DOMAINS (DISABLED.TLD → empty string, v0.22.0+)\n"
    fi

    # Check if any secret files already exist
    if [ -f "${SECRETS_DIR}/jwt_key" ] || [ -f "${SECRETS_DIR}/data_protection_cert_pass" ] ||
       [ -f "${SECRETS_DIR}/postgres_password" ] || [ -f "${SECRETS_DIR}/admin_password_hash" ]; then
        printf "${YELLOW}⚠ Some secret files already exist. Do you want to overwrite them with values from .env? (y/n):${NC} "
        read -r response
        if [ "$response" != "y" ] && [ "$response" != "Y" ]; then
            printf "${CYAN}ℹ Skipping secret migration (keeping existing secret files)${NC}\n"
            # Still remove secrets from .env if they exist there
            remove_secrets_from_env
            return 0
        fi
        confirm_overwrite=true
    fi

    # Create secrets directory if it doesn't exist
    if [ ! -d "$SECRETS_DIR" ]; then
        mkdir -p "$SECRETS_DIR"
        chmod 700 "$SECRETS_DIR"
    fi

    # Migrate JWT_KEY
    if grep -q "^JWT_KEY=" "$ENV_FILE" 2>/dev/null; then
        JWT_KEY=$(grep "^JWT_KEY=" "$ENV_FILE" | cut -d '=' -f2-)
        if [ -n "$JWT_KEY" ]; then
            write_secret_to_file "jwt_key" "$JWT_KEY"
            migrated=true
        fi
    fi

    # Migrate DATA_PROTECTION_CERT_PASS
    if grep -q "^DATA_PROTECTION_CERT_PASS=" "$ENV_FILE" 2>/dev/null; then
        CERT_PASS=$(grep "^DATA_PROTECTION_CERT_PASS=" "$ENV_FILE" | cut -d '=' -f2-)
        if [ -n "$CERT_PASS" ]; then
            write_secret_to_file "data_protection_cert_pass" "$CERT_PASS"
            migrated=true
        fi
    fi

    # Migrate POSTGRES_PASSWORD
    if grep -q "^POSTGRES_PASSWORD=" "$ENV_FILE" 2>/dev/null; then
        POSTGRES_PASS=$(grep "^POSTGRES_PASSWORD=" "$ENV_FILE" | cut -d '=' -f2-)
        if [ -n "$POSTGRES_PASS" ]; then
            write_secret_to_file "postgres_password" "$POSTGRES_PASS"
            migrated=true
        fi
    fi

    # Migrate ADMIN_PASSWORD_HASH (with timestamp)
    if grep -q "^ADMIN_PASSWORD_HASH=" "$ENV_FILE" 2>/dev/null; then
        ADMIN_HASH=$(grep "^ADMIN_PASSWORD_HASH=" "$ENV_FILE" | cut -d '=' -f2-)
        if [ -n "$ADMIN_HASH" ]; then
            # Get the timestamp if it exists
            TIMESTAMP=""
            if grep -q "^ADMIN_PASSWORD_GENERATED=" "$ENV_FILE" 2>/dev/null; then
                TIMESTAMP=$(grep "^ADMIN_PASSWORD_GENERATED=" "$ENV_FILE" | cut -d '=' -f2-)
            else
                TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
            fi
            # Write hash with timestamp appended
            write_secret_to_file "admin_password_hash" "${ADMIN_HASH}|${TIMESTAMP}"
            migrated=true
        fi
    fi

    if [ "$migrated" = true ]; then
        printf "  ${GREEN}✓ Secrets have been migrated from .env to secret files${NC}\n"
        # Remove secrets from .env after successful migration
        remove_secrets_from_env
    elif [ "$confirm_overwrite" = false ]; then
        printf "  ${CYAN}ℹ No secrets found in .env to migrate${NC}\n"
    fi
}

# Function to remove secrets from .env file. This is used to migrate pre-0.22.0 .env files to the new secret file format.
# Can be removed later when v1.0 is launched.
remove_secrets_from_env() {
    local removed=false

    # Only proceed if .env file exists
    if [ ! -f "$ENV_FILE" ]; then
        return 0
    fi

    # Verify that secret files exist before removing from .env
    local all_secrets_migrated=true

    if grep -q "^JWT_KEY=" "$ENV_FILE" 2>/dev/null && [ ! -f "${SECRETS_DIR}/jwt_key" ]; then
        all_secrets_migrated=false
    fi

    if grep -q "^DATA_PROTECTION_CERT_PASS=" "$ENV_FILE" 2>/dev/null && [ ! -f "${SECRETS_DIR}/data_protection_cert_pass" ]; then
        all_secrets_migrated=false
    fi

    if grep -q "^POSTGRES_PASSWORD=" "$ENV_FILE" 2>/dev/null && [ ! -f "${SECRETS_DIR}/postgres_password" ]; then
        all_secrets_migrated=false
    fi

    if grep -q "^ADMIN_PASSWORD_HASH=" "$ENV_FILE" 2>/dev/null && [ ! -f "${SECRETS_DIR}/admin_password_hash" ]; then
        all_secrets_migrated=false
    fi

    if [ "$all_secrets_migrated" = false ]; then
        printf "${YELLOW}⚠ Not all secrets have been migrated to files. Keeping secrets in .env${NC}\n"
        return 1
    fi

    # Remove secrets from .env
    if grep -q "^JWT_KEY=" "$ENV_FILE"; then
        sed -i.bak "/^JWT_KEY=/d" "$ENV_FILE" && rm -f "$ENV_FILE.bak"
        removed=true
    fi

    if grep -q "^DATA_PROTECTION_CERT_PASS=" "$ENV_FILE"; then
        sed -i.bak "/^DATA_PROTECTION_CERT_PASS=/d" "$ENV_FILE" && rm -f "$ENV_FILE.bak"
        removed=true
    fi

    if grep -q "^POSTGRES_PASSWORD=" "$ENV_FILE"; then
        sed -i.bak "/^POSTGRES_PASSWORD=/d" "$ENV_FILE" && rm -f "$ENV_FILE.bak"
        removed=true
    fi

    if grep -q "^ADMIN_PASSWORD_HASH=" "$ENV_FILE"; then
        sed -i.bak "/^ADMIN_PASSWORD_HASH=/d" "$ENV_FILE" && rm -f "$ENV_FILE.bak"
        removed=true
    fi

    if grep -q "^ADMIN_PASSWORD_GENERATED=" "$ENV_FILE"; then
        sed -i.bak "/^ADMIN_PASSWORD_GENERATED=/d" "$ENV_FILE" && rm -f "$ENV_FILE.bak"
        removed=true
    fi

    if [ "$removed" = true ]; then
        printf "  ${GREEN}✓ Secrets have been removed from .env file${NC}\n"
    fi
}


# Helper function to delete environment variables
delete_env_var() {
    local key=$1

    if [ -f "$ENV_FILE" ]; then
        sed -i.bak "/^${key}=/d" "$ENV_FILE" && rm -f "$ENV_FILE.bak"
        printf "  ${GREEN}> $key has been removed from $ENV_FILE.${NC}\n"
    fi
}

# Function to migrate Docker image URLs from old namespace to new namespace
# Used when migrating from 0.22.0 or lower to 0.23.0+
migrate_docker_image_urls() {
    if [ ! -f "docker-compose.yml" ] || ! grep -q "ghcr.io/lanedirt/aliasvault-" "docker-compose.yml" 2>/dev/null; then
        return 0
    fi

    printf "${CYAN}ℹ Migrating Docker image URLs to new official namespace aliasvault/*...${NC}\n"

    # Process docker-compose.yml
    if [ -f "docker-compose.yml" ] && grep -q "ghcr.io/lanedirt/aliasvault-" "docker-compose.yml"; then
        # Create backup before modifying
        cp docker-compose.yml "docker-compose.yml.backup.$(date +%Y%m%d_%H%M%S)"

        # Update all image URLs
        sed -i.tmp 's|ghcr.io/lanedirt/aliasvault-postgres:|ghcr.io/aliasvault/postgres:|g' docker-compose.yml
        sed -i.tmp 's|ghcr.io/lanedirt/aliasvault-client:|ghcr.io/aliasvault/client:|g' docker-compose.yml
        sed -i.tmp 's|ghcr.io/lanedirt/aliasvault-api:|ghcr.io/aliasvault/api:|g' docker-compose.yml
        sed -i.tmp 's|ghcr.io/lanedirt/aliasvault-admin:|ghcr.io/aliasvault/admin:|g' docker-compose.yml
        sed -i.tmp 's|ghcr.io/lanedirt/aliasvault-smtp:|ghcr.io/aliasvault/smtp:|g' docker-compose.yml
        sed -i.tmp 's|ghcr.io/lanedirt/aliasvault-task-runner:|ghcr.io/aliasvault/task-runner:|g' docker-compose.yml
        sed -i.tmp 's|ghcr.io/lanedirt/aliasvault-reverse-proxy:|ghcr.io/aliasvault/reverse-proxy:|g' docker-compose.yml

        # Clean up temp files
        rm -f docker-compose.yml.tmp docker-compose.yml.backup.*

        printf "  ${GREEN}✓ Updated docker-compose.yml${NC}\n"
    fi

    printf "  ${GREEN}✓ Docker image URL migration completed${NC}\n"
}

# Function to print a success box
print_success_box() {
    local message="$1"
    local width=70
    local header="╔══════════════════════════════════════════════════════════════════════╗"
    local footer="╚══════════════════════════════════════════════════════════════════════╝"
    local success="✓ SUCCESS!"
    local success_line
    local message_line
    local padding

    # Print header
    printf "${MAGENTA}%s${NC}\n" "$header"

    # Construct second line with centered success text
    local success_padding=$(( (width - ${#success}) / 2 ))
    printf "${MAGENTA}║${NC}%*s${GREEN}%s${NC}%*s${MAGENTA}║${NC}\n" \
        "$success_padding" "" "$success" "$((width - success_padding - ${#success}))" ""

    # Construct third line with centered message
    local msg_len=${#message}
    local msg_padding=$(( (width  - msg_len) / 2 ))
    printf "${MAGENTA}║${NC}%*s${BOLD}%s${NC}%*s${MAGENTA}║${NC}\n" \
        "$msg_padding" "" "$message" "$((width - msg_padding - msg_len))" ""

    # Print footer
    printf "${MAGENTA}%s${NC}\n" "$footer"
}

# Function to clean up old Docker images
cleanup_docker_images() {
    printf "\n${YELLOW}+++ Cleaning up old Docker images +++${NC}\n"

    # Only clean up AliasVault-related images and dangling images
    local current_version="$1"
    local removed_count=0
    local total_count=0

    if [ "$VERBOSE" = true ]; then
        printf "${CYAN}ℹ Removing old AliasVault images...${NC}\n"

        # Remove dangling images (untagged)
        docker image prune -f

        # Get all images containing "aliasvault" (case-insensitive) in the repository name
        docker images --format "{{.Repository}}:{{.Tag}}" | grep -i "aliasvault" | \
        while read -r image_full; do
            # Extract just the tag from the full image name
            local tag="${image_full##*:}"

            # Skip if image is currently being used by a container
            if ! docker ps -a --format "{{.Image}}" | grep -q "^${image_full}$"; then
                # Skip if it's the current version or latest
                if [ "$tag" != "$current_version" ] && [ "$tag" != "latest" ]; then
                    ((total_count++))
                    printf "  Removing old image: $image_full"
                    # Remove by full repository:tag name, not by ID
                    if docker rmi "$image_full" > /dev/null 2>&1; then
                        printf " ${GREEN}✓${NC}\n"
                        ((removed_count++))
                    else
                        printf " ${YELLOW}(skipped - may share layers)${NC}\n"
                    fi
                fi
            fi
        done

        if [ $total_count -eq 0 ]; then
            printf "  ${CYAN}No old images to remove${NC}\n"
        elif [ $removed_count -eq $total_count ]; then
            printf "  ${GREEN}Successfully removed all $removed_count old image(s)${NC}\n"
        else
            printf "  ${GREEN}Removed $removed_count of $total_count old image(s)${NC}\n"
        fi

        printf "${GREEN}✓ Docker image cleanup completed${NC}\n"
    else
        (
            # Silent cleanup with spinner
            {
                # Remove dangling images
                docker image prune -f > /dev/null 2>&1

                # Remove old AliasVault images by full name
                docker images --format "{{.Repository}}:{{.Tag}}" | grep -i "aliasvault" | \
                while read -r image_full; do
                    # Extract just the tag from the full image name
                    local tag="${image_full##*:}"

                    # Skip if image is currently being used by a container
                    if ! docker ps -a --format "{{.Image}}" | grep -q "^${image_full}$"; then
                        # Skip if it's the current version or latest
                        if [ "$tag" != "$current_version" ] && [ "$tag" != "latest" ]; then
                            # Remove by full repository:tag name
                            docker rmi "$image_full" > /dev/null 2>&1 || true
                        fi
                    fi
                done
            } &
            CLEANUP_PID=$!
            show_spinner $CLEANUP_PID "Cleaning up old Docker images "
            wait $CLEANUP_PID
        )
        printf "${GREEN}✓ Docker image cleanup completed${NC}\n"
    fi
}

# Function to print success message
print_install_success_message() {
    printf "\n"
    print_success_box "AliasVault is successfully installed!"
    printf "\n"
    printf "${BOLD}To configure the server, login to the admin panel:${NC}\n"
    printf "\n"
    if [ -n "$PASSWORD" ]; then
        printf "  ${CYAN}Admin Panel:${NC} https://localhost/admin\n"
        printf "  ${CYAN}Username:${NC} admin\n"
        printf "  ${CYAN}Password:${NC} $PASSWORD\n"
        printf "\n"
        printf "${YELLOW}⚠  IMPORTANT: Make sure to backup the above credentials in a safe place,${NC}\n"
        printf "${YELLOW}   they won't be shown again!${NC}\n"
    else
        printf "  ${CYAN}Admin Panel:${NC} https://localhost/admin\n"
        printf "  ${CYAN}Username:${NC} admin\n"
        printf "  ${CYAN}Password:${NC} (Previously set. Use ./install.sh reset-admin-password to generate new one.)\n"
    fi
    printf "\n"
    printf "${BOLD}To start using AliasVault, log into the client website:${NC}\n"
    printf "\n"
    printf "  ${CYAN}Client Website:${NC} https://localhost/\n"
}

# Function to check if AliasVault is responding on configured ports
check_aliasvault_health() {
    printf "${CYAN}ℹ Verifying AliasVault is responding...${NC} "

    local ports_config
    ports_config=$(get_port_config)
    read -r http_port https_port smtp_port <<< "$ports_config"

    local max_attempts=30
    local attempt=1
    local spinstr='|/-\\'
    local i=0
    local all_healthy=false

    while [ $attempt -le $max_attempts ]; do
        printf "\b%c" "${spinstr:$i:1}"
        ((i = (i + 1) % 4))

        local http_ok=false
        local https_ok=false

        # Check HTTP port (should redirect to HTTPS)
        if curl -Ls -o /dev/null -w "%{http_code}" "http://localhost:$http_port" 2>/dev/null | grep -q "301"; then
            http_ok=true
        fi

        # Check HTTPS port (should return status page or main app)
        local https_status
        https_status=$(curl -Ls -k -o /dev/null -w "%{http_code}" "https://localhost:$https_port" 2>/dev/null)
        if [ "$https_status" = "200" ] || [ "$https_status" = "503" ]; then
            https_ok=true
        fi

        if [ "$http_ok" = true ] && [ "$https_ok" = true ]; then
            all_healthy=true
            break
        fi

        sleep 2
        ((attempt++))
    done

    if [ "$all_healthy" = true ]; then
        printf "\b ${GREEN}✓${NC}\n"
        printf "  ${GREEN}✓ AliasVault is responding on HTTP:$http_port and HTTPS:$https_port${NC}\n"
        return 0
    else
        printf "\b ${RED}✗${NC}\n"
        printf "\n${RED}❌ Health Check Failed${NC}\n"
        printf "AliasVault containers started but the service is not responding properly.\n"
        printf "\n"
        printf "${YELLOW}Troubleshooting steps:${NC}\n"
        printf "1. Check container status: ${DIM}docker compose ps${NC}\n"
        printf "2. Check container logs: ${DIM}docker compose logs${NC}\n"
        printf "3. Verify ports are not blocked by firewall\n"
        printf "4. Wait a few more minutes for services to fully initialize\n"
        printf "\n"
        printf "You can manually check if AliasVault is working by visiting:\n"
        printf "  ${CYAN}https://localhost:$https_port${NC}\n"
        printf "\n"
        return 1
    fi
}

# Function to recreate (restart) Docker containers
recreate_docker_containers() {

    if [ "$VERBOSE" = true ]; then
        printf "${CYAN}ℹ (Re)creating Docker containers...${NC}\n"
        printf "\b${NC}\n"
        if ! eval "$(get_docker_compose_command) up -d --force-recreate"; then
            log_error "Failed to recreate Docker containers"
            exit 1
        fi
    else
        (
            eval "$(get_docker_compose_command) up -d --force-recreate" > /tmp/docker_recreate.log 2>&1 &
            RECREATE_PID=$!
            show_spinner $RECREATE_PID "Recreating Docker containers "
            wait $RECREATE_PID
            RECREATE_EXIT_CODE=$?

            if [ $RECREATE_EXIT_CODE -ne 0 ]; then
                log_error "Failed to recreate Docker containers. Output:"
                cat /tmp/docker_recreate.log >&2
                exit 1
            fi

            rm -f /tmp/docker_recreate.log
        )
    fi
    printf "${GREEN}✓ Docker containers (re)created successfully${NC}\n"
}

# Function to print password reset success message
print_password_reset_message() {
    printf "\n"
    print_success_box "The admin password has been successfully reset!"
    printf "\n"
    printf "${BOLD}New admin credentials:${NC}\n"
    printf "  ${CYAN}Admin Panel:${NC} https://localhost/admin\n"
    printf "  ${CYAN}Username:${NC} admin\n"
    printf "  ${CYAN}Password:${NC} $PASSWORD\n"
    printf "\n"
    printf "${YELLOW}⚠  IMPORTANT: Make sure to backup the above credentials in a safe place,${NC}\n"
    printf "${YELLOW}   they won't be shown again!${NC}\n"
}

# Function to get docker compose command with appropriate config files
get_docker_compose_command() {
    local base_command="docker compose -f docker-compose.yml"

    # Check if using build configuration
    if grep -q "^DEPLOYMENT_MODE=build" "$ENV_FILE" 2>/dev/null; then
        base_command="$base_command -f dockerfiles/docker-compose.build.yml"
    fi

    # Check if Let's Encrypt is enabled
    if grep -q "^LETSENCRYPT_ENABLED=true" "$ENV_FILE" 2>/dev/null; then
        base_command="$base_command -f docker-compose.letsencrypt.yml"
    fi

    echo "$base_command"
}

# Add this new function for handling registration configuration
handle_registration_configuration() {
    printf "${YELLOW}+++ Public Registration Configuration +++${NC}\n"
    printf "\n"

    # Check if AliasVault is installed
    if [ ! -f "docker-compose.yml" ]; then
        printf "${RED}Error: AliasVault must be installed first.${NC}\n"
        exit 1
    fi

    # Get current registration setting
    CURRENT_SETTING=$(grep "^PUBLIC_REGISTRATION_ENABLED=" "$ENV_FILE" | cut -d '=' -f2)

    printf "${CYAN}About Public Registration:${NC}\n"
    printf "Public registration allows new users to create their own accounts on your AliasVault server.\n"
    printf "When disabled, no new accounts can be created.\n"
    printf "\n"
    printf "${CYAN}Current Configuration:${NC}\n"
    if [ "$CURRENT_SETTING" = "true" ]; then
        printf "Public Registration: ${GREEN}Enabled${NC}\n"
    else
        printf "Public Registration: ${RED}Disabled${NC}\n"
    fi

    printf "\n"
    printf "Options:\n"
    printf "1) Enable public registration\n"
    printf "2) Disable public registration\n"
    printf "3) Cancel\n"
    printf "\n"

    read -p "Select an option [1-3]: " reg_option

    case $reg_option in
        1)
            update_env_var "PUBLIC_REGISTRATION_ENABLED" "true"

            printf "\n${YELLOW}Warning: Docker containers need to be restarted to apply these changes.${NC}\n"
            read -p "Restart now? (y/n): " restart_confirm

            if [ "$restart_confirm" != "y" ] && [ "$restart_confirm" != "Y" ]; then
                printf "${YELLOW}Please restart manually to apply the changes.${NC}\n"
                exit 0
            fi

            handle_restart

            # Print success message
            printf "\n"
            print_success_box "Public registration has been enabled!"
            ;;
        2)
            update_env_var "PUBLIC_REGISTRATION_ENABLED" "false"

            printf "\n${YELLOW}Warning: Docker containers need to be restarted to apply these changes.${NC}\n"
            read -p "Restart now? (y/n): " restart_confirm

            if [ "$restart_confirm" != "y" ] && [ "$restart_confirm" != "Y" ]; then
                printf "${YELLOW}Please restart manually to apply the changes.${NC}\n"
                exit 0
            fi

            handle_restart

            # Print success message
            printf "\n"
            print_success_box "Public registration has been disabled!"
            ;;
        3)
            printf "${YELLOW}Registration configuration cancelled.${NC}\n"
            exit 0
            ;;
        *)
            printf "${RED}Invalid option selected.${NC}\n"
            exit 1
            ;;
    esac
}

# Function to handle initial installation or reinstallation
handle_install() {
    local specified_version="$1"

    # If version specified, install that version directly
    if [ -n "$specified_version" ]; then
        handle_install_version "$specified_version"
        return
    fi

    # Check if .env exists before reading
    if [ -f "$ENV_FILE" ]; then
        if grep -q "^ALIASVAULT_VERSION=" "$ENV_FILE"; then
            current_version=$(grep "^ALIASVAULT_VERSION=" "$ENV_FILE" | cut -d '=' -f2)
            printf "${CYAN}> Current AliasVault version: ${current_version}${NC}\n"
            printf "\n"
            printf "==================================================\n"
            printf "AliasVault is already installed.\n"
            printf "==================================================\n"
            printf "1. To reinstall the current version (${current_version}), continue with this script\n"
            printf "2. To check for updates and to install the latest version, use: ./install.sh update\n"
            printf "3. To install a specific version, use: ./install.sh install <version>\n\n"

            read -p "Would you like to reinstall the current version? [y/N]: " REPLY
            if [[ ! $REPLY =~ ^[Yy]$ ]]; then
                printf "${YELLOW}> Installation cancelled.${NC}\n"
                exit 0
            fi

            handle_install_version "$current_version"
            return
        fi
    fi

    handle_install_version "latest"
}

# Function to handle build
handle_build() {
    printf "\n${YELLOW}+++ Building AliasVault from source +++${NC}\n"

    # Set deployment mode to build to ensure container lifecycle uses build configuration
    set_deployment_mode "build"

    # Initialize workspace which makes sure all required directories and files exist
    initialize_workspace

    # Check for required build files
    if [ ! -f "dockerfiles/docker-compose.build.yml" ] || [ ! -d "apps/server" ]; then
        printf "${RED}Error: Required files for building from source are missing.${NC}\n"
        printf "\n"
        printf "To build AliasVault from source, you need:\n"
        printf "1. dockerfiles/docker-compose.build.yml file\n"
        printf "2. apps/server/ directory with the complete source code\n"
        printf "\n"
        printf "Please clone the complete repository using:\n"
        printf "git clone https://github.com/${REPO_OWNER}/${REPO_NAME}.git\n"
        printf "\n"
        printf "Alternatively, you can use './install.sh install' to pull pre-built images.\n"
        exit 1
    fi

    # Initialize environment with proper error handling
    check_and_populate_env

    # Only generate admin password if not already set
    if [ ! -f "${SECRETS_DIR}/admin_password_hash" ] || [ -z "$(cat "${SECRETS_DIR}/admin_password_hash" 2>/dev/null)" ]; then
        generate_admin_password || { printf "${RED}> Failed to generate admin password${NC}\n"; exit 1; }
    fi

    printf "\n${YELLOW}+++ Building and starting services +++${NC}\n"

    if [ "$VERBOSE" = true ]; then
        printf "${CYAN}ℹ Building Docker Compose stack...${NC}\n"
        printf "\b${NC}\n"
        if ! eval "$(get_docker_compose_command) build"; then
            log_error "Failed to build Docker Compose stack"
            exit 1
        fi
    else
        (
            eval "$(get_docker_compose_command) build" > install_compose_build_output.log 2>&1 &
            BUILD_PID=$!
            show_spinner $BUILD_PID "Building Docker Compose stack "
            wait $BUILD_PID
            BUILD_EXIT_CODE=$?

            if [ $BUILD_EXIT_CODE -ne 0 ]; then
                log_error "Failed to build Docker Compose stack. Build output:"
                cat install_compose_build_output.log >&2
                exit 1
            fi

            rm -f install_compose_build_output.log
        )
    fi
    printf "${GREEN}✓ Docker Compose stack built successfully${NC}\n"

    recreate_docker_containers

    # Check if AliasVault is actually responding before showing success
    if ! check_aliasvault_health; then
        printf "${YELLOW}Installation completed but AliasVault health check failed.${NC}\n"
        printf "${YELLOW}Please check the troubleshooting steps above.${NC}\n"
        exit 1
    fi

    # Clean up old Docker images
    cleanup_docker_images "$target_version"

    # Only show success message if we made it here without errors and health check passed
    print_install_success_message
}

# Function to handle uninstall
handle_uninstall() {
    printf "${YELLOW}+++ Uninstalling AliasVault +++${NC}\n"
    printf "\n"

    # Check if -y flag was passed
    if [ "$FORCE_YES" != "true" ]; then
        # Ask for confirmation before proceeding
        read -p "Are you sure you want to uninstall AliasVault? This will remove all containers and images. [y/N]: " REPLY
        if [[ ! $REPLY =~ ^[Yy]$ ]]; then
            printf "${YELLOW}> Uninstall cancelled.${NC}\n"
            exit 0
        fi
    fi

    printf "${CYAN}> Stopping and removing Docker containers...${NC}\n"
    if [ "$VERBOSE" = true ]; then
        docker compose -f docker-compose.yml down -v || {
            printf "${RED}> Failed to stop and remove Docker containers${NC}\n"
            exit 1
        }
    else
        docker compose -f docker-compose.yml down -v > /dev/null 2>&1 || {
            printf "${RED}> Failed to stop and remove Docker containers${NC}\n"
            exit 1
        }
    fi
    printf "${GREEN}> Docker containers stopped and removed.${NC}\n"

    # Remove version from .env
    delete_env_var "ALIASVAULT_VERSION" ""

    printf "${CYAN}> Removing Docker images...${NC}\n"
    if [ "$VERBOSE" = true ]; then
        docker compose -f docker-compose.yml down --rmi all || {
            printf "${RED}> Failed to remove Docker images${NC}\n"
            exit 1
        }
    else
        docker compose -f docker-compose.yml down --rmi all > /dev/null 2>&1 || {
            printf "${RED}> Failed to remove Docker images${NC}\n"
            exit 1
        }
    fi
    printf "${GREEN}> Docker images removed.${NC}\n"

    printf "${CYAN}> Pruning Docker system...${NC}\n"
    if [ "$VERBOSE" = true ]; then
        docker system prune -af || {
            printf "${RED}> Failed to prune Docker system${NC}\n"
            exit 1
        }
    else
        docker system prune -af > /dev/null 2>&1 || {
            printf "${RED}> Failed to prune Docker system${NC}\n"
            exit 1
        }
    fi
    printf "${GREEN}> Docker system pruned.${NC}\n"

    # Only show success message if we made it here without errors
    printf "\n"
    print_success_box "AliasVault has been successfully uninstalled!"
    printf "\n"
    printf "All Docker containers and images related to AliasVault have been removed.\n"
    printf "The current directory, including database, logs and .env files, has been left intact.\n"
    printf "\n"
    printf "If you wish to remove the remaining files, it's safe to do so now.\n"
}

# Function to handle SSL configuration
handle_ssl_configuration() {
    printf "${YELLOW}+++ SSL Certificate Configuration +++${NC}\n"
    printf "\n"

    # Check if AliasVault is installed
    if [ ! -f "docker-compose.yml" ]; then
        printf "${RED}Error: AliasVault must be installed first.${NC}\n"
        exit 1
    fi

    populate_hostname || { printf "${RED}> Failed to set hostname${NC}\n"; exit 1; }

    # Get the current hostname and SSL config from .env
    CURRENT_HOSTNAME=$(grep "^HOSTNAME=" "$ENV_FILE" | cut -d '=' -f2)
    LETSENCRYPT_ENABLED=$(grep "^LETSENCRYPT_ENABLED=" "$ENV_FILE" | cut -d '=' -f2)

    printf "${CYAN}SSL Certificate Options:${NC}\n"
    printf "AliasVault uses a self-signed SSL certificate by default.\n"
    printf "This provides encryption but may trigger browser warnings.\n"
    printf "\n"
    printf "You can switch to a trusted Let's Encrypt certificate, which:\n"
    printf "  - Avoids browser warnings\n"
    printf "  - Requires a public domain (not localhost)\n"
    printf "  - Needs ports 80 and 443 open to the internet\n"
    printf "\n"
    printf "Let's Encrypt certificates auto-renew before expiry.\n"
    printf "\n"
    printf "${CYAN}Current Configuration:${NC}\n"
    if [ "$LETSENCRYPT_ENABLED" = "true" ]; then
        printf "Using: ${GREEN}Let's Encrypt${NC}\n"
    else
        printf "Using: ${YELLOW}Self-signed${NC}\n"
    fi
    printf "Hostname: ${CYAN}${CURRENT_HOSTNAME}${NC} (change via: ./install.sh configure-hostname)\n"
    printf "\n"
    printf "Choose an option:\n"
    printf "1) Use Let's Encrypt certificate (recommended for public domains)\n"
    printf "2) Use self-signed certificate\n"
    printf "3) Cancel\n"
    printf "\n"

    read -p "Select an option [1-3]: " ssl_option

    case $ssl_option in
        1)
            configure_letsencrypt
            ;;
        2)
            generate_self_signed_cert
            ;;
        3)
            printf "${YELLOW}SSL configuration cancelled.${NC}\n"
            exit 0
            ;;
        *)
            printf "${RED}Invalid option selected.${NC}\n"
            exit 1
            ;;
    esac
}

# Function to handle email server configuration
handle_email_configuration() {
    # Setup trap for Ctrl+C and other interrupts
    trap 'printf "\n${YELLOW}Configuration cancelled by user.${NC}\n"; exit 1' INT TERM

    printf "${YELLOW}+++ Email Server Configuration +++${NC}\n"
    printf "\n"

    # Check if AliasVault is installed
    if [ ! -f "docker-compose.yml" ]; then
        printf "${RED}Error: AliasVault must be installed first.${NC}\n"
        exit 1
    fi

    # Get current email domains from .env
    CURRENT_DOMAINS=$(grep "^PRIVATE_EMAIL_DOMAINS=" "$ENV_FILE" | cut -d '=' -f2)

    printf "${CYAN}About Email Server:${NC}\n"
    printf "AliasVault includes a built-in email server for handling virtual email addresses.\n"
    printf "When enabled, it can receive emails for one or more configured domains.\n"
    printf "Each domain must have an MX DNS record pointing to this server's hostname.\n"
    printf "\n"
    printf "${CYAN}Current Configuration:${NC}\n"

    if [ -z "$CURRENT_DOMAINS" ] || [ "$CURRENT_DOMAINS" = "DISABLED.TLD" ]; then
        printf "Email Server Status: ${RED}Disabled${NC}\n"
    else
        printf "Email Server Status: ${GREEN}Enabled${NC}\n"
        printf "Active Domains: ${CYAN}${CURRENT_DOMAINS}${NC}\n"
    fi

    printf "\n"
    printf "Email Server Options:\n"
    printf "1) Enable email server / Update domains\n"
    printf "2) Disable email server\n"
    printf "3) Cancel\n"
    printf "\n"

    read -p "Select an option [1-3]: " email_option

    case $email_option in
        1)
            while true; do
                printf "\n${CYAN}Enter domain(s) for email server${NC}\n"
                printf "For multiple domains, separate with commas (e.g. domain1.com,domain2.com)\n"
                printf "IMPORTANT: Each domain must have an MX record in DNS pointing to this server.\n"
                read -p "Domains: " new_domains

                if [ -z "$new_domains" ]; then
                    printf "${RED}Error: Domains cannot be empty${NC}\n"
                    continue
                fi

                printf "\n${CYAN}You entered the following domains:${NC}\n"
                IFS=',' read -ra DOMAIN_ARRAY <<< "$new_domains"
                for domain in "${DOMAIN_ARRAY[@]}"; do
                    printf "  - ${GREEN}${domain}${NC}\n"
                done
                printf "\n"

                read -p "Are these domains correct? (y/n): " confirm
                if [ "$confirm" = "y" ] || [ "$confirm" = "Y" ]; then
                    break
                fi
            done

            # Update .env file and restart
            if ! update_env_var "PRIVATE_EMAIL_DOMAINS" "$new_domains"; then
                printf "${RED}Failed to update configuration.${NC}\n"
                exit 1
            fi

            printf "${GREEN}Email server configuration updated${NC}\n"

            printf "\n${YELLOW}Warning: Docker containers need to be restarted to apply these changes.${NC}\n"
            read -p "Restart now? (y/n): " restart_confirm

            if [ "$restart_confirm" != "y" ] && [ "$restart_confirm" != "Y" ]; then
                printf "${YELLOW}Please restart manually to apply the changes.${NC}\n"
                exit 0
            fi

            if ! handle_restart; then
                printf "${RED}Failed to restart services.${NC}\n"
                exit 1
            fi

            # Print success message
            printf "\n"
            print_success_box "The email server is now successfully configured!"

            # Print next steps
            printf "\n"
            printf "To test the email server:\n"
            printf "   a. Log in to your AliasVault account\n"
            printf "   b. Create a new alias using one of your configured private domains\n"
            printf "   c. Send a test email from an external email service (e.g., Gmail)\n"
            printf "   d. Check if the email appears in your AliasVault inbox\n"
            printf "\n"
            printf "If emails don't arrive, please verify:\n"
            printf "   > DNS MX records are correctly configured\n"
            printf "   > Your server's firewall allows incoming traffic on port 25 and 587\n"
            printf "   > Your ISP/hosting provider doesn't block SMTP traffic\n"
            printf "\n"
            ;;
        2)
            printf "\n${YELLOW}Warning: Docker containers need to be restarted after disabling the email server.${NC}\n"
            read -p "Continue with disable and restart? (y/n): " disable_confirm

            if [ "$disable_confirm" != "y" ] && [ "$disable_confirm" != "Y" ]; then
                printf "${YELLOW}Configuration cancelled.${NC}\n"
                exit 0
            fi

            # Disable email server
            if ! update_env_var "PRIVATE_EMAIL_DOMAINS" ""; then
                printf "${RED}Failed to update configuration.${NC}\n"
                exit 1
            fi

            if ! handle_restart; then
                printf "${RED}Failed to restart services.${NC}\n"
                exit 1
            fi

            # Print success message
            printf "\n"
            print_success_box "The email server has been disabled successfully!"
            ;;
        3)
            printf "${YELLOW}Email configuration cancelled.${NC}\n"
            exit 0
            ;;
        *)
            printf "${RED}Invalid option selected.${NC}\n"
            exit 1
            ;;
    esac

    # Remove the trap before normal exit
    trap - INT TERM
}

# Function to configure Let's Encrypt
configure_letsencrypt() {
    printf "${CYAN}> Configuring Let's Encrypt SSL certificate...${NC}\n"

    # Check if hostname is localhost
    if [ "$CURRENT_HOSTNAME" = "localhost" ]; then
        printf "${RED}Error: Let's Encrypt certificates cannot be issued for 'localhost'.${NC}\n"
        printf "${YELLOW}Please configure a valid publically resolvable domain name (e.g. mydomain.com) before setting up Let's Encrypt.${NC}\n"
        exit 1
    fi

    # Check if hostname is a valid domain
    if ! [[ "$CURRENT_HOSTNAME" =~ \.[a-zA-Z]{2,}$ ]]; then
        printf "${RED}Error: Invalid hostname '${CURRENT_HOSTNAME}'.${NC}\n"
        printf "${YELLOW}Please configure a valid publically resolvable domain name (e.g. mydomain.com) before setting up Let's Encrypt.${NC}\n"
        exit 1
    fi

    # Verify DNS is properly configured
    printf "\n${YELLOW}Important: Before proceeding, ensure that:${NC}\n"
    printf "1. AliasVault is currently running and accessible at ${CYAN}https://${CURRENT_HOSTNAME}${NC}\n"
    printf "2. Your domain (${CYAN}${CURRENT_HOSTNAME}${NC}) is externally resolvable to this server's IP address\n"
    printf "3. Ports 80 and 443 are open and accessible from the internet\n"
    printf "\n"

    read -p "Have you completed these steps? [y/N]: " REPLY
    if [[ ! $REPLY =~ ^[Yy]$ ]]; then
        printf "${YELLOW}> Let's Encrypt configuration cancelled.${NC}\n"
        exit 0
    fi

    # Get contact email for Let's Encrypt
    SUPPORT_EMAIL=$(grep "^SUPPORT_EMAIL=" "$ENV_FILE" | cut -d '=' -f2)
    LETSENCRYPT_EMAIL=""

    while true; do
        printf "\nPlease enter a valid email address that will be used for Let's Encrypt certificate notifications:\n"
        read -p "Email: " LETSENCRYPT_EMAIL
        if [[ "$LETSENCRYPT_EMAIL" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then
            printf "Confirm using ${CYAN}${LETSENCRYPT_EMAIL}${NC} for Let's Encrypt notifications? [y/N] "
            read REPLY
            if [[ $REPLY =~ ^[Yy]$ ]]; then
                break
            fi
        else
            printf "${RED}Invalid email format. Please try again.${NC}\n"
        fi
    done

    # Create certbot directories
    printf "${CYAN}> Creating Let's Encrypt directories...${NC}\n"
    mkdir -p ./certificates/letsencrypt/www

    # Get absolute path for certificates directory for the docker bind mounts
    CERTIFICATES_DIR=$(realpath ./certificates)

    # Request certificate using a temporary certbot container
    printf "${CYAN}> Requesting Let's Encrypt certificate...${NC}\n"
    docker run --rm \
        -v "${CERTIFICATES_DIR}/letsencrypt:/etc/letsencrypt:rw" \
        -v "${CERTIFICATES_DIR}/letsencrypt/www:/var/www/certbot:rw" \
        certbot/certbot certonly \
        --webroot \
        --webroot-path=/var/www/certbot \
        --email "$LETSENCRYPT_EMAIL" \
        --agree-tos \
        --no-eff-email \
        --non-interactive \
        --domains ${CURRENT_HOSTNAME} \
        --force-renewal

    if [ $? -ne 0 ]; then
        printf "${RED}Failed to obtain Let's Encrypt certificate.${NC}\n"
        exit 1
    fi

    # Fix permissions on Let's Encrypt directories and files
    sudo chmod -R 755 ./certificates/letsencrypt

    # Ensure private keys remain secure
    sudo find ./certificates/letsencrypt -type f -name "privkey*.pem" -exec chmod 600 {} \;
    sudo find ./certificates/letsencrypt -type f -name "fullchain*.pem" -exec chmod 644 {} \;

    # Update .env to indicate Let's Encrypt is enabled
    update_env_var "LETSENCRYPT_ENABLED" "true"

    # Restart only the reverse proxy with new configuration so it loads the new certificate
    printf "${CYAN}> Restarting reverse proxy with Let's Encrypt configuration...${NC}\n"
    eval "$(get_docker_compose_command) up -d reverse-proxy --force-recreate"

    # Starting certbot container to renew certificates automatically
    printf "${CYAN}> Starting new certbot container to renew certificates automatically...${NC}\n"
    eval "$(get_docker_compose_command) up -d certbot"

    # Print success message
    printf "\n"
    print_success_box "Let's Encrypt SSL certificate has been configured successfully!"
}

# Function to generate self-signed certificate
generate_self_signed_cert() {
    printf "${CYAN}> Generating new self-signed certificate...${NC}\n"

    # Disable Let's Encrypt
    update_env_var "LETSENCRYPT_ENABLED" "false"

    # Get current hostname from .env
    HOSTNAME_VALUE=$(grep "^HOSTNAME=" "$ENV_FILE" | cut -d '=' -f2)

    if [ -n "$HOSTNAME_VALUE" ] && [ "$HOSTNAME_VALUE" != "localhost" ]; then
        printf "${CYAN}> Using configured hostname: ${HOSTNAME_VALUE}${NC}\n"
        printf "${CYAN}> The certificate will include:${NC}\n"
        printf "  ${GREEN}Primary CN:${NC} ${HOSTNAME_VALUE}\n"
        printf "  ${GREEN}Alternative Names:${NC} localhost, 127.0.0.1\n\n"
    fi

    # Stop existing containers
    printf "${CYAN}> Stopping existing containers...${NC}\n"
    docker compose down

    # Remove existing certificates and hostname marker
    rm -f ./certificates/ssl/cert.pem ./certificates/ssl/key.pem ./certificates/ssl/.hostname_marker

    # Remove Let's Encrypt directories
    rm -rf ./certificates/letsencrypt

    # Start containers (which will generate new self-signed cert with hostname)
    printf "${CYAN}> Restarting services...${NC}\n"
    docker compose up -d

    # Print success message
    printf "\n"
    print_success_box "New self-signed certificate has been generated successfully!"
}

# New functions to handle container lifecycle:
handle_start() {
    printf "${CYAN}> Starting AliasVault containers...${NC}\n"
    eval "$(get_docker_compose_command) up -d"
    printf "${GREEN}> AliasVault containers started successfully.${NC}\n"
}

handle_stop() {
    printf "${CYAN}> Stopping AliasVault containers...${NC}\n"
    if ! docker compose ps --quiet 2>/dev/null | grep -q .; then
        printf "${YELLOW}> No containers are currently running.${NC}\n"
        exit 0
    fi

    eval "$(get_docker_compose_command) down"
    printf "${GREEN}> AliasVault containers stopped successfully.${NC}\n"
}

handle_restart() {
    printf "\n${CYAN}> Restarting AliasVault containers...${NC}\n"
    eval "$(get_docker_compose_command) down"
    eval "$(get_docker_compose_command) up -d"
    printf "${GREEN}> AliasVault containers restarted successfully.${NC}\n"
}

# Function to handle updates
handle_update() {
    printf "\n${YELLOW}+++ Checking for AliasVault updates +++${NC}\n"

    # First check for install.sh updates
    check_install_script_update || true

    # Check current version
    if ! grep -q "^ALIASVAULT_VERSION=" "$ENV_FILE"; then
        printf "${CYAN}> No version information found. Running first-time update check...${NC}\n"
        printf "\n"
        handle_install_version "latest"
        return
    fi

    current_version=$(grep "^ALIASVAULT_VERSION=" "$ENV_FILE" | cut -d '=' -f2)
    latest_version=$(get_latest_version) || {
        printf "${RED}> Failed to check for updates. Please try again later.${NC}\n"
        exit 1
    }

    if [ -z "$latest_version" ]; then
        printf "${RED}> Failed to check for updates. Please try again later.${NC}\n"
        exit 1
    fi

    if [ "$current_version" = "$latest_version" ]; then
        printf "\n"
        printf "You are already running the latest version of AliasVault (${current_version})!\n"
        exit 0
    fi

    if [ "$FORCE_YES" = true ]; then
        printf "${CYAN}> Updating AliasVault to the latest version (${latest_version})...${NC}\n"
        handle_install_version "$latest_version"
        printf "${GREEN}> Update completed successfully!${NC}\n"
        return
    fi

    printf "\n"
    printf "A new version of AliasVault is available (${latest_version})!\n"
    printf "\n"
    printf "${MAGENTA}Important:${NC}\n"
    printf "1. It's recommended to backup your database before updating (./install.sh db-export > backup.sql.gz)\n"
    printf "2. The update process will restart all containers\n"
    printf "\n"

    read -p "Would you like to continue with the update? [y/N]: " REPLY
    if [[ ! $REPLY =~ ^[Yy]$ ]]; then
        printf "${YELLOW}> Update cancelled.${NC}\n"
        exit 0
    fi

    printf "${CYAN}> Updating AliasVault...${NC}\n"
    printf "\n"
    handle_install_version "$latest_version"
}

# Function to extract version
extract_version() {
    local file="$1"
    local version=$(head -n 2 "$file" | grep '@version' | cut -d' ' -f3)
    echo "$version"
}

# Function to compare semantic versions
compare_versions() {
    local version1="$1"
    local version2="$2"

    # Split versions into arrays
    IFS='.' read -ra v1_parts <<< "$version1"
    IFS='.' read -ra v2_parts <<< "$version2"

    # Compare each part numerically
    for i in {0..2}; do
        # Default to 0 if part doesn't exist
        local v1_part=${v1_parts[$i]:-0}
        local v2_part=${v2_parts[$i]:-0}

        # Compare numerically
        if [ "$v1_part" -gt "$v2_part" ]; then
            echo "1"  # version1 is greater
            return
        elif [ "$v1_part" -lt "$v2_part" ]; then
            echo "-1"  # version1 is lesser
            return
        fi
    done

    echo "0"  # versions are equal
}

# Function to check if install.sh needs updating
check_install_script_update() {
    printf "${CYAN}> Checking for install script updates...${NC}\n"

    # Get latest release version
    local latest_version=$(get_latest_version) || {
        printf "${RED}> Failed to check for install script updates. Continuing with current version.${NC}\n"
        return 1
    }

    if [ -z "$latest_version" ]; then
        printf "${RED}> Failed to check for install script updates. Continuing with current version.${NC}\n"
        return 1
    fi

    if ! curl -LsSf "${GITHUB_RAW_URL_REPO}/${latest_version}/install.sh" -o "install.sh.tmp"; then
        printf "${RED}> Failed to check for install script updates. Continuing with current version.${NC}\n"
        rm -f install.sh.tmp
        return 1
    fi

    # Get versions
    local current_version=$(extract_version "install.sh")
    local new_version=$(extract_version "install.sh.tmp")

    # Check if versions could be extracted
    if [ -z "$current_version" ] || [ -z "$new_version" ]; then
        printf "${YELLOW}> Could not determine script versions. Falling back to file comparison...${NC}\n"
        # Fall back to file comparison
        if ! cmp -s "install.sh" "install.sh.tmp"; then
            printf "${YELLOW}> Changes detected in install script.${NC}\n"
        else
            printf "${GREEN}> Install script is up to date.${NC}\n"
            rm -f install.sh.tmp
            return 0
        fi
    else
        # Compare versions using semver comparison
        if [ "$current_version" = "$new_version" ]; then
            printf "${GREEN}> Install script is up to date.${NC}\n"
            rm -f install.sh.tmp
            return 0
        else
            local compare_result=$(compare_versions "$current_version" "$new_version")

            if [ "$compare_result" -ge "0" ]; then
                printf "${GREEN}> Install script is up to date.${NC}\n"
                rm -f install.sh.tmp
                return 0
            fi
        fi
    fi

    # If we get here, an update is available
    if [ "$FORCE_YES" = true ]; then
        printf "${CYAN}> Updating install script...${NC}\n"
        cp "install.sh" "install.sh.backup"
        mv "install.sh.tmp" "install.sh"
        chmod +x "install.sh"
        printf "${GREEN}> Install script updated successfully.${NC}\n"
        printf "${GREEN}> Backup of previous version saved as install.sh.backup${NC}\n"
        exit 0
    fi

    printf "${YELLOW}> A new version of the install script is available (${new_version}).${NC}\n"
    printf "\n"
    printf "Would you like to update the install script? [Y/n]: "
    read -r reply

    if [[ ! $reply =~ ^[Nn]$ ]]; then
        # Create backup of current script
        cp "install.sh" "install.sh.backup"

        if mv "install.sh.tmp" "install.sh"; then
            chmod +x "install.sh"
            printf "${GREEN}> Install script updated successfully.${NC}\n"
            printf "${GREEN}> Backup of previous version saved as install.sh.backup${NC}\n"
            printf "${YELLOW}> Please run the update command again to continue with the update process.${NC}\n"
            exit 0
        else
            printf "${RED}> Failed to update install script. Continuing with current version.${NC}\n"
            # Restore from backup if update failed
            mv "install.sh.backup" "install.sh"
            rm -f install.sh.tmp
            return 1
        fi
    else
        printf "${YELLOW}> Continuing with current install script version.${NC}\n"
        rm -f install.sh.tmp
        return 0
    fi
}

# Function to perform the actual installation with specific version
handle_install_version() {
    local target_version="$1"

    # Validate semver format if a specific version is provided
    if [ -n "$target_version" ] && [ "$target_version" != "latest" ]; then
        if ! validate_semver "$target_version"; then
            printf "${RED}Error: You tried to install AliasVault with version '${target_version}' which is an incorrect value.${NC}\n"
            printf "Please check the command you executed and try again.${NC}\n"
            printf "\n"
            printf "The provided version must follow semantic versioning format (e.g., '0.0.1', '1.0.5') and match an existing version on GitHub.${NC}\n"
            printf "Alternatively, you can omit the version to install the latest version.${NC}\n"
            exit 1
        fi
    fi

    # If latest, get actual version number from GitHub API
    if [ "$target_version" = "latest" ]; then
        local actual_version=$(get_latest_version) || {
            printf "${RED}> Failed to get latest version. Please try again later.${NC}\n"
            exit 1
        }
        if [ -n "$actual_version" ]; then
            target_version="$actual_version"
        fi
    fi

    printf "\n${YELLOW}+++ Installing AliasVault ${target_version} +++${NC}\n"

    # Initialize workspace which makes sure all required directories and files exist
    initialize_workspace

    # Check if install script needs updating for this version
    check_install_script_version "$target_version"
    local check_result=$?

    if [ $check_result -eq 2 ]; then
        if [ "$FORCE_YES" = true ]; then
            printf "${CYAN}> Updating install script to match version ${target_version}...${NC}\n"
        else
            printf "${YELLOW}> A different version of the install script is required for installing version ${target_version}.${NC}\n"
            read -p "Would you like to self-update the install script before proceeding? [Y/n]: " reply
            if [[ $reply =~ ^[Nn]$ ]]; then
                printf "${YELLOW}> Continuing with current install script version.${NC}\n"
                rm -f install.sh.tmp
            fi
        fi

        if [ "$FORCE_YES" = true ] || [[ ! $reply =~ ^[Nn]$ ]]; then
            # Create backup of current script
            cp "install.sh" "install.sh.backup"

            if mv "install.sh.tmp" "install.sh"; then
                chmod +x "install.sh"
                printf "${GREEN}> Install script updated successfully.${NC}\n"
                printf "${GREEN}> Backup of previous version saved as install.sh.backup${NC}\n"
                printf "${YELLOW}> Please run the same install command again to continue with the installation.${NC}\n"
                exit 2
            else
                printf "${RED}> Failed to update install script. Continuing with current version.${NC}\n"
                mv "install.sh.backup" "install.sh"
                rm -f install.sh.tmp
            fi
        fi
    fi

    # Update docker-compose files with correct version so we pull the correct images
    if ! handle_docker_compose "$target_version"; then
        log_error "Failed to download docker-compose files"
        exit 1
    fi

    # Initialize environment
    check_and_populate_env

    # Set deployment mode to install to ensure container lifecycle uses install configuration
    set_deployment_mode "install"

    # Only generate admin password if not already set
    if [ ! -f "${SECRETS_DIR}/admin_password_hash" ] || [ -z "$(cat "${SECRETS_DIR}/admin_password_hash" 2>/dev/null)" ]; then
        generate_admin_password || { printf "${RED}> Failed to generate admin password${NC}\n"; exit 1; }
    fi

    # Pull images from GitHub Container Registry
    printf "\n${YELLOW}+++ Pulling Docker images +++${NC}\n"
    printf "${CYAN}ℹ Installing version: ${target_version}${NC}\n"

    images=(
        "${GITHUB_CONTAINER_REGISTRY}/postgres:${target_version}"
        "${GITHUB_CONTAINER_REGISTRY}/reverse-proxy:${target_version}"
        "${GITHUB_CONTAINER_REGISTRY}/api:${target_version}"
        "${GITHUB_CONTAINER_REGISTRY}/client:${target_version}"
        "${GITHUB_CONTAINER_REGISTRY}/admin:${target_version}"
        "${GITHUB_CONTAINER_REGISTRY}/smtp:${target_version}"
        "${GITHUB_CONTAINER_REGISTRY}/task-runner:${target_version}"
    )

    for image in "${images[@]}"; do
        if ! retry_command 3 5 enhanced_docker_pull "$image"; then
            log_warning "Failed to pull image: $image - continuing anyway"
        fi
    done

    printf "${GREEN}✓ Docker image pulling completed${NC}\n"

    # Save version to .env
    update_env_var "ALIASVAULT_VERSION" "$target_version"

    # Start containers
    printf "\n${YELLOW}+++ Starting services +++${NC}\n"

    if [ "$VERBOSE" = true ]; then
        printf "${CYAN}ℹ Starting Docker containers...${NC} "
        printf "\b${NC}\n"
        if ! docker compose up -d --force-recreate; then
            log_error "Failed to start Docker containers"
            exit 1
        fi
    else
        (
            docker compose up -d --force-recreate > /tmp/docker_start.log 2>&1 &
            START_PID=$!
            show_spinner $START_PID "Starting Docker containers "
            wait $START_PID
            START_EXIT_CODE=$?

            if [ $START_EXIT_CODE -ne 0 ]; then
                log_error "Failed to start Docker containers. Output:"
                cat /tmp/docker_start.log >&2
                exit 1
            fi

            rm -f /tmp/docker_start.log
        )
    fi
    printf "${GREEN}✓ Docker containers started successfully${NC}\n"

    # Check if AliasVault is actually responding before showing success
    if ! check_aliasvault_health; then
        printf "${YELLOW}Installation completed but AliasVault health check failed.${NC}\n"
        printf "${YELLOW}Please check the troubleshooting steps above.${NC}\n"
        exit 1
    fi

    # Clean up old Docker images
    cleanup_docker_images "$target_version"

    # Only show success message if we made it here without errors and health check passed
    print_install_success_message
}

# Function to handle development database configuration
configure_dev_database() {
    printf "${YELLOW}+++ Development Database Configuration +++${NC}\n"
    printf "\n"

    if [ ! -f "dockerfiles/docker-compose.dev.yml" ]; then
        printf "${RED}> The dockerfiles/docker-compose.dev.yml file is missing. This file is required to start the development database. Please checkout the full GitHub repository and try again.${NC}\n"
        return 1
    fi

    # Check if direct option was provided
    if [ -n "$COMMAND_ARG" ]; then
        case $COMMAND_ARG in
            1|start)
                if docker compose -f dockerfiles/docker-compose.dev.yml -p aliasvault-dev ps --status running 2>/dev/null | grep -q postgres-dev; then
                    printf "${YELLOW}> Development database is already running.${NC}\n"
                else
                    printf "${CYAN}> Starting development database...${NC}\n"
                    docker compose -p aliasvault-dev -f dockerfiles/docker-compose.dev.yml up -d --wait --wait-timeout 60
                    printf "${GREEN}> Development database started successfully.${NC}\n"
                fi
                print_dev_db_details
                return
                ;;
            0|stop)
                if ! docker compose -f dockerfiles/docker-compose.dev.yml -p aliasvault-dev ps --status running 2>/dev/null | grep -q postgres-dev; then
                    printf "${YELLOW}> Development database is already stopped.${NC}\n"
                else
                    printf "${CYAN}> Stopping development database...${NC}\n"
                    docker compose -p aliasvault-dev -f dockerfiles/docker-compose.dev.yml down
                    printf "${GREEN}> Development database stopped successfully.${NC}\n"
                fi
                return
                ;;
        esac
    fi

    # Check current status
    if docker compose -f dockerfiles/docker-compose.dev.yml -p aliasvault-dev ps --status running 2>/dev/null | grep -q postgres-dev; then
        DEV_DB_STATUS="running"
    else
        DEV_DB_STATUS="stopped"
    fi

    printf "${CYAN}About Development Database:${NC}\n"
    printf "A separate PostgreSQL instance for development purposes that:\n"
    printf "  - Runs on port 5433 (to avoid conflicts)\n"
    printf "  - Uses simple credentials (password: 'password')\n"
    printf "  - Stores data separately from production\n"
    printf "\n"
    printf "${CYAN}Current Status:${NC}\n"
    if [ "$DEV_DB_STATUS" = "running" ]; then
        printf "Development Database: ${GREEN}Running${NC}\n"
    else
        printf "Development Database: ${YELLOW}Stopped${NC}\n"
    fi
    printf "\n"
    printf "Options:\n"
    printf "1) Start development database\n"
    printf "2) Stop development database\n"
    printf "3) View connection details\n"
    printf "4) Cancel\n"
    printf "\n"

    read -p "Select an option [1-4]: " dev_db_option

    case $dev_db_option in
        1)
            if [ "$DEV_DB_STATUS" = "running" ]; then
                printf "${YELLOW}> Development database is already running.${NC}\n"
            else
                printf "${CYAN}> Starting development database...${NC}\n"
                docker compose -p aliasvault-dev -f dockerfiles/docker-compose.dev.yml up -d --wait --wait-timeout 60
                printf "${GREEN}> Development database started successfully.${NC}\n"
            fi
            print_dev_db_details
            ;;
        2)
            if [ "$DEV_DB_STATUS" = "stopped" ]; then
                printf "${YELLOW}> Development database is already stopped.${NC}\n"
            else
                printf "${CYAN}> Stopping development database...${NC}\n"
                docker compose -p aliasvault-dev -f dockerfiles/docker-compose.dev.yml down
                printf "${GREEN}> Development database stopped successfully.${NC}\n"
            fi
            ;;
        3)
            print_dev_db_details
            ;;
        4)
            printf "${YELLOW}Configuration cancelled.${NC}\n"
            exit 0
            ;;
        *)
            printf "${RED}Invalid option selected.${NC}\n"
            exit 1
            ;;
    esac
}

# Function to print development database connection details
print_dev_db_details() {
    printf "\n"
    printf "${MAGENTA}=========================================================${NC}\n"
    printf "\n"
    printf "${CYAN}Development Database Connection Details:${NC}\n"
    printf "Host: localhost\n"
    printf "Port: 5433\n"
    printf "Database: aliasvault\n"
    printf "Username: aliasvault\n"
    printf "Password: password\n"
    printf "\n"
    printf "Connection string:\n"
    printf "Host=localhost;Port=5433;Database=aliasvault;Username=aliasvault;Password=password\n"
    printf "\n"
    printf "${MAGENTA}=========================================================${NC}\n"
}

# Function to set deployment mode in .env
set_deployment_mode() {
    local mode=$1
    if [ "$mode" != "build" ] && [ "$mode" != "install" ]; then
        printf "${RED}Invalid deployment mode: $mode${NC}\n"
        exit 1
    fi
    update_env_var "DEPLOYMENT_MODE" "$mode" &>/dev/null
}

# Function to handle database export
handle_db_export() {
    printf "${YELLOW}+++ Exporting Database +++${NC}\n" >&2

    # Check if output redirection is present
    if [ -t 1 ]; then
        printf "Usage: ./install.sh db-export [OPTIONS] > backup.sql.gz\n" >&2
        printf "\n" >&2
        printf "Options:\n" >&2
        printf "  --dev                Export from development database\n" >&2
        printf "  --parallel=N         Use pigz with N threads for faster compression (max: 32)\n" >&2
        printf "\n" >&2
        printf "Examples:\n" >&2
        printf "  ./install.sh db-export > backup.sql.gz              # Standard compression\n" >&2
        printf "  ./install.sh db-export --parallel=4 > backup.sql.gz # Parallel compression\n" >&2
        printf "\n" >&2
        printf "Note: Parallel compression runs at lowest priority to minimize system impact.\n" >&2
        printf "\n" >&2
        exit 1
    fi

    # Determine docker compose command based on dev/prod
    if [ "$DEV_DB" = true ]; then
        # Check if dev containers are running
        if ! docker compose -f dockerfiles/docker-compose.dev.yml -p aliasvault-dev ps postgres-dev --quiet 2>/dev/null | grep -q .; then
            printf "${RED}Error: Development database container is not running. Start it first with: ./install.sh configure-dev-db${NC}\n" >&2
            exit 1
        fi

        # Check if postgres-dev container is healthy
        if ! docker compose -f dockerfiles/docker-compose.dev.yml -p aliasvault-dev ps postgres-dev | grep -q "healthy"; then
            printf "${RED}Error: Development PostgreSQL container is not healthy. Please check the logs.${NC}\n" >&2
            exit 1
        fi

        DOCKER_CMD="docker compose -f dockerfiles/docker-compose.dev.yml -p aliasvault-dev exec -T postgres-dev"
        DB_TYPE="development"
    else
        # Production database export logic
        if ! docker compose ps --quiet 2>/dev/null | grep -q .; then
            printf "${RED}Error: AliasVault containers are not running. Start them first with: ./install.sh start${NC}\n" >&2
            exit 1
        fi

        if ! docker compose ps postgres | grep -q "healthy"; then
            printf "${RED}Error: PostgreSQL container is not healthy. Please check the logs with: docker compose logs postgres${NC}\n" >&2
            exit 1
        fi

        DOCKER_CMD="docker compose exec -T postgres"
        DB_TYPE="production"
    fi

    # Stream export directly to stdout (no temp files)

    # Start timing
    export_start_time=$(date +%s)

    if [ "$PARALLEL_JOBS" -gt 0 ]; then
        # Use pigz for parallel compression
        # Use nice (lowest CPU priority) and ionice (lowest I/O priority) to minimize impact
        printf "${CYAN}> Exporting ${DB_TYPE} database with parallel=${PARALLEL_JOBS} compression...${NC}\n" >&2
        $DOCKER_CMD bash -c "
            ionice -c 3 nice -n 19 pg_dump -U aliasvault aliasvault | ionice -c 3 nice -n 19 pigz -1 -p ${PARALLEL_JOBS} 2>/dev/null || \
            nice -n 19 pg_dump -U aliasvault aliasvault | nice -n 19 pigz -1 -p ${PARALLEL_JOBS}
        " 2>/dev/null
    else
        # Standard gzip
        printf "${CYAN}> Exporting ${DB_TYPE} database...${NC}\n" >&2
        $DOCKER_CMD nice -n 19 pg_dump -U aliasvault aliasvault | gzip -1
    fi
    export_status=$?

    # End timing
    export_end_time=$(date +%s)
    export_duration=$((export_end_time - export_start_time))

    if [ $export_status -eq 0 ]; then
        printf "${GREEN}> Database exported successfully.${NC}\n" >&2
        printf "${CYAN}> Export duration: ${export_duration}s${NC}\n" >&2
    else
        printf "${RED}> Failed to export database.${NC}\n" >&2
        exit 1
    fi
}

# Function to handle database import
handle_db_import() {
    # Save stdin to file descriptor 3 AS THE VERY FIRST THING
    # This MUST be first to prevent bash/docker/any command from consuming stdin bytes
    exec 3<&0

    printf "${YELLOW}+++ Importing Database +++${NC}\n"

    # Check if containers are running
    if [ "$DEV_DB" = true ]; then
        if ! docker compose -f dockerfiles/docker-compose.dev.yml -p aliasvault-dev ps postgres-dev | grep -q "healthy"; then
            printf "${RED}Error: Development PostgreSQL container is not healthy.${NC}\n"
            exit 1
        fi
    else
        if ! docker compose ps postgres | grep -q "healthy"; then
            printf "${RED}Error: PostgreSQL container is not healthy.${NC}\n"
            exit 1
        fi
    fi

    # Check if we're getting input from a pipe (check fd 3 instead of stdin now)
    if [ -t 3 ]; then
        printf "Usage: ./install.sh db-import [OPTIONS] < backup_file\n"
        printf "\n"
        printf "Options:\n"
        printf "  --dev      Import to development database\n"
        printf "\n"
        printf "Examples:\n"
        printf "  ./install.sh db-import < backup.sql.gz              # Import gzipped SQL\n"
        printf "  ./install.sh db-import < backup.sql                 # Import plain SQL\n"
        printf "  ./install.sh db-import --dev < backup.sql.gz        # Import to dev database\n"
        printf "\n"
        printf "Note: Import uses a temp file for format detection. For large imports,\n"
        printf "      ensure you have enough disk space (backup size + decompressed size).\n"
        exit 1
    fi

    printf "${RED}Warning: This will DELETE ALL EXISTING DATA in the "
    if [ "$DEV_DB" = true ]; then
        printf "development database"
    else
        printf "database"
    fi
    printf ".${NC}\n"

    if [ "$FORCE_YES" != true ]; then
        # Use /dev/tty to read from terminal even when stdin is redirected
        if [ -t 1 ] && [ -t 2 ] && [ -e /dev/tty ]; then
            # Temporarily switch stdin to tty for confirmation
            exec < /dev/tty
            read -p "Continue? [y/N]: " confirm
            # Switch back to original stdin
            exec 0<&3
            if [[ ! $confirm =~ ^[Yy]$ ]]; then
                exec 3<&-  # Close fd 3
                exit 1
            fi
        else
            printf "${RED}Error: Cannot read confirmation from terminal. Use -y flag to bypass confirmation.${NC}\n"
            exec 3<&-  # Close fd 3
            exit 1
        fi
    fi

    if [ "$DEV_DB" != true ]; then
        printf "${CYAN}> Stopping dependent services...${NC}\n"
        if [ "$VERBOSE" = true ]; then
            docker compose stop api admin task-runner smtp
        else
            docker compose stop api admin task-runner smtp > /dev/null 2>&1
        fi
    fi

    printf "${CYAN}> Importing "
    if [ "$DEV_DB" = true ]; then
        printf "development "
    fi
    printf "database...${NC}\n"

    # Determine docker compose command based on dev/prod
    if [ "$DEV_DB" = true ]; then
        DOCKER_CMD="docker compose -f dockerfiles/docker-compose.dev.yml -p aliasvault-dev exec -T postgres-dev"
    else
        DOCKER_CMD="docker compose exec -T postgres"
    fi

    # Create a temporary file to store the input
    temp_file=$(mktemp)
    # Set up trap to ensure temp file cleanup on exit
    trap 'rm -f "$temp_file"' EXIT INT TERM

    cat <&3 > "$temp_file"  # Read from fd 3 instead of stdin
    exec 3<&-  # Close fd 3

    # Get input filesize
    if [ -f "$temp_file" ]; then
        import_filesize=$(wc -c < "$temp_file")
        import_filesize_mb=$(awk "BEGIN {printf \"%.2f\", $import_filesize/1024/1024}")
        printf "${CYAN}> Input file size: ${import_filesize_mb} MB${NC}\n"
    fi

    # Detect file format
    is_gzipped=false

    if gzip -t "$temp_file" 2>/dev/null; then
        is_gzipped=true
        printf "${CYAN}> Detected gzipped SQL backup${NC}\n"
    else
        # Check if it looks like SQL (basic validation)
        if head -n 10 "$temp_file" | grep -qE '(^--|^CREATE |^INSERT |^ALTER |^DROP |^\\\\connect|^SET |^COMMENT |^GRANT |^REVOKE )'; then
            printf "${CYAN}> Detected plain SQL backup${NC}\n"
        else
            printf "${RED}Error: Input is neither a valid gzip file nor a SQL file${NC}\n"
            exit 1
        fi
    fi

    # Start timing
    import_start_time=$(date +%s)

    if [ "$VERBOSE" = true ]; then
        $DOCKER_CMD psql -U aliasvault postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'aliasvault' AND pid <> pg_backend_pid();" && \
        $DOCKER_CMD psql -U aliasvault postgres -c "DROP DATABASE IF EXISTS aliasvault;" && \
        $DOCKER_CMD psql -U aliasvault postgres -c "CREATE DATABASE aliasvault OWNER aliasvault;" && \
        if [ "$is_gzipped" = true ]; then
            gunzip -c "$temp_file" | $DOCKER_CMD psql -U aliasvault aliasvault
        else
            cat "$temp_file" | $DOCKER_CMD psql -U aliasvault aliasvault
        fi
    else
        $DOCKER_CMD psql -U aliasvault postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'aliasvault' AND pid <> pg_backend_pid();" > /dev/null 2>&1 && \
        $DOCKER_CMD psql -U aliasvault postgres -c "DROP DATABASE IF EXISTS aliasvault;" > /dev/null 2>&1 && \
        $DOCKER_CMD psql -U aliasvault postgres -c "CREATE DATABASE aliasvault OWNER aliasvault;" > /dev/null 2>&1 && \
        if [ "$is_gzipped" = true ]; then
            gunzip -c "$temp_file" | $DOCKER_CMD psql -U aliasvault aliasvault > /dev/null 2>&1
        else
            cat "$temp_file" | $DOCKER_CMD psql -U aliasvault aliasvault > /dev/null 2>&1
        fi
    fi

    import_status=$?

    # End timing
    import_end_time=$(date +%s)
    import_duration=$((import_end_time - import_start_time))

    rm "$temp_file"

    if [ $import_status -eq 0 ]; then
        printf "${GREEN}> Database imported successfully.${NC}\n"
        printf "${CYAN}> Import duration: ${import_duration}s${NC}\n"
        if [ "$DEV_DB" != true ]; then
            printf "${CYAN}> Starting services...${NC}\n"
            if [ "$VERBOSE" = true ]; then
                docker compose restart api admin task-runner smtp reverse-proxy
            else
                docker compose restart api admin task-runner smtp reverse-proxy > /dev/null 2>&1
            fi
        fi
    else
        printf "${RED}> Import failed. Please check that your backup file is valid.${NC}\n"
        exit 1
    fi
}

# Function to handle hostname configuration
handle_hostname_configuration() {
    printf "${YELLOW}+++ Hostname Configuration +++${NC}\n"
    printf "\n"

    # Check if AliasVault is installed
    if [ ! -f "docker-compose.yml" ]; then
        printf "${RED}Error: AliasVault must be installed first.${NC}\n"
        exit 1
    fi

    printf "The hostname is the domain name where your AliasVault server will be accessible.\n"
    printf "This hostname will be used for both Let's Encrypt and self-signed certificates.\n"
    printf "\n"

    # Get current hostname
    CURRENT_HOSTNAME=$(grep "^HOSTNAME=" "$ENV_FILE" | cut -d '=' -f2)
    printf "Current hostname: ${CYAN}${CURRENT_HOSTNAME}${NC}\n"
    printf "\n"

    # Ask for new hostname
    while true; do
        read -p "Enter new hostname (e.g. aliasvault.example.com): " NEW_HOSTNAME
        if [ -n "$NEW_HOSTNAME" ]; then
            break
        else
            printf "${YELLOW}> Hostname cannot be empty. Please enter a valid hostname.${NC}\n"
        fi
    done

    # Check if hostname changed
    HOSTNAME_CHANGED=false
    if [ "$CURRENT_HOSTNAME" != "$NEW_HOSTNAME" ]; then
        HOSTNAME_CHANGED=true
    fi

    # Update the hostname
    update_env_var "HOSTNAME" "$NEW_HOSTNAME"

    # If using self-signed cert and hostname changed, offer to regenerate
    if [ "$HOSTNAME_CHANGED" = true ]; then
        LETSENCRYPT_ENABLED=$(grep "^LETSENCRYPT_ENABLED=" "$ENV_FILE" | cut -d '=' -f2)
        if [ "$LETSENCRYPT_ENABLED" != "true" ]; then
            printf "\n${YELLOW}Hostname changed. The self-signed certificate needs to be regenerated.${NC}\n"
            read -p "Regenerate certificate now? (y/n): " REGEN_CERT
            if [ "$REGEN_CERT" = "y" ] || [ "$REGEN_CERT" = "Y" ]; then
                # Remove the hostname marker to force regeneration
                rm -f ./certificates/ssl/.hostname_marker

                printf "\n${YELLOW}Restarting services to regenerate certificate...${NC}\n"
                handle_restart
            else
                printf "${YELLOW}Please restart services manually to apply the new certificate.${NC}\n"
            fi
        else
            printf "\n${YELLOW}Note: You're using Let's Encrypt. Make sure the new hostname has proper DNS records.${NC}\n"
            printf "${YELLOW}You may need to reconfigure Let's Encrypt for the new hostname.${NC}\n"
        fi
    fi

    printf "\n"
    print_success_box "Hostname updated successfully to ${NEW_HOSTNAME}!"
}

# Function to handle IP logging configuration
handle_ip_logging_configuration() {
    # Get current IP logging setting
    CURRENT_SETTING=$(grep "^IP_LOGGING_ENABLED=" "$ENV_FILE" | cut -d '=' -f2)

    printf "${YELLOW}+++ Configure IP Address Logging +++${NC}\n"
    printf "\n"
    printf "Current setting: ${CYAN}${CURRENT_SETTING}${NC}\n"
    printf "\n"
    printf "1) Enable IP address logging\n"
    printf "2) Disable IP address logging\n"
    printf "\n"
    printf "Choose an option (1-2): "
    read -r choice

    case $choice in
        1)
            update_env_var "IP_LOGGING_ENABLED" "true"

            printf "\n${YELLOW}Warning: Docker containers need to be restarted to apply these changes.${NC}\n"
            read -p "Restart now? (y/n): " restart_confirm

            if [ "$restart_confirm" != "y" ] && [ "$restart_confirm" != "Y" ]; then
                printf "${YELLOW}Please restart manually to apply the changes.${NC}\n"
                exit 0
            fi

            handle_restart

            # Print success message
            printf "\n"
            print_success_box "IP address logging has been enabled!"
            ;;
        2)
            update_env_var "IP_LOGGING_ENABLED" "false"

            printf "\n${YELLOW}Warning: Docker containers need to be restarted to apply these changes.${NC}\n"
            read -p "Restart now? (y/n): " restart_confirm

            if [ "$restart_confirm" != "y" ] && [ "$restart_confirm" != "Y" ]; then
                printf "${YELLOW}Please restart manually to apply the changes.${NC}\n"
                exit 0
            fi

            handle_restart

            # Print success message
            printf "\n"
            print_success_box "IP address logging has been disabled!"
            ;;
        *)
            printf "${RED}Invalid option selected.${NC}\n"
            return 1
            ;;
    esac
}

check_and_populate_env() {
    printf "${CYAN}ℹ Checking .env values...${NC} ${GREEN}✓${NC}\n"

    # SUPPORT_EMAIL
    if ! grep -q "^SUPPORT_EMAIL=" "$ENV_FILE"; then
        read -p "Enter server admin support email address that is shown on contact page (optional, press Enter to skip): " SUPPORT_EMAIL
        update_env_var "SUPPORT_EMAIL" "$SUPPORT_EMAIL"
        printf "  Set SUPPORT_EMAIL\n"
    fi

    # JWT_KEY
    if [ ! -f "${SECRETS_DIR}/jwt_key" ] || [ -z "$(cat "${SECRETS_DIR}/jwt_key" 2>/dev/null)" ]; then
        JWT_KEY=$(openssl rand -base64 32)
        write_secret_to_file "jwt_key" "$JWT_KEY"
        printf "  Generated JWT_KEY\n"
    fi

    # DATA_PROTECTION_CERT_PASS
    if [ ! -f "${SECRETS_DIR}/data_protection_cert_pass" ] || [ -z "$(cat "${SECRETS_DIR}/data_protection_cert_pass" 2>/dev/null)" ]; then
        CERT_PASS=$(openssl rand -base64 32)
        write_secret_to_file "data_protection_cert_pass" "$CERT_PASS"
        printf "  Generated DATA_PROTECTION_CERT_PASS\n"
    fi

    # POSTGRES_PASSWORD
    if [ ! -f "${SECRETS_DIR}/postgres_password" ] || [ -z "$(cat "${SECRETS_DIR}/postgres_password" 2>/dev/null)" ]; then
        POSTGRES_PASS=$(openssl rand -base64 32)
        write_secret_to_file "postgres_password" "$POSTGRES_PASS"
        printf "  Generated POSTGRES_PASSWORD\n"
    fi

    # PRIVATE_EMAIL_DOMAINS
    if ! grep -q "^PRIVATE_EMAIL_DOMAINS=" "$ENV_FILE"; then
        update_env_var "PRIVATE_EMAIL_DOMAINS" ""
        printf "  Set PRIVATE_EMAIL_DOMAINS\n"
    fi

    # HIDDEN_PRIVATE_EMAIL_DOMAINS
    if ! grep -q "^HIDDEN_PRIVATE_EMAIL_DOMAINS=" "$ENV_FILE"; then
        update_env_var "HIDDEN_PRIVATE_EMAIL_DOMAINS" ""
        printf "  Set HIDDEN_PRIVATE_EMAIL_DOMAINS\n"
    fi

    # HTTP_PORT
    if ! grep -q "^HTTP_PORT=" "$ENV_FILE" || [ -z "$(grep "^HTTP_PORT=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
        update_env_var "HTTP_PORT" "80"
        printf "  Set HTTP_PORT\n"
    fi

    # HTTPS_PORT
    if ! grep -q "^HTTPS_PORT=" "$ENV_FILE" || [ -z "$(grep "^HTTPS_PORT=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
        update_env_var "HTTPS_PORT" "443"
        printf "  Set HTTPS_PORT\n"
    fi

    # SMTP_PORT
    if ! grep -q "^SMTP_PORT=" "$ENV_FILE" || [ -z "$(grep "^SMTP_PORT=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
        update_env_var "SMTP_PORT" "25"
        printf "  Set SMTP_PORT\n"
    fi

    # SMTP_TLS_PORT
    if ! grep -q "^SMTP_TLS_PORT=" "$ENV_FILE" || [ -z "$(grep "^SMTP_TLS_PORT=" "$ENV_FILE" | cut -d '=' -f2)" ]; then
        update_env_var "SMTP_TLS_PORT" "587"
        printf "  Set SMTP_TLS_PORT\n"
    fi
}

main "$@"
