#!/bin/bash
# Mosaic Stack Setup Wizard
# Interactive installer for Mosaic Stack personal assistant platform
# Supports Docker and native deployments

set -e

# ============================================================================
# Configuration
# ============================================================================

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

# Set up logging
LOG_DIR="$PROJECT_ROOT/logs"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/setup-$(date +%Y%m%d_%H%M%S).log"

# Redirect stdout/stderr to both console and log file
exec > >(tee -a "$LOG_FILE") 2>&1

# Send trace output (set -x) ONLY to log file on fd 3
exec 3>>"$LOG_FILE"
export BASH_XTRACEFD=3

# Enable verbose command tracing (only goes to log file now)
set -x

echo "==================================================================="
echo "Mosaic Stack Setup Wizard"
echo "Started: $(date)"
echo "Full log: $LOG_FILE"
echo "==================================================================="
echo ""

# Source common functions
# shellcheck source=lib/common.sh
source "$SCRIPT_DIR/lib/common.sh"

# ============================================================================
# Global Variables
# ============================================================================

NON_INTERACTIVE=false
DRY_RUN=false
MODE=""
ENABLE_SSO=false
USE_BUNDLED_AUTHENTIK=false
EXTERNAL_AUTHENTIK_URL=""
OLLAMA_MODE="disabled"
OLLAMA_URL=""
ENABLE_MOLTBOT=false
MOSAIC_BASE_URL=""
API_BASE_URL=""
AUTHENTIK_PUBLIC_URL=""

BASE_URL_MODE=""
BASE_URL_SCHEME="http"
BASE_URL_HOST=""
BASE_URL_PORT=""

TRAEFIK_MODE="none"
TRAEFIK_ENABLE=false
TRAEFIK_TLS_ENABLED=true
TRAEFIK_ENTRYPOINT="websecure"
TRAEFIK_NETWORK=""
TRAEFIK_DOCKER_NETWORK="mosaic-public"
TRAEFIK_ACME_EMAIL=""
TRAEFIK_CERTRESOLVER=""
TRAEFIK_DASHBOARD_ENABLED=true
TRAEFIK_DASHBOARD_PORT=""

MOSAIC_WEB_DOMAIN=""
MOSAIC_API_DOMAIN=""
MOSAIC_AUTH_DOMAIN=""

WEB_PORT=""
API_PORT=""
POSTGRES_PORT=""
VALKEY_PORT=""
AUTHENTIK_PORT_HTTP=""
AUTHENTIK_PORT_HTTPS=""
OLLAMA_PORT=""
TRAEFIK_HTTP_PORT=""
TRAEFIK_HTTPS_PORT=""

DETECTED_OS=""
DETECTED_PKG_MANAGER=""
PORT_OVERRIDES=()

# ============================================================================
# Taglines (OpenClaw-inspired flavor)
# ============================================================================

TAGLINES=(
    "Claws out, configs in — let's ship a calm, clean stack."
    "Less yak-shaving, more uptime."
    "Turnkey today, productive tonight."
    "Ports resolved. Secrets sealed. Stack ready."
    "All signal, no ceremony."
    "Your .env is safe with me."
)

pick_tagline() {
    local idx=$((RANDOM % ${#TAGLINES[@]}))
    echo "${TAGLINES[$idx]}"
}

# ============================================================================
# Help and Usage
# ============================================================================

show_help() {
    cat << EOF
Mosaic Stack Setup Wizard

USAGE:
    $0 [OPTIONS]

OPTIONS:
    -h, --help                  Show this help message
    --non-interactive           Run in non-interactive mode (requires all options)
    --dry-run                   Show what would happen without executing
    --mode MODE                 Deployment mode: docker or native
    --enable-sso                Enable Authentik SSO (Docker mode only)
    --bundled-authentik         Use bundled Authentik server (with --enable-sso)
    --external-authentik URL    Use external Authentik server URL (with --enable-sso)
    --ollama-mode MODE          Ollama mode: local, remote, disabled (default: disabled)
    --ollama-url URL            Ollama server URL (if --ollama-mode remote)
    --enable-moltbot            Enable MoltBot integration
    --base-url URL              Mosaic base URL (e.g., https://mosaic.example.com)

EXAMPLES:
    # Interactive mode (recommended for first-time setup)
    $0

    # Non-interactive Docker deployment with bundled SSO
    $0 --non-interactive --mode docker --enable-sso --bundled-authentik

    # Non-interactive Docker with external Authentik and local Ollama
    $0 --non-interactive --mode docker --enable-sso --external-authentik "https://auth.example.com" --ollama-mode local

    # Dry run to see what would happen
    $0 --dry-run --mode docker --enable-sso --bundled-authentik

EOF
}

# ============================================================================
# Argument Parsing
# ============================================================================

parse_arguments() {
    while [[ $# -gt 0 ]]; do
        case $1 in
            -h|--help)
                show_help
                exit 0
                ;;
            --non-interactive)
                NON_INTERACTIVE=true
                ;;
            --dry-run)
                DRY_RUN=true
                ;;
            --mode)
                if [[ -z "${2:-}" || "$2" == --* ]]; then
                    print_error "--mode requires a value (docker or native)"
                    exit 1
                fi
                MODE="$2"
                shift
                ;;
            --enable-sso)
                ENABLE_SSO=true
                ;;
            --bundled-authentik)
                USE_BUNDLED_AUTHENTIK=true
                ;;
            --external-authentik)
                if [[ -z "${2:-}" || "$2" == --* ]]; then
                    print_error "--external-authentik requires a URL"
                    exit 1
                fi
                EXTERNAL_AUTHENTIK_URL="$2"
                shift
                ;;
            --ollama-mode)
                if [[ -z "${2:-}" || "$2" == --* ]]; then
                    print_error "--ollama-mode requires a value (local, remote, or disabled)"
                    exit 1
                fi
                OLLAMA_MODE="$2"
                shift
                ;;
            --ollama-url)
                if [[ -z "${2:-}" || "$2" == --* ]]; then
                    print_error "--ollama-url requires a URL"
                    exit 1
                fi
                OLLAMA_URL="$2"
                shift
                ;;
            --enable-moltbot)
                ENABLE_MOLTBOT=true
                ;;
            --base-url)
                if [[ -z "${2:-}" || "$2" == --* ]]; then
                    print_error "--base-url requires a URL"
                    exit 1
                fi
                MOSAIC_BASE_URL="$2"
                shift
                ;;
            *)
                print_error "Unknown option: $1"
                echo "Use --help for usage information"
                exit 1
                ;;
        esac
        shift
    done

    # Validate non-interactive mode
    if [[ "$NON_INTERACTIVE" == true ]]; then
        if [[ -z "$MODE" ]]; then
            print_error "Non-interactive mode requires --mode"
            exit 1
        fi

        if [[ "$MODE" != "native" && "$MODE" != "docker" ]]; then
            print_error "Invalid mode: $MODE (must be 'native' or 'docker')"
            exit 1
        fi

        if [[ "$OLLAMA_MODE" == "remote" && -z "$OLLAMA_URL" ]]; then
            print_error "Remote Ollama mode requires --ollama-url"
            exit 1
        fi

        if [[ "$ENABLE_SSO" == true && "$USE_BUNDLED_AUTHENTIK" != true && -z "$EXTERNAL_AUTHENTIK_URL" ]]; then
            print_error "SSO enabled in non-interactive mode requires --bundled-authentik or --external-authentik URL"
            exit 1
        fi
    fi
}

# ============================================================================
# Welcome Banner
# ============================================================================

show_banner() {
    if [[ "$NON_INTERACTIVE" == true ]]; then
        return
    fi

    cat << "EOF"

  __  __                 _       ____ _             _
 |  \/  | ___  ___  __ _(_) ___ / ___| |_ __ _  ___| | __
 | |\/| |/ _ \/ __|/ _` | |/ __|\___ | __/ _` |/ __| |/ /
 | |  | | (_) \__ \ (_| | | (__ ___/ | || (_| | (__|   <
 |_|  |_|\___/|___/\__,_|_|\___|____/ \__\__,_|\___|_|\_\

  Multi-Tenant Personal Assistant Platform

EOF
    local tagline
    tagline=$(pick_tagline)
    echo "  $tagline"
    echo ""

    echo "Welcome to the Mosaic Stack Setup Wizard!"
    echo ""
    echo "This wizard will guide you through setting up Mosaic Stack on your system."
    echo "You can choose between Docker (production) or native (development) deployment,"
    echo "and configure optional features like Authentik SSO and Ollama LLM integration."
    echo ""
}

# ============================================================================
# Platform Detection
# ============================================================================

detect_platform() {
    print_header "Detecting Platform"

    DETECTED_OS=$(detect_os)
    DETECTED_PKG_MANAGER=$(detect_package_manager "$DETECTED_OS")

    local os_name
    os_name=$(get_os_name "$DETECTED_OS")

    if [[ "$DETECTED_OS" == "unknown" ]]; then
        print_warning "Could not detect operating system"
        print_info "Detected OS type: $OSTYPE"
        echo ""
        if [[ "$NON_INTERACTIVE" == true ]]; then
            print_error "Cannot proceed in non-interactive mode on unknown OS"
            exit 1
        fi
        if ! confirm "Continue anyway?"; then
            exit 1
        fi
    else
        print_success "Detected: $os_name ($DETECTED_PKG_MANAGER)"
    fi
}

# ============================================================================
# Mode Selection
# ============================================================================

select_deployment_mode() {
    if [[ -n "$MODE" ]]; then
        print_header "Deployment Mode"
        print_info "Using mode: $MODE"
        return
    fi

    print_header "Deployment Mode"
    echo ""
    echo "How would you like to run Mosaic Stack?"
    echo ""
    echo "  1) Docker (Recommended)"
    echo "     - Best for production deployment"
    echo "     - Isolated environment with all dependencies"
    echo "     - Includes PostgreSQL, Valkey, all services"
    echo "     - Optional Authentik SSO integration"
    echo "     - Turnkey deployment"
    echo ""
    echo "  2) Native"
    echo "     - Best for development"
    echo "     - Runs directly on your system"
    echo "     - Requires manual dependency installation"
    echo "     - Easier to debug and modify"
    echo ""

    local selection
    selection=$(select_option "Select deployment mode:" \
        "Docker (production, recommended)" \
        "Native (development)")

    if [[ "$selection" == *"Docker"* ]]; then
        MODE="docker"
    else
        MODE="native"
    fi

    print_success "Selected: $MODE mode"
}

# ============================================================================
# Dependency Checking
# ============================================================================

check_and_install_dependencies() {
    print_header "Checking Dependencies"

    local missing_deps=()
    local docker_status=0

    if [[ "$MODE" == "docker" ]]; then
        check_docker
        docker_status=$?

        if [[ $docker_status -eq 1 ]]; then
            # Docker not installed
            missing_deps+=("Docker")
        elif [[ $docker_status -ne 0 ]]; then
            # Docker installed but not accessible (permission/daemon issue)
            echo ""
            print_error "Docker is installed but not accessible"
            print_info "Please fix the issue above and run the setup script again"
            exit 1
        else
            print_success "Docker: OK"
        fi

        if ! check_docker_compose; then
            missing_deps+=("Docker Compose")
        else
            print_success "Docker Compose: OK"
        fi
    else
        if ! check_node 18; then
            missing_deps+=("Node.js 18+")
        else
            print_success "Node.js: OK"
        fi
        if ! check_pnpm; then
            missing_deps+=("pnpm")
        else
            print_success "pnpm: OK"
        fi
        if ! check_postgres; then
            missing_deps+=("PostgreSQL")
        else
            print_success "PostgreSQL: OK"
        fi
    fi

    if [[ ${#missing_deps[@]} -eq 0 ]]; then
        echo ""
        print_success "All dependencies satisfied"
        return 0
    fi

    echo ""
    print_warning "Missing dependencies:"
    for dep in "${missing_deps[@]}"; do
        echo "  - $dep"
    done
    echo ""

    if [[ "$NON_INTERACTIVE" == true ]]; then
        print_error "Dependency check failed in non-interactive mode"
        exit 1
    fi

    if confirm "Would you like to install missing dependencies?"; then
        install_missing_dependencies
    else
        print_error "Cannot proceed without required dependencies"
        exit 1
    fi
}

install_missing_dependencies() {
    print_header "Installing Dependencies"

    check_sudo

    if [[ "$MODE" == "docker" ]]; then
        # Install Docker
        if ! check_docker; then
            local docker_pkg
            docker_pkg=$(get_package_name "$DETECTED_PKG_MANAGER" "docker")
            if [[ -n "$docker_pkg" ]]; then
                install_package "$DETECTED_PKG_MANAGER" "$docker_pkg"

                # Start Docker service
                case "$DETECTED_PKG_MANAGER" in
                    pacman|dnf)
                        print_step "Starting Docker service..."
                        sudo systemctl enable --now docker
                        ;;
                esac

                # Add user to docker group
                print_step "Adding user to docker group..."
                sudo usermod -aG docker "$USER"
                print_warning "You may need to log out and back in for docker group membership to take effect"
                print_info "Or run: newgrp docker"
            fi
        fi

        # Install Docker Compose
        if ! check_docker_compose; then
            local compose_pkg
            compose_pkg=$(get_package_name "$DETECTED_PKG_MANAGER" "docker-compose")
            if [[ -n "$compose_pkg" ]]; then
                install_package "$DETECTED_PKG_MANAGER" "$compose_pkg"
            fi
        fi
    else
        # Install Node.js
        if ! check_node 18; then
            local node_pkg
            node_pkg=$(get_package_name "$DETECTED_PKG_MANAGER" "node")
            if [[ -n "$node_pkg" ]]; then
                install_package "$DETECTED_PKG_MANAGER" "$node_pkg"
            fi
        fi

        # Install pnpm
        if ! check_pnpm; then
            print_step "Installing pnpm via npm..."
            npm install -g pnpm
        fi

        # Install PostgreSQL
        if ! check_postgres; then
            local postgres_pkg
            postgres_pkg=$(get_package_name "$DETECTED_PKG_MANAGER" "postgres")
            if [[ -n "$postgres_pkg" ]]; then
                install_package "$DETECTED_PKG_MANAGER" "$postgres_pkg"
            fi
        fi
    fi

    # Re-check dependencies
    echo ""
    local still_missing=false
    if [[ "$MODE" == "docker" ]]; then
        if ! check_docker || ! check_docker_compose; then
            still_missing=true
        fi
    else
        if ! check_node 18 || ! check_pnpm; then
            still_missing=true
        fi
    fi

    if [[ "$still_missing" == true ]]; then
        print_error "Some dependencies are still missing after installation"
        print_info "You may need to install them manually"
        exit 1
    else
        print_success "All dependencies installed successfully"
    fi
}

# ============================================================================
# Configuration Collection
# ============================================================================

load_existing_env() {
    if [[ -f "$PROJECT_ROOT/.env" ]]; then
        print_header "Detecting Existing Configuration"
        parse_env_file "$PROJECT_ROOT/.env"
        print_success "Found existing .env file"
        return 0
    fi
    return 1
}

# ============================================================================
# URL and Port Helpers
# ============================================================================

strip_trailing_slash() {
    local value="$1"
    echo "${value%/}"
}

format_url() {
    local scheme="$1"
    local host="$2"
    local port="$3"
    local always_port="${4:-false}"

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

    if [[ -z "$port" ]]; then
        echo "${scheme}://${host}"
        return
    fi

    if [[ "$always_port" == true ]]; then
        echo "${scheme}://${host}:${port}"
        return
    fi

    if [[ "$scheme" == "http" && "$port" == "80" ]] || [[ "$scheme" == "https" && "$port" == "443" ]]; then
        echo "${scheme}://${host}"
    else
        echo "${scheme}://${host}:${port}"
    fi
}

bool_str() {
    if [[ "$1" == true ]]; then
        echo "true"
    else
        echo "false"
    fi
}

resolve_env_value() {
    local key="$1"
    local fallback="$2"
    local value
    value=$(get_env_value "$key")
    if [[ -n "$value" ]] && ! is_placeholder "$value"; then
        echo "$value"
    else
        echo "$fallback"
    fi
}

resolve_secret_value() {
    local key="$1"
    local generator="$2"
    local length="$3"
    local value
    value=$(get_env_value "$key")
    if [[ -n "$value" ]] && ! is_placeholder "$value"; then
        echo "$value"
    else
        "$generator" "$length"
    fi
}

derive_cookie_domain() {
    local domain="$1"
    if [[ "$domain" == *.* ]]; then
        echo ".${domain#*.}"
    else
        echo ".${domain}"
    fi
}

resolve_port_value() {
    local key="$1"
    local fallback="$2"
    local value
    value=$(get_env_value "$key")
    if validate_port "$value"; then
        echo "$value"
    else
        echo "$fallback"
    fi
}

initialize_ports() {
    WEB_PORT=$(resolve_port_value "WEB_PORT" "3000")
    API_PORT=$(resolve_port_value "API_PORT" "3001")
    POSTGRES_PORT=$(resolve_port_value "POSTGRES_PORT" "5432")
    VALKEY_PORT=$(resolve_port_value "VALKEY_PORT" "6379")
    AUTHENTIK_PORT_HTTP=$(resolve_port_value "AUTHENTIK_PORT_HTTP" "9000")
    AUTHENTIK_PORT_HTTPS=$(resolve_port_value "AUTHENTIK_PORT_HTTPS" "9443")
    OLLAMA_PORT=$(resolve_port_value "OLLAMA_PORT" "11434")
    TRAEFIK_HTTP_PORT=$(resolve_port_value "TRAEFIK_HTTP_PORT" "80")
    TRAEFIK_HTTPS_PORT=$(resolve_port_value "TRAEFIK_HTTPS_PORT" "443")
    TRAEFIK_DASHBOARD_PORT=$(resolve_port_value "TRAEFIK_DASHBOARD_PORT" "8080")
}

parse_base_url() {
    local url="$1"

    if [[ "$url" =~ ^(https?)://([^/:]+)(:([0-9]+))?(/.*)?$ ]]; then
        BASE_URL_SCHEME="${BASH_REMATCH[1]}"
        BASE_URL_HOST="${BASH_REMATCH[2]}"
        BASE_URL_PORT="${BASH_REMATCH[4]}"
        return 0
    fi

    return 1
}

recalculate_urls() {
    if [[ "$BASE_URL_MODE" == "traefik" ]]; then
        local traefik_port=""
        if [[ "$TRAEFIK_MODE" == "bundled" ]]; then
            if [[ "$BASE_URL_SCHEME" == "https" ]]; then
                traefik_port="$TRAEFIK_HTTPS_PORT"
            else
                traefik_port="$TRAEFIK_HTTP_PORT"
            fi
        fi

        MOSAIC_BASE_URL=$(format_url "$BASE_URL_SCHEME" "$MOSAIC_WEB_DOMAIN" "$traefik_port")
        API_BASE_URL=$(format_url "$BASE_URL_SCHEME" "$MOSAIC_API_DOMAIN" "$traefik_port")
    else
        local always_port=false
        if [[ "$BASE_URL_MODE" == "localhost" || "$BASE_URL_MODE" == "ip" ]]; then
            always_port=true
        fi
        MOSAIC_BASE_URL=$(format_url "$BASE_URL_SCHEME" "$BASE_URL_HOST" "$WEB_PORT" "$always_port")
        API_BASE_URL=$(format_url "$BASE_URL_SCHEME" "$BASE_URL_HOST" "$API_PORT" true)
    fi

    if [[ "$ENABLE_SSO" == true ]]; then
        if [[ "$USE_BUNDLED_AUTHENTIK" == true ]]; then
            if [[ "$BASE_URL_MODE" == "traefik" && -n "$MOSAIC_AUTH_DOMAIN" ]]; then
                local auth_port=""
                if [[ "$TRAEFIK_MODE" == "bundled" ]]; then
                    if [[ "$BASE_URL_SCHEME" == "https" ]]; then
                        auth_port="$TRAEFIK_HTTPS_PORT"
                    else
                        auth_port="$TRAEFIK_HTTP_PORT"
                    fi
                fi
                AUTHENTIK_PUBLIC_URL=$(format_url "$BASE_URL_SCHEME" "$MOSAIC_AUTH_DOMAIN" "$auth_port")
            else
                AUTHENTIK_PUBLIC_URL="http://localhost:${AUTHENTIK_PORT_HTTP}"
            fi
        else
            AUTHENTIK_PUBLIC_URL="$EXTERNAL_AUTHENTIK_URL"
        fi
    fi
}

# Detect access mode from existing configuration
detect_access_mode() {
    local base_url="$1"
    local traefik_mode
    traefik_mode=$(get_env_value "TRAEFIK_MODE")

    if [[ "$traefik_mode" == "bundled" || "$traefik_mode" == "upstream" ]]; then
        echo "traefik"
    elif [[ "$base_url" == *"localhost"* ]]; then
        echo "localhost"
    elif [[ "$base_url" =~ ^https?://[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+ ]]; then
        echo "ip"
    else
        echo "domain"
    fi
}

# Display existing access configuration
display_access_config() {
    local web_url="$1"
    local api_url="$2"
    local auth_url="$3"
    local mode="$4"

    local mode_label
    case "$mode" in
        localhost) mode_label="Localhost development" ;;
        ip)        mode_label="Local network IP" ;;
        domain)    mode_label="Custom domain" ;;
        traefik)   mode_label="Traefik reverse proxy" ;;
        *)         mode_label="Custom" ;;
    esac

    echo ""
    echo "  Mode:    $mode_label"
    echo ""
    echo "  Web URL: $web_url"
    [[ -n "$api_url" ]] && echo "  API URL: $api_url"
    [[ -n "$auth_url" ]] && echo "  Auth:    $auth_url"
    echo ""
}

port_label() {
    local key="$1"
    case "$key" in
        WEB_PORT) echo "Web UI" ;;
        API_PORT) echo "API" ;;
        POSTGRES_PORT) echo "PostgreSQL" ;;
        VALKEY_PORT) echo "Valkey" ;;
        AUTHENTIK_PORT_HTTP) echo "Authentik HTTP" ;;
        AUTHENTIK_PORT_HTTPS) echo "Authentik HTTPS" ;;
        OLLAMA_PORT) echo "Ollama" ;;
        TRAEFIK_HTTP_PORT) echo "Traefik HTTP" ;;
        TRAEFIK_HTTPS_PORT) echo "Traefik HTTPS" ;;
        TRAEFIK_DASHBOARD_PORT) echo "Traefik Dashboard" ;;
        *) echo "$key" ;;
    esac
}

suggest_port_for_key() {
    local key="$1"
    local port="$2"

    case "$key" in
        TRAEFIK_HTTP_PORT)
            if ! check_port_in_use 8080; then
                echo "8080"
                return 0
            fi
            ;;
        TRAEFIK_HTTPS_PORT)
            if ! check_port_in_use 8443; then
                echo "8443"
                return 0
            fi
            ;;
    esac

    suggest_alternative_port "$port"
}

apply_port_override() {
    local key="$1"
    local value="$2"

    case "$key" in
        WEB_PORT) WEB_PORT="$value" ;;
        API_PORT) API_PORT="$value" ;;
        POSTGRES_PORT) POSTGRES_PORT="$value" ;;
        VALKEY_PORT) VALKEY_PORT="$value" ;;
        AUTHENTIK_PORT_HTTP) AUTHENTIK_PORT_HTTP="$value" ;;
        AUTHENTIK_PORT_HTTPS) AUTHENTIK_PORT_HTTPS="$value" ;;
        OLLAMA_PORT) OLLAMA_PORT="$value" ;;
        TRAEFIK_HTTP_PORT) TRAEFIK_HTTP_PORT="$value" ;;
        TRAEFIK_HTTPS_PORT) TRAEFIK_HTTPS_PORT="$value" ;;
        TRAEFIK_DASHBOARD_PORT) TRAEFIK_DASHBOARD_PORT="$value" ;;
    esac

    set_env_value "$key" "$value"
}

apply_port_overrides() {
    if [[ ${#PORT_OVERRIDES[@]} -eq 0 ]]; then
        return
    fi

    for override in "${PORT_OVERRIDES[@]}"; do
        local key="${override%%=*}"
        local value="${override#*=}"
        apply_port_override "$key" "$value"
    done

    recalculate_urls
}

resolve_port_conflicts() {
    if [[ "$MODE" != "docker" ]]; then
        return
    fi

    print_header "Port Check"

    local port_entries=()
    port_entries+=("WEB_PORT=$WEB_PORT")
    port_entries+=("API_PORT=$API_PORT")
    port_entries+=("POSTGRES_PORT=$POSTGRES_PORT")
    port_entries+=("VALKEY_PORT=$VALKEY_PORT")

    if [[ "$ENABLE_SSO" == true && "$USE_BUNDLED_AUTHENTIK" == true ]]; then
        port_entries+=("AUTHENTIK_PORT_HTTP=$AUTHENTIK_PORT_HTTP")
        port_entries+=("AUTHENTIK_PORT_HTTPS=$AUTHENTIK_PORT_HTTPS")
    fi

    if [[ "$OLLAMA_MODE" == "local" ]]; then
        port_entries+=("OLLAMA_PORT=$OLLAMA_PORT")
    fi

    if [[ "$TRAEFIK_MODE" == "bundled" ]]; then
        port_entries+=("TRAEFIK_HTTP_PORT=$TRAEFIK_HTTP_PORT")
        port_entries+=("TRAEFIK_HTTPS_PORT=$TRAEFIK_HTTPS_PORT")
        if [[ "$TRAEFIK_DASHBOARD_ENABLED" == true ]]; then
            port_entries+=("TRAEFIK_DASHBOARD_PORT=$TRAEFIK_DASHBOARD_PORT")
        fi
    fi

    local conflicts=()
    local suggestions=()

    for entry in "${port_entries[@]}"; do
        local key="${entry%%=*}"
        local port="${entry#*=}"
        if check_port_in_use "$port"; then
            local suggested
            suggested=$(suggest_port_for_key "$key" "$port")
            conflicts+=("${key}=${port}")
            suggestions+=("${key}=${suggested}")
        fi
    done

    if [[ ${#conflicts[@]} -eq 0 ]]; then
        print_success "No port conflicts detected"
        return
    fi

    echo ""
    print_warning "Port conflicts detected:"
    for conflict in "${conflicts[@]}"; do
        local key="${conflict%%=*}"
        local port="${conflict#*=}"
        echo "  - $(port_label "$key"): $port"
    done
    echo ""
    print_info "Suggested alternatives:"
    for suggestion in "${suggestions[@]}"; do
        local key="${suggestion%%=*}"
        local port="${suggestion#*=}"
        echo "  - $(port_label "$key"): $port"
    done
    echo ""

    if [[ "$NON_INTERACTIVE" == true ]]; then
        PORT_OVERRIDES=("${suggestions[@]}")
        print_success "Applying alternative ports automatically (non-interactive)"
        apply_port_overrides
        return
    fi

    if confirm "Apply suggested ports automatically?" "y"; then
        PORT_OVERRIDES=("${suggestions[@]}")
        apply_port_overrides
        print_success "Alternative ports applied"
    else
        print_error "Port conflicts must be resolved to continue"
        exit 1
    fi
}

collect_configuration() {
    print_header "Configuration"

    # Try to load existing .env
    local has_existing_env=false
    if load_existing_env; then
        has_existing_env=true
        echo ""
        print_info "Found existing configuration. I'll ask about each setting."
        echo ""
    fi

    initialize_ports

    # Base URL Configuration
    configure_base_url

    # SSO Configuration (Docker mode only)
    if [[ "$MODE" == "docker" ]]; then
        configure_sso
    fi

    # Ollama Configuration
    configure_ollama

    # MoltBot Configuration
    configure_moltbot

    # Generate secrets if needed
    configure_secrets
}

configure_base_url() {
    echo ""
    print_step "Base URL Configuration"
    echo ""

    local current_url
    current_url=$(get_env_value "MOSAIC_BASE_URL")

    if [[ -n "$current_url" ]] && ! is_placeholder "$current_url"; then
        # Show detailed breakdown of existing configuration
        local current_api_url current_auth_url detected_mode
        current_api_url=$(get_env_value "NEXT_PUBLIC_API_URL")
        [[ -z "$current_api_url" ]] && current_api_url=$(get_env_value "API_BASE_URL")
        current_auth_url=$(get_env_value "AUTHENTIK_PUBLIC_URL")
        detected_mode=$(detect_access_mode "$current_url")

        display_access_config "$current_url" "$current_api_url" "$current_auth_url" "$detected_mode"

        if [[ "$NON_INTERACTIVE" == true ]] || ! confirm "Change access configuration?" "n"; then
            if parse_base_url "$current_url"; then
                BASE_URL_MODE="custom"
                if [[ -n "$BASE_URL_PORT" ]]; then
                    WEB_PORT="$BASE_URL_PORT"
                else
                    if [[ "$BASE_URL_SCHEME" == "https" ]]; then
                        WEB_PORT="443"
                    else
                        WEB_PORT="80"
                    fi
                fi
                recalculate_urls
                return
            fi
        fi
    fi

    if [[ -n "$MOSAIC_BASE_URL" ]]; then
        # Already set via CLI argument
        print_info "Using base URL from command line: $MOSAIC_BASE_URL"
        if ! parse_base_url "$MOSAIC_BASE_URL"; then
            print_error "Invalid base URL format. Expected: http(s)://host[:port]"
            exit 1
        fi
        BASE_URL_MODE="custom"
        if [[ -n "$BASE_URL_PORT" ]]; then
            WEB_PORT="$BASE_URL_PORT"
        else
            if [[ "$BASE_URL_SCHEME" == "https" ]]; then
                WEB_PORT="443"
            else
                WEB_PORT="80"
            fi
        fi
        recalculate_urls
        return
    fi

    if [[ "$NON_INTERACTIVE" == true ]]; then
        BASE_URL_MODE="localhost"
        BASE_URL_SCHEME="http"
        BASE_URL_HOST="localhost"
        BASE_URL_PORT="$WEB_PORT"
        recalculate_urls
        print_info "Non-interactive mode: using $MOSAIC_BASE_URL"
        return
    fi

    echo "How will Mosaic Stack be accessed?"
    echo ""

    local options=(
        "Localhost (recommended for local install)"
        "Local network IP"
        "Custom domain (direct ports)"
    )

    if [[ "$MODE" == "docker" ]]; then
        options+=("Traefik reverse proxy (bundled or existing)")
    fi

    local selection
    selection=$(select_option "Select access method:" "${options[@]}")

    case "$selection" in
        *"Localhost"*)
            configure_localhost_url
            ;;
        *"Local network IP"*)
            configure_ip_url
            ;;
        *"Custom domain"*)
            configure_dns_url
            ;;
        *"Traefik"*)
            configure_traefik_url
            ;;
    esac
}

configure_dns_url() {
    echo ""
    BASE_URL_MODE="domain"

    local domain
    while true; do
        read -r -p "Enter your domain (e.g., mosaic.example.com): " domain
        if validate_domain "$domain"; then
            break
        else
            print_error "Invalid domain format"
        fi
    done

    # Ask about SSL
    local use_https=true
    if ! confirm "Use HTTPS?" "y"; then
        use_https=false
    fi

    if [[ "$use_https" == true ]]; then
        BASE_URL_SCHEME="https"
        print_warning "Consider using a reverse proxy for TLS certificates"
    else
        BASE_URL_SCHEME="http"
    fi

    BASE_URL_HOST="$domain"

    local default_port="$WEB_PORT"
    local port
    while true; do
        read -r -p "Enter web port [$default_port]: " port
        port=${port:-$default_port}
        if validate_port "$port"; then
            break
        else
            print_error "Invalid port number"
        fi
    done

    WEB_PORT="$port"
    BASE_URL_PORT="$WEB_PORT"
    recalculate_urls
    print_success "Base URL set to: $MOSAIC_BASE_URL"
}

configure_localhost_url() {
    echo ""
    BASE_URL_MODE="localhost"
    BASE_URL_SCHEME="http"
    BASE_URL_HOST="localhost"

    local default_port="$WEB_PORT"

    local port
    while true; do
        read -r -p "Enter web port [$default_port]: " port
        port=${port:-$default_port}
        if validate_port "$port"; then
            break
        else
            print_error "Invalid port number"
        fi
    done

    WEB_PORT="$port"
    BASE_URL_PORT="$WEB_PORT"
    recalculate_urls
    print_success "Base URL set to: $MOSAIC_BASE_URL"
}

configure_ip_url() {
    echo ""
    BASE_URL_MODE="ip"
    BASE_URL_SCHEME="http"

    # Try to detect local IP
    local detected_ip
    if command -v ip >/dev/null 2>&1; then
        detected_ip=$(ip route get 1 2>/dev/null | awk '{print $7; exit}')
    elif command -v ifconfig >/dev/null 2>&1; then
        detected_ip=$(ifconfig | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1' | head -n1)
    fi

    if [[ -n "$detected_ip" ]]; then
        print_info "Detected IP: $detected_ip"
    fi

    local ip_addr
    while true; do
        read -r -p "Enter IP address${detected_ip:+ [$detected_ip]}: " ip_addr
        ip_addr=${ip_addr:-$detected_ip}
        if validate_ipv4 "$ip_addr"; then
            break
        else
            print_error "Invalid IP address (must be 0.0.0.0 - 255.255.255.255)"
        fi
    done

    BASE_URL_HOST="$ip_addr"

    local default_port="$WEB_PORT"
    local port
    while true; do
        read -r -p "Enter web port [$default_port]: " port
        port=${port:-$default_port}
        if validate_port "$port"; then
            break
        else
            print_error "Invalid port number"
        fi
    done

    WEB_PORT="$port"
    BASE_URL_PORT="$WEB_PORT"
    recalculate_urls
    print_success "Base URL set to: $MOSAIC_BASE_URL"
}

configure_traefik_url() {
    echo ""
    print_info "Configuring Traefik integration"
    echo ""

    local mode_selection
    mode_selection=$(select_option "Traefik mode:" \
        "Bundled Traefik (run inside this stack)" \
        "Upstream Traefik (existing instance)")

    if [[ "$mode_selection" == *"Bundled"* ]]; then
        TRAEFIK_MODE="bundled"
        TRAEFIK_ENABLE=true
    else
        TRAEFIK_MODE="upstream"
        TRAEFIK_ENABLE=true
    fi

    if confirm "Enable TLS/HTTPS for Traefik?" "y"; then
        TRAEFIK_TLS_ENABLED=true
        TRAEFIK_ENTRYPOINT="websecure"
        BASE_URL_SCHEME="https"
    else
        TRAEFIK_TLS_ENABLED=false
        TRAEFIK_ENTRYPOINT="web"
        BASE_URL_SCHEME="http"
    fi

    BASE_URL_MODE="traefik"

    local default_web_domain="mosaic.local"
    while true; do
        read -r -p "Web domain [$default_web_domain]: " MOSAIC_WEB_DOMAIN
        MOSAIC_WEB_DOMAIN=${MOSAIC_WEB_DOMAIN:-$default_web_domain}
        if validate_domain "$MOSAIC_WEB_DOMAIN"; then
            break
        fi
        print_error "Invalid domain format"
    done

    local default_api_domain="api.${MOSAIC_WEB_DOMAIN}"
    while true; do
        read -r -p "API domain [$default_api_domain]: " MOSAIC_API_DOMAIN
        MOSAIC_API_DOMAIN=${MOSAIC_API_DOMAIN:-$default_api_domain}
        if validate_domain "$MOSAIC_API_DOMAIN"; then
            break
        fi
        print_error "Invalid domain format"
    done

    if [[ "$ENABLE_SSO" == true ]]; then
        local default_auth_domain="auth.${MOSAIC_WEB_DOMAIN}"
        while true; do
            read -r -p "Auth domain [$default_auth_domain]: " MOSAIC_AUTH_DOMAIN
            MOSAIC_AUTH_DOMAIN=${MOSAIC_AUTH_DOMAIN:-$default_auth_domain}
            if validate_domain "$MOSAIC_AUTH_DOMAIN"; then
                break
            fi
            print_error "Invalid domain format"
        done
    fi

    if [[ "$TRAEFIK_MODE" == "upstream" ]]; then
        local default_network="traefik-public"
        read -r -p "External Traefik network name [$default_network]: " TRAEFIK_NETWORK
        TRAEFIK_NETWORK=${TRAEFIK_NETWORK:-$default_network}
        TRAEFIK_DOCKER_NETWORK="$TRAEFIK_NETWORK"
    fi

    if [[ "$TRAEFIK_MODE" == "bundled" ]]; then
        if confirm "Enable Traefik dashboard?" "y"; then
            TRAEFIK_DASHBOARD_ENABLED=true
            local default_dash_port="$TRAEFIK_DASHBOARD_PORT"
            local dash_port
            while true; do
                read -r -p "Traefik dashboard port [$default_dash_port]: " dash_port
                dash_port=${dash_port:-$default_dash_port}
                if validate_port "$dash_port"; then
                    break
                else
                    print_error "Invalid port number"
                fi
            done
            TRAEFIK_DASHBOARD_PORT="$dash_port"
        else
            TRAEFIK_DASHBOARD_ENABLED=false
        fi
    fi

    recalculate_urls
    print_success "Base URL set to: $MOSAIC_BASE_URL"
    print_info "Traefik domains configured for web and API"
}

configure_sso() {
    echo ""
    print_step "SSO Configuration (Authentik)"
    echo ""

    # Check if SSO already configured
    local current_sso_enabled
    current_sso_enabled=$(get_env_value "ENABLE_SSO")

    if [[ -n "$ENABLE_SSO" ]]; then
        # Already set via CLI
        print_info "SSO enabled: $ENABLE_SSO"
    elif [[ "$current_sso_enabled" == "true" ]]; then
        echo "SSO is currently enabled"
        if [[ "$NON_INTERACTIVE" == false ]] && ! confirm "Keep SSO enabled?" "y"; then
            ENABLE_SSO=false
            return
        fi
        ENABLE_SSO=true
    else
        if [[ "$NON_INTERACTIVE" == true ]]; then
            ENABLE_SSO=false
            return
        fi
        if ! confirm "Enable Authentik SSO?"; then
            ENABLE_SSO=false
            return
        fi
        ENABLE_SSO=true
    fi

    if [[ "$ENABLE_SSO" != true ]]; then
        return
    fi

    # Bundled vs External Authentik
    local current_bundled
    current_bundled=$(get_env_value "USE_BUNDLED_AUTHENTIK")

    if [[ -n "$USE_BUNDLED_AUTHENTIK" ]]; then
        # Already set via CLI
        print_info "Using bundled Authentik: $USE_BUNDLED_AUTHENTIK"
    elif [[ "$current_bundled" == "true" ]]; then
        echo "Currently using bundled Authentik"
        if [[ "$NON_INTERACTIVE" == false ]] && ! confirm "Keep using bundled Authentik?" "y"; then
            USE_BUNDLED_AUTHENTIK=false
        else
            USE_BUNDLED_AUTHENTIK=true
        fi
    else
        if [[ "$NON_INTERACTIVE" == true ]]; then
            USE_BUNDLED_AUTHENTIK=false
        elif confirm "Use bundled Authentik server?" "y"; then
            USE_BUNDLED_AUTHENTIK=true
        else
            USE_BUNDLED_AUTHENTIK=false
        fi
    fi

    # If external, get URL
    if [[ "$USE_BUNDLED_AUTHENTIK" != true ]]; then
        local current_auth_url
        current_auth_url=$(get_env_value "AUTHENTIK_BASE_URL")

        if [[ -n "$EXTERNAL_AUTHENTIK_URL" ]]; then
            EXTERNAL_AUTHENTIK_URL=$(strip_trailing_slash "$EXTERNAL_AUTHENTIK_URL")
        elif [[ -n "$current_auth_url" ]] && ! is_placeholder "$current_auth_url"; then
            echo "Current Authentik URL: $current_auth_url"
            if [[ "$NON_INTERACTIVE" == true ]] || ! confirm "Change Authentik URL?" "n"; then
                EXTERNAL_AUTHENTIK_URL=$(strip_trailing_slash "$current_auth_url")
            else
                read_authentik_url
            fi
        else
            read_authentik_url
        fi

        if [[ -z "$EXTERNAL_AUTHENTIK_URL" ]]; then
            print_error "External Authentik URL is required when not using bundled Authentik"
            exit 1
        fi
    fi

    if [[ "$USE_BUNDLED_AUTHENTIK" == true ]]; then
        if [[ "$BASE_URL_MODE" == "traefik" && -z "$MOSAIC_AUTH_DOMAIN" ]]; then
            if [[ "$NON_INTERACTIVE" == true ]]; then
                MOSAIC_AUTH_DOMAIN="auth.${MOSAIC_WEB_DOMAIN:-mosaic.local}"
            else
                local default_auth_domain="auth.${MOSAIC_WEB_DOMAIN:-mosaic.local}"
                read -r -p "Auth domain [$default_auth_domain]: " MOSAIC_AUTH_DOMAIN
                MOSAIC_AUTH_DOMAIN=${MOSAIC_AUTH_DOMAIN:-$default_auth_domain}
            fi
        fi
    fi

    recalculate_urls
}

read_authentik_url() {
    while true; do
        read -r -p "Enter external Authentik URL: " EXTERNAL_AUTHENTIK_URL
        if validate_url "$EXTERNAL_AUTHENTIK_URL"; then
            EXTERNAL_AUTHENTIK_URL=$(strip_trailing_slash "$EXTERNAL_AUTHENTIK_URL")
            break
        else
            print_error "Invalid URL format"
        fi
    done
}

configure_ollama() {
    echo ""
    print_step "Ollama Configuration"
    echo ""

    local current_mode
    current_mode=$(get_env_value "OLLAMA_MODE")

    if [[ -n "$OLLAMA_MODE" ]] && [[ "$OLLAMA_MODE" != "disabled" ]]; then
        # Already set via CLI
        print_info "Ollama mode: $OLLAMA_MODE"
    elif [[ -n "$current_mode" ]] && [[ "$current_mode" != "disabled" ]]; then
        echo "Current Ollama mode: $current_mode"
        if [[ "$NON_INTERACTIVE" == false ]] && confirm "Change Ollama configuration?" "n"; then
            select_ollama_mode
        else
            OLLAMA_MODE="$current_mode"
        fi
    else
        select_ollama_mode
    fi

    # Get Ollama URL if remote mode
    if [[ "$OLLAMA_MODE" == "remote" ]]; then
        local current_url
        current_url=$(get_env_value "OLLAMA_ENDPOINT")

        if [[ -n "$OLLAMA_URL" ]]; then
            # Already set via CLI
            print_info "Ollama URL: $OLLAMA_URL"
        elif [[ -n "$current_url" ]] && ! is_placeholder "$current_url"; then
            echo "Current Ollama URL: $current_url"
            if [[ "$NON_INTERACTIVE" == false ]] && confirm "Change Ollama URL?" "n"; then
                read_ollama_url
            else
                OLLAMA_URL="$current_url"
            fi
        else
            read_ollama_url
        fi
    fi
}

select_ollama_mode() {
    if [[ "$NON_INTERACTIVE" == true ]]; then
        OLLAMA_MODE="disabled"
        return
    fi

    local selection
    selection=$(select_option "Select Ollama mode:" \
        "Disabled (no local LLM)" \
        "Local (run Ollama in Docker)" \
        "Remote (connect to existing Ollama server)")

    case "$selection" in
        *"Disabled"*)
            OLLAMA_MODE="disabled"
            ;;
        *"Local"*)
            OLLAMA_MODE="local"
            ;;
        *"Remote"*)
            OLLAMA_MODE="remote"
            ;;
    esac
}

read_ollama_url() {
    while true; do
        read -r -p "Enter Ollama server URL: " OLLAMA_URL
        if validate_url "$OLLAMA_URL"; then
            break
        else
            print_error "Invalid URL format"
        fi
    done
}

configure_moltbot() {
    echo ""
    print_step "MoltBot Configuration"
    echo ""

    local current_enabled
    current_enabled=$(get_env_value "ENABLE_MOLTBOT")

    if [[ -n "$ENABLE_MOLTBOT" ]]; then
        # Already set via CLI
        print_info "MoltBot enabled: $ENABLE_MOLTBOT"
    elif [[ "$current_enabled" == "true" ]]; then
        echo "MoltBot is currently enabled"
        if [[ "$NON_INTERACTIVE" == false ]] && ! confirm "Keep MoltBot enabled?" "y"; then
            ENABLE_MOLTBOT=false
        else
            ENABLE_MOLTBOT=true
        fi
    else
        if [[ "$NON_INTERACTIVE" == true ]]; then
            ENABLE_MOLTBOT=false
        elif confirm "Enable MoltBot integration?"; then
            ENABLE_MOLTBOT=true
        else
            ENABLE_MOLTBOT=false
        fi
    fi
}

configure_secrets() {
    echo ""
    print_step "Secret Generation"
    echo ""

    # Check if secrets exist and are not placeholders
    local db_password
    local jwt_secret
    local auth_db_password
    local auth_secret_key
    local auth_bootstrap_password

    db_password=$(get_env_value "POSTGRES_PASSWORD")
    jwt_secret=$(get_env_value "JWT_SECRET")
    auth_db_password=$(get_env_value "AUTHENTIK_POSTGRES_PASSWORD")
    auth_secret_key=$(get_env_value "AUTHENTIK_SECRET_KEY")
    auth_bootstrap_password=$(get_env_value "AUTHENTIK_BOOTSTRAP_PASSWORD")

    local needs_generation=false

    if is_placeholder "$db_password"; then
        print_info "Will generate database password"
        needs_generation=true
    fi

    if is_placeholder "$jwt_secret"; then
        print_info "Will generate JWT secret"
        needs_generation=true
    fi

    if [[ "$ENABLE_SSO" == true && "$USE_BUNDLED_AUTHENTIK" == true ]]; then
        if is_placeholder "$auth_db_password"; then
            print_info "Will generate Authentik database password"
            needs_generation=true
        fi

        if is_placeholder "$auth_secret_key"; then
            print_info "Will generate Authentik secret key"
            needs_generation=true
        fi

        if is_placeholder "$auth_bootstrap_password"; then
            print_info "Will generate Authentik bootstrap password"
            needs_generation=true
        fi
    fi

    if [[ "$needs_generation" == true ]]; then
        print_success "Secrets will be generated during .env creation"
    else
        print_success "Existing secrets will be preserved"
    fi
}

# ============================================================================
# .env File Generation
# ============================================================================

declare -gA ENV_OVERRIDES

env_override_set() {
    local key="$1"
    local value="$2"
    ENV_OVERRIDES["$key"]="$value"
}

write_env_with_overrides() {
    local template="$1"
    local output="$2"

    local overrides_file
    overrides_file=$(mktemp)

    for key in "${!ENV_OVERRIDES[@]}"; do
        printf '%s=%s\n' "$key" "${ENV_OVERRIDES[$key]}" >> "$overrides_file"
    done

    awk -v overrides_file="$overrides_file" '
        BEGIN {
            while ((getline < overrides_file) > 0) {
                line = $0
                if (line ~ /^[A-Za-z0-9_]+=*/) {
                    eq = index(line, "=")
                    key = substr(line, 1, eq - 1)
                    value = substr(line, eq + 1)
                    map[key] = value
                }
            }
            close(overrides_file)
        }
        {
            if ($0 ~ /^[A-Za-z0-9_]+=/) {
                eq = index($0, "=")
                key = substr($0, 1, eq - 1)
                if (key in map) {
                    print key "=" map[key]
                    used[key] = 1
                    next
                }
            }
            print
        }
        END {
            for (key in map) {
                if (!(key in used)) {
                    print key "=" map[key]
                }
            }
        }
    ' "$template" > "$output"

    rm -f "$overrides_file"
}

generate_env_file() {
    print_header ".env File Generation"

    # Backup existing .env if it exists
    if [[ -f "$PROJECT_ROOT/.env" ]]; then
        backup_file "$PROJECT_ROOT/.env"
    fi

    echo ""
    print_step "Generating .env file with your configuration..."
    echo ""

    # Start with .env.example template
    local template="$PROJECT_ROOT/.env.example"
    local output="$PROJECT_ROOT/.env"

    if [[ ! -f "$template" ]]; then
        print_error ".env.example template not found"
        exit 1
    fi

    ENV_OVERRIDES=()

    for key in "${!ENV_VALUES[@]}"; do
        local value="${ENV_VALUES[$key]}"
        if [[ -n "$value" ]] && ! is_placeholder "$value"; then
            env_override_set "$key" "$value"
        fi
    done

    local xtrace_was_on=false
    if [[ "$-" == *x* ]]; then
        xtrace_was_on=true
        set +x
    fi

    local postgres_user
    local postgres_db
    local db_password
    local jwt_secret
    local jwt_expiration
    local api_host
    local valkey_url
    local database_url

    postgres_user=$(resolve_env_value "POSTGRES_USER" "mosaic")
    postgres_db=$(resolve_env_value "POSTGRES_DB" "mosaic")
    api_host=$(resolve_env_value "API_HOST" "0.0.0.0")
    jwt_expiration=$(resolve_env_value "JWT_EXPIRATION" "24h")

    local existing_db_password
    existing_db_password=$(get_env_value "POSTGRES_PASSWORD")
    db_password=$(resolve_secret_value "POSTGRES_PASSWORD" generate_password 32)
    if [[ -n "$existing_db_password" ]] && ! is_placeholder "$existing_db_password"; then
        print_info "Preserving existing database password"
    else
        print_info "Generated database password"
    fi

    local existing_jwt_secret
    existing_jwt_secret=$(get_env_value "JWT_SECRET")
    jwt_secret=$(resolve_secret_value "JWT_SECRET" generate_secret 50)
    if [[ -n "$existing_jwt_secret" ]] && ! is_placeholder "$existing_jwt_secret"; then
        print_info "Preserving existing JWT secret"
    else
        print_info "Generated JWT secret"
    fi

    if [[ "$MODE" == "docker" ]]; then
        database_url="postgresql://${postgres_user}:${db_password}@postgres:5432/${postgres_db}"
        valkey_url="redis://valkey:6379"
    else
        database_url="postgresql://${postgres_user}:${db_password}@localhost:${POSTGRES_PORT}/${postgres_db}"
        valkey_url="redis://localhost:${VALKEY_PORT}"
    fi

    local authentik_db_password=""
    local authentik_secret_key=""
    local authentik_bootstrap_password=""
    local authentik_postgres_user=""
    local authentik_postgres_db=""
    local authentik_bootstrap_email=""
    local authentik_cookie_domain=""

    if [[ "$ENABLE_SSO" == true && "$USE_BUNDLED_AUTHENTIK" == true ]]; then
        authentik_postgres_user=$(resolve_env_value "AUTHENTIK_POSTGRES_USER" "authentik")
        authentik_postgres_db=$(resolve_env_value "AUTHENTIK_POSTGRES_DB" "authentik")
        authentik_bootstrap_email=$(resolve_env_value "AUTHENTIK_BOOTSTRAP_EMAIL" "admin@localhost")

        local existing_auth_db_password
        existing_auth_db_password=$(get_env_value "AUTHENTIK_POSTGRES_PASSWORD")
        authentik_db_password=$(resolve_secret_value "AUTHENTIK_POSTGRES_PASSWORD" generate_password 32)
        if [[ -n "$existing_auth_db_password" ]] && ! is_placeholder "$existing_auth_db_password"; then
            print_info "Preserving existing Authentik database password"
        else
            print_info "Generated Authentik database password"
        fi

        local existing_auth_secret
        existing_auth_secret=$(get_env_value "AUTHENTIK_SECRET_KEY")
        authentik_secret_key=$(resolve_secret_value "AUTHENTIK_SECRET_KEY" generate_secret 64)
        if [[ -n "$existing_auth_secret" ]] && ! is_placeholder "$existing_auth_secret"; then
            print_info "Preserving existing Authentik secret key"
        else
            print_info "Generated Authentik secret key"
        fi

        local existing_auth_bootstrap
        existing_auth_bootstrap=$(get_env_value "AUTHENTIK_BOOTSTRAP_PASSWORD")
        authentik_bootstrap_password=$(resolve_secret_value "AUTHENTIK_BOOTSTRAP_PASSWORD" generate_password 24)
        if [[ -n "$existing_auth_bootstrap" ]] && ! is_placeholder "$existing_auth_bootstrap"; then
            print_info "Preserving existing Authentik bootstrap password"
        else
            print_info "Generated Authentik bootstrap password"
        fi

        authentik_cookie_domain=$(resolve_env_value "AUTHENTIK_COOKIE_DOMAIN" "")
        if [[ -z "$authentik_cookie_domain" ]]; then
            if [[ "$BASE_URL_MODE" == "traefik" && -n "$MOSAIC_AUTH_DOMAIN" ]]; then
                authentik_cookie_domain=$(derive_cookie_domain "$MOSAIC_AUTH_DOMAIN")
            else
                authentik_cookie_domain=".localhost"
            fi
        fi
    fi

    local oidc_issuer=""
    local oidc_client_id=""
    local oidc_client_secret=""
    local oidc_redirect_uri=""

    if [[ "$ENABLE_SSO" == true ]]; then
        local auth_url
        if [[ "$USE_BUNDLED_AUTHENTIK" == true ]]; then
            auth_url=$(strip_trailing_slash "$AUTHENTIK_PUBLIC_URL")
        else
            auth_url=$(strip_trailing_slash "$EXTERNAL_AUTHENTIK_URL")
        fi

        oidc_issuer="${auth_url}/application/o/mosaic-stack/"
        oidc_client_id=$(resolve_env_value "OIDC_CLIENT_ID" "mosaic-stack")
        oidc_client_secret=$(resolve_env_value "OIDC_CLIENT_SECRET" "change-after-authentik-setup")
        oidc_redirect_uri="${API_BASE_URL}/auth/callback"
    fi

    local ollama_mode_env=""
    local ollama_endpoint=""
    if [[ "$OLLAMA_MODE" == "local" ]]; then
        ollama_mode_env="local"
        ollama_endpoint="http://ollama:11434"
    elif [[ "$OLLAMA_MODE" == "remote" ]]; then
        ollama_mode_env="remote"
        ollama_endpoint="$OLLAMA_URL"
    else
        ollama_mode_env="remote"
        ollama_endpoint=$(resolve_env_value "OLLAMA_ENDPOINT" "http://localhost:11434")
    fi

    local compose_profiles=()
    if [[ "$ENABLE_SSO" == true && "$USE_BUNDLED_AUTHENTIK" == true ]]; then
        compose_profiles+=("authentik")
    fi
    if [[ "$OLLAMA_MODE" == "local" ]]; then
        compose_profiles+=("ollama")
    fi
    if [[ "$TRAEFIK_MODE" == "bundled" ]]; then
        compose_profiles+=("traefik-bundled")
    fi

    local profiles_string=""
    if [[ ${#compose_profiles[@]} -gt 0 ]]; then
        profiles_string=$(IFS=,; echo "${compose_profiles[*]}")
    fi

    env_override_set "API_PORT" "$API_PORT"
    env_override_set "API_HOST" "$api_host"
    env_override_set "WEB_PORT" "$WEB_PORT"
    env_override_set "POSTGRES_USER" "$postgres_user"
    env_override_set "POSTGRES_PASSWORD" "$db_password"
    env_override_set "POSTGRES_DB" "$postgres_db"
    env_override_set "POSTGRES_PORT" "$POSTGRES_PORT"
    env_override_set "DATABASE_URL" "$database_url"
    env_override_set "VALKEY_URL" "$valkey_url"
    env_override_set "VALKEY_PORT" "$VALKEY_PORT"
    env_override_set "NEXT_PUBLIC_API_URL" "$API_BASE_URL"
    if [[ -n "$MOSAIC_BASE_URL" ]]; then
        env_override_set "MOSAIC_BASE_URL" "$MOSAIC_BASE_URL"
    fi
    env_override_set "JWT_SECRET" "$jwt_secret"
    env_override_set "JWT_EXPIRATION" "$jwt_expiration"
    env_override_set "OLLAMA_MODE" "$ollama_mode_env"
    env_override_set "OLLAMA_ENDPOINT" "$ollama_endpoint"

    if [[ -n "$profiles_string" ]]; then
        env_override_set "COMPOSE_PROFILES" "$profiles_string"
    else
        env_override_set "COMPOSE_PROFILES" ""
    fi

    env_override_set "TRAEFIK_MODE" "$TRAEFIK_MODE"
    env_override_set "TRAEFIK_ENABLE" "$(bool_str "$TRAEFIK_ENABLE")"
    env_override_set "TRAEFIK_TLS_ENABLED" "$(bool_str "$TRAEFIK_TLS_ENABLED")"
    env_override_set "TRAEFIK_ENTRYPOINT" "$TRAEFIK_ENTRYPOINT"

    if [[ -n "$TRAEFIK_NETWORK" ]]; then
        env_override_set "TRAEFIK_NETWORK" "$TRAEFIK_NETWORK"
    fi
    if [[ -n "$TRAEFIK_DOCKER_NETWORK" ]]; then
        env_override_set "TRAEFIK_DOCKER_NETWORK" "$TRAEFIK_DOCKER_NETWORK"
    fi
    if [[ -n "$MOSAIC_WEB_DOMAIN" ]]; then
        env_override_set "MOSAIC_WEB_DOMAIN" "$MOSAIC_WEB_DOMAIN"
    fi
    if [[ -n "$MOSAIC_API_DOMAIN" ]]; then
        env_override_set "MOSAIC_API_DOMAIN" "$MOSAIC_API_DOMAIN"
    fi
    if [[ -n "$MOSAIC_AUTH_DOMAIN" ]]; then
        env_override_set "MOSAIC_AUTH_DOMAIN" "$MOSAIC_AUTH_DOMAIN"
    fi

    if [[ "$TRAEFIK_MODE" == "bundled" ]]; then
        env_override_set "TRAEFIK_HTTP_PORT" "$TRAEFIK_HTTP_PORT"
        env_override_set "TRAEFIK_HTTPS_PORT" "$TRAEFIK_HTTPS_PORT"
        env_override_set "TRAEFIK_DASHBOARD_ENABLED" "$(bool_str "$TRAEFIK_DASHBOARD_ENABLED")"
        env_override_set "TRAEFIK_DASHBOARD_PORT" "$TRAEFIK_DASHBOARD_PORT"
        if [[ -n "$TRAEFIK_ACME_EMAIL" ]]; then
            env_override_set "TRAEFIK_ACME_EMAIL" "$TRAEFIK_ACME_EMAIL"
        fi
        if [[ -n "$TRAEFIK_CERTRESOLVER" ]]; then
            env_override_set "TRAEFIK_CERTRESOLVER" "$TRAEFIK_CERTRESOLVER"
        fi
    fi

    if [[ "$ENABLE_SSO" == true ]]; then
        env_override_set "OIDC_ISSUER" "$oidc_issuer"
        env_override_set "OIDC_CLIENT_ID" "$oidc_client_id"
        env_override_set "OIDC_CLIENT_SECRET" "$oidc_client_secret"
        env_override_set "OIDC_REDIRECT_URI" "$oidc_redirect_uri"
    fi

    if [[ "$ENABLE_SSO" == true && "$USE_BUNDLED_AUTHENTIK" == true ]]; then
        env_override_set "AUTHENTIK_POSTGRES_USER" "$authentik_postgres_user"
        env_override_set "AUTHENTIK_POSTGRES_PASSWORD" "$authentik_db_password"
        env_override_set "AUTHENTIK_POSTGRES_DB" "$authentik_postgres_db"
        env_override_set "AUTHENTIK_SECRET_KEY" "$authentik_secret_key"
        env_override_set "AUTHENTIK_BOOTSTRAP_PASSWORD" "$authentik_bootstrap_password"
        env_override_set "AUTHENTIK_BOOTSTRAP_EMAIL" "$authentik_bootstrap_email"
        env_override_set "AUTHENTIK_COOKIE_DOMAIN" "$authentik_cookie_domain"
        env_override_set "AUTHENTIK_PORT_HTTP" "$AUTHENTIK_PORT_HTTP"
        env_override_set "AUTHENTIK_PORT_HTTPS" "$AUTHENTIK_PORT_HTTPS"
    fi

    if [[ "$OLLAMA_MODE" == "local" ]]; then
        env_override_set "OLLAMA_PORT" "$OLLAMA_PORT"
    fi

    write_env_with_overrides "$template" "$output"

    chmod 600 "$output"
    print_success "Generated .env file"

    # Write credentials file
    write_credentials_file "$db_password" "$authentik_bootstrap_password"

    if [[ "$xtrace_was_on" == true ]]; then
        set -x
    fi
}

write_credentials_file() {
    local db_password="$1"
    local authentik_password="$2"

    local creds_file="$PROJECT_ROOT/.admin-credentials"
    local db_user
    db_user=$(resolve_env_value "POSTGRES_USER" "mosaic")

    cat > "$creds_file" << EOF
# Mosaic Stack Admin Credentials
# Generated: $(date)
# KEEP THIS FILE SECURE AND DO NOT COMMIT TO VERSION CONTROL

Database (PostgreSQL):
  Username: ${db_user}
  Password: $(mask_value "$db_password")
  Full password available in .env as POSTGRES_PASSWORD

JWT Secret:
  Available in .env as JWT_SECRET

EOF

    if [[ "$ENABLE_SSO" == true && -n "$authentik_password" ]]; then
        cat >> "$creds_file" << EOF
Authentik Admin:
  Email: admin@localhost
  Password: $(mask_value "$authentik_password")
  Full password available in .env as AUTHENTIK_BOOTSTRAP_PASSWORD
  URL: ${AUTHENTIK_PUBLIC_URL:-http://localhost:9000}

  IMPORTANT: Change the bootstrap password after first login!

EOF
    fi

    chmod 600 "$creds_file"
    print_success "Saved credentials to .admin-credentials"
}

# ============================================================================
# Deployment
# ============================================================================

run_deployment() {
    print_header "Deployment"

    if [[ "$MODE" == "docker" ]]; then
        deploy_docker
    else
        deploy_native
    fi
}

prepare_traefik_upstream() {
    if [[ "$TRAEFIK_MODE" != "upstream" ]]; then
        return
    fi

    print_header "Traefik Upstream Setup"

    local network="${TRAEFIK_NETWORK:-traefik-public}"

    if ! docker network ls --format '{{.Name}}' | grep -q "^${network}$"; then
        print_warning "Traefik network '${network}' not found"
        if [[ "$NON_INTERACTIVE" == true ]] || confirm "Create network '${network}' now?" "y"; then
            docker network create "$network"
            print_success "Created network: ${network}"
        else
            print_warning "Continuing without creating network"
        fi
    fi

    local override_file="$PROJECT_ROOT/docker-compose.override.yml"
    if [[ -f "$override_file" ]]; then
        print_info "docker-compose.override.yml already exists. Leaving it unchanged."
        return
    fi

    cat > "$override_file" << 'EOF'
version: '3.9'

services:
  api:
    networks:
      - traefik-public

  web:
    networks:
      - traefik-public

  authentik-server:
    networks:
      - traefik-public

networks:
  traefik-public:
    external: true
    name: ${TRAEFIK_NETWORK:-traefik-public}
EOF

    print_success "Created docker-compose.override.yml for upstream Traefik"
}

deploy_docker() {
    echo ""
    print_step "Starting Docker deployment..."
    echo ""

    prepare_traefik_upstream

    # Check if docker-compose.yml exists
    if [[ ! -f "$PROJECT_ROOT/docker-compose.yml" ]]; then
        print_error "docker-compose.yml not found"
        exit 1
    fi

    cd "$PROJECT_ROOT" || exit 1

    local compose_files=("-f" "docker-compose.yml")
    if [[ -f "docker-compose.override.yml" ]]; then
        compose_files+=("-f" "docker-compose.override.yml")
    fi

    # Set up error trap for rollback
    local deployment_started=false
    rollback_deployment() {
        if [[ "$deployment_started" == true ]]; then
            print_warning "Deployment failed, rolling back..."
            docker compose "${compose_files[@]}" down --remove-orphans 2>/dev/null || true
            print_info "Rollback complete"
        fi
    }
    trap rollback_deployment ERR

    # Pull images
    print_step "Pulling Docker images..."
    if ! docker compose "${compose_files[@]}" pull; then
        print_warning "Failed to pull some images (will build locally)"
    fi

    # Build services
    print_step "Building services..."
    if ! docker compose "${compose_files[@]}" build; then
        print_error "Build failed"
        trap - ERR
        return 1
    fi

    # Start services
    print_step "Starting services..."
    deployment_started=true
    if ! docker compose "${compose_files[@]}" up -d; then
        print_error "Failed to start services"
        trap - ERR
        rollback_deployment
        return 1
    fi

    # Clear trap on success
    trap - ERR

    # Wait for services to be healthy
    echo ""
    print_step "Waiting for services to start..."
    sleep 5

    # Check service health
    local all_healthy=true
    local services=("postgres" "valkey" "api" "web")

    for service in "${services[@]}"; do
        if docker compose "${compose_files[@]}" ps "$service" 2>/dev/null | grep -q "Up"; then
            print_success "$service: Running"
        else
            print_warning "$service: Not running"
            all_healthy=false
        fi
    done

    if [[ "$all_healthy" == true ]]; then
        echo ""
        print_success "All services started successfully"
    else
        echo ""
        print_warning "Some services may not be running correctly"
        print_info "Check logs with: docker compose logs"
    fi
}

deploy_native() {
    echo ""
    print_step "Native deployment setup..."
    echo ""

    cd "$PROJECT_ROOT" || exit 1

    # Install dependencies
    print_step "Installing Node.js dependencies..."
    if ! pnpm install; then
        print_error "Failed to install dependencies"
        exit 1
    fi

    # Database setup
    print_step "Setting up database..."
    print_info "Make sure PostgreSQL is running and accessible"

    # Run migrations
    print_step "Running database migrations..."
    if ! pnpm -F api run prisma:migrate; then
        print_warning "Database migration failed - you may need to set this up manually"
    fi

    # Build
    print_step "Building application..."
    pnpm run build

    print_success "Native setup complete"
    echo ""
    print_info "To start the application:"
    print_info "  pnpm dev          # Development mode"
    print_info "  pnpm start        # Production mode"
}

# ============================================================================
# Post-Install Information
# ============================================================================

show_post_install_info() {
    if [[ "$DRY_RUN" == true ]]; then
        print_header "Dry Run Summary"
        echo ""
        print_info "No files were written and no containers were started."
        echo ""
        print_step "Planned URLs:"
        echo ""
        if [[ "$MODE" == "docker" ]]; then
            echo "  Web Interface:  $MOSAIC_BASE_URL"
            echo "  API:            $API_BASE_URL"
            if [[ "$ENABLE_SSO" == true ]]; then
                echo "  Authentik SSO:  ${AUTHENTIK_PUBLIC_URL:-http://localhost:9000}"
            fi
        fi
        echo ""
        return
    fi

    print_header "Installation Complete"

    echo ""
    echo "🎉 Mosaic Stack has been set up successfully!"
    echo ""

    # Show URLs
    print_step "Access URLs:"
    echo ""

    if [[ "$MODE" == "docker" ]]; then
        local web_url="$MOSAIC_BASE_URL"
        local api_url="$API_BASE_URL"

        echo "  Web Interface:  $web_url"
        echo "  API:            $api_url"

        if [[ "$ENABLE_SSO" == true ]]; then
            local auth_url="${AUTHENTIK_PUBLIC_URL:-http://localhost:9000}"
            echo "  Authentik SSO:  $auth_url"
        fi

        if [[ "$OLLAMA_MODE" == "local" ]]; then
            echo "  Ollama:         http://localhost:${OLLAMA_PORT}"
        fi
    else
        echo "  Run 'pnpm dev' to start the development server"
    fi

    echo ""
    print_step "Credentials:"
    echo ""
    echo "  Saved to: .admin-credentials"
    print_warning "Keep this file secure!"

    echo ""
    print_step "Next Steps:"
    echo ""

    if [[ "$ENABLE_SSO" == true ]]; then
        echo "  1. Access Authentik at ${AUTHENTIK_PUBLIC_URL:-http://localhost:9000}"
        echo "  2. Log in with credentials from .admin-credentials"
        echo "  3. Complete SSO setup (create application, get client secret)"
        echo "  4. Update OIDC_CLIENT_SECRET in .env"
        echo "  5. Access Mosaic Stack at $MOSAIC_BASE_URL"
    else
        echo "  1. Access Mosaic Stack at $MOSAIC_BASE_URL"
        echo "  2. Create your first user account"
    fi

    if [[ "$TRAEFIK_MODE" == "upstream" ]]; then
        echo ""
        print_info "Upstream Traefik: ensure your Traefik is attached to '${TRAEFIK_NETWORK:-traefik-public}'"
    elif [[ "$TRAEFIK_MODE" == "bundled" && "$TRAEFIK_DASHBOARD_ENABLED" == true ]]; then
        echo ""
        print_info "Traefik dashboard: http://localhost:${TRAEFIK_DASHBOARD_PORT}/dashboard/"
    fi

    echo ""
    print_step "Useful Commands:"
    echo ""

    if [[ "$MODE" == "docker" ]]; then
        echo "  View logs:       docker compose logs -f"
        echo "  Stop services:   docker compose down"
        echo "  Restart:         docker compose restart"
        echo "  View status:     docker compose ps"
    else
        echo "  Development:     pnpm dev"
        echo "  Production:      pnpm start"
        echo "  Database:        pnpm -F api run prisma:migrate"
    fi

    echo ""
    print_step "Documentation:"
    echo ""
    echo "  Setup docs:      /home/jwoltje/src/mosaic-stack/scripts/README.md"
    echo "  Architecture:    /home/jwoltje/src/mosaic-stack/docs/"

    echo ""
}

# ============================================================================
# Main Execution
# ============================================================================

main() {
    parse_arguments "$@"

    show_banner

    detect_platform

    select_deployment_mode

    check_and_install_dependencies

    collect_configuration
    resolve_port_conflicts

    if [[ "$DRY_RUN" != true ]]; then
        generate_env_file
        run_deployment
    else
        echo ""
        print_warning "Dry run mode - skipping .env generation and deployment"
        echo ""
        print_info "Configuration collected but not applied"
    fi

    show_post_install_info

    echo ""
    print_success "Setup complete!"
    echo ""
    echo "==================================================================="
    echo "Setup completed: $(date)"
    echo "Full log saved to: $LOG_FILE"
    echo "==================================================================="
    echo ""

    # Reset terminal colors before exiting
    printf "${NC}"
}

# Run main function
main "$@"
