#!/bin/bash
# Common utility functions for Mosaic Stack setup
# Adapted from Calibr setup pattern

# ============================================================================
# Output Formatting
# ============================================================================

# Colors
if [[ -t 1 ]]; then
    RED='\033[0;31m'
    GREEN='\033[0;32m'
    YELLOW='\033[1;33m'
    BLUE='\033[0;34m'
    CYAN='\033[0;36m'
    BOLD='\033[1m'
    NC='\033[0m' # No Color
else
    RED=''
    GREEN=''
    YELLOW=''
    BLUE=''
    CYAN=''
    BOLD=''
    NC=''
fi

print_header() {
    echo ""
    echo -e "${BOLD}${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
    echo -e "${BOLD}${CYAN} $1${NC}"
    echo -e "${BOLD}${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
    echo ""
}

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

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

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

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

print_step() {
    echo -e "${CYAN}→${NC} $1"
}

# ============================================================================
# User Input
# ============================================================================

confirm() {
    local prompt="$1"
    local default="${2:-n}"
    local response

    if [[ "$default" == "y" ]]; then
        prompt="$prompt [Y/n]: "
    else
        prompt="$prompt [y/N]: "
    fi

    while true; do
        read -r -p "$prompt" response
        response=${response:-$default}
        case "$response" in
            [Yy]|[Yy][Ee][Ss])
                return 0
                ;;
            [Nn]|[Nn][Oo])
                return 1
                ;;
            *)
                echo "Please answer yes or no."
                ;;
        esac
    done
}

select_option() {
    local prompt="$1"
    shift
    local options=("$@")
    local num_options=${#options[@]}

    # Output UI to /dev/tty so it's visible even when function output is captured
    echo "$prompt" >/dev/tty
    for i in "${!options[@]}"; do
        printf "  %d) %s\n" "$((i + 1))" "${options[$i]}" >/dev/tty
    done
    echo "" >/dev/tty

    local selection
    while true; do
        read -r -p "Enter selection [1-$num_options]: " selection </dev/tty >/dev/tty
        if [[ "$selection" =~ ^[0-9]+$ ]] && \
           [ "$selection" -ge 1 ] && \
           [ "$selection" -le "$num_options" ]; then
            # Only output the selected value to stdout (for capture)
            echo "${options[$((selection - 1))]}"
            return 0
        else
            print_error "Invalid selection. Please enter a number between 1 and $num_options." >/dev/tty
        fi
    done
}

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

detect_os() {
    case "$OSTYPE" in
        linux-gnu*)
            if [[ -f /etc/os-release ]]; then
                source /etc/os-release
                case "$ID" in
                    ubuntu|debian)
                        echo "debian"
                        ;;
                    arch|manjaro|endeavouros)
                        echo "arch"
                        ;;
                    fedora|rhel|centos)
                        echo "fedora"
                        ;;
                    *)
                        echo "linux"
                        ;;
                esac
            else
                echo "linux"
            fi
            ;;
        darwin*)
            echo "macos"
            ;;
        *)
            echo "unknown"
            ;;
    esac
}

detect_package_manager() {
    local os="$1"

    case "$os" in
        debian)
            echo "apt"
            ;;
        arch)
            echo "pacman"
            ;;
        fedora)
            echo "dnf"
            ;;
        macos)
            if command -v brew >/dev/null 2>&1; then
                echo "brew"
            else
                echo "none"
            fi
            ;;
        *)
            echo "unknown"
            ;;
    esac
}

get_os_name() {
    local os="$1"

    case "$os" in
        debian)
            echo "Debian/Ubuntu"
            ;;
        arch)
            echo "Arch Linux"
            ;;
        fedora)
            echo "Fedora/RHEL"
            ;;
        macos)
            echo "macOS"
            ;;
        *)
            echo "Unknown OS"
            ;;
    esac
}

# ============================================================================
# Command and Dependency Checking
# ============================================================================

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

check_docker() {
    if ! check_command docker; then
        return 1
    fi

    # Check if daemon is accessible
    if docker info >/dev/null 2>&1; then
        return 0
    fi

    # Docker exists but daemon not accessible
    # This could be permission issue or daemon not running
    local error_msg
    error_msg=$(docker info 2>&1)

    if [[ "$error_msg" =~ "permission denied" ]]; then
        print_warning "Docker installed but permission denied"
        print_info "You may need to add your user to the docker group:"
        print_info "  sudo usermod -aG docker \$USER"
        print_info "  Then log out and back in"
        return 2  # Special code for permission issue
    elif [[ "$error_msg" =~ "Cannot connect to the Docker daemon" ]]; then
        print_warning "Docker installed but daemon not running"
        print_info "Start it with: sudo systemctl start docker"
        return 3  # Special code for daemon not running
    else
        print_warning "Docker installed but not accessible"
        return 4  # Unknown issue
    fi
}

check_docker_compose() {
    if docker compose version >/dev/null 2>&1; then
        return 0
    elif check_command docker-compose; then
        return 0
    else
        return 1
    fi
}

check_docker_buildx() {
    docker buildx version >/dev/null 2>&1
}

check_node() {
    local min_version="${1:-18}"
    if ! check_command node; then
        return 1
    fi

    local node_version
    node_version=$(node --version | sed 's/v//' | cut -d. -f1)

    if [[ "$node_version" -ge "$min_version" ]]; then
        return 0
    else
        return 1
    fi
}

check_pnpm() {
    check_command pnpm
}

check_postgres() {
    check_command psql
}

check_sudo() {
    if ! command -v sudo >/dev/null 2>&1; then
        print_error "sudo is not installed"
        return 1
    fi

    if ! sudo -n true 2>/dev/null; then
        print_warning "This script may need elevated privileges"
        print_info "You may be prompted for your password"
        sudo -v
    fi
}

# ============================================================================
# Package Installation
# ============================================================================

get_package_name() {
    local pkg_manager="$1"
    local package="$2"

    case "$pkg_manager" in
        apt)
            case "$package" in
                docker) echo "docker.io" ;;
                docker-compose) echo "docker-compose" ;;
                node) echo "nodejs" ;;
                postgres) echo "postgresql postgresql-contrib" ;;
                *) echo "$package" ;;
            esac
            ;;
        pacman)
            case "$package" in
                docker) echo "docker" ;;
                docker-compose) echo "docker-compose" ;;
                node) echo "nodejs" ;;
                postgres) echo "postgresql" ;;
                *) echo "$package" ;;
            esac
            ;;
        dnf)
            case "$package" in
                docker) echo "docker" ;;
                docker-compose) echo "docker-compose" ;;
                node) echo "nodejs" ;;
                postgres) echo "postgresql-server" ;;
                *) echo "$package" ;;
            esac
            ;;
        brew)
            case "$package" in
                docker) echo "docker" ;;
                node) echo "node" ;;
                postgres) echo "postgresql@17" ;;
                *) echo "$package" ;;
            esac
            ;;
        *)
            echo "$package"
            ;;
    esac
}

install_package() {
    local pkg_manager="$1"
    local package="$2"

    print_step "Installing $package..."

    case "$pkg_manager" in
        apt)
            # Don't quote $package to allow multi-word package names
            sudo apt update && sudo apt install -y $package
            ;;
        pacman)
            sudo pacman -Sy --noconfirm $package
            ;;
        dnf)
            sudo dnf install -y $package
            ;;
        brew)
            brew install $package
            ;;
        *)
            print_error "Unknown package manager: $pkg_manager"
            return 1
            ;;
    esac
}

# ============================================================================
# Validation Functions
# ============================================================================

validate_url() {
    local url="$1"
    if [[ "$url" =~ ^https?://[a-zA-Z0-9.-]+(:[0-9]+)?(/.*)?$ ]]; then
        return 0
    else
        return 1
    fi
}

validate_email() {
    local email="$1"
    if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
        return 0
    else
        return 1
    fi
}

validate_port() {
    local port="$1"
    if [[ "$port" =~ ^[0-9]+$ ]] && [ "$port" -ge 1 ] && [ "$port" -le 65535 ]; then
        return 0
    else
        return 1
    fi
}

validate_domain() {
    local domain="$1"
    # Allow single-character subdomains and properly validate domain structure
    if [[ "$domain" =~ ^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$ ]]; then
        return 0
    else
        return 1
    fi
}

validate_ipv4() {
    local ip="$1"
    local IFS='.'
    local -a octets
    read -ra octets <<< "$ip"

    # Must have exactly 4 octets
    [[ ${#octets[@]} -eq 4 ]] || return 1

    # Each octet must be 0-255
    for octet in "${octets[@]}"; do
        # Must be numeric
        [[ "$octet" =~ ^[0-9]+$ ]] || return 1
        # Must be in range 0-255
        (( octet >= 0 && octet <= 255 )) || return 1
    done

    return 0
}

# ============================================================================
# Secret and Password Generation
# ============================================================================

generate_secret() {
    local length="${1:-32}"
    # Use /dev/urandom for cryptographically secure random
    LC_ALL=C tr -dc 'A-Za-z0-9!@#$%^&*()-_=+' </dev/urandom | head -c "$length"
}

generate_password() {
    local length="${1:-16}"
    # Passwords without special chars for easier manual entry
    LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c "$length"
}

mask_value() {
    local value="$1"
    local length=${#value}

    if [ "$length" -le 8 ]; then
        echo "***${value: -2}"
    else
        echo "${value:0:3}...${value: -3}"
    fi
}

is_placeholder() {
    local value="$1"

    # Check for common placeholder patterns
    if [[ "$value" =~ ^\$\{.*\}$ ]] || \
       [[ "$value" =~ ^(change-me|changeme|your-.*|example|placeholder|TODO|FIXME|xxx+)$ ]] || \
       [[ "$value" =~ ^\<.*\>$ ]] || \
       [[ -z "$value" ]]; then
        return 0
    else
        return 1
    fi
}

# ============================================================================
# .env File Management
# ============================================================================

# Global associative array to store env values
declare -gA ENV_VALUES

parse_env_file() {
    local env_file="$1"

    if [[ ! -f "$env_file" ]]; then
        return 1
    fi

    # Clear existing values
    ENV_VALUES=()

    while IFS='=' read -r key value || [[ -n "$key" ]]; do
        # Skip comments and empty lines
        [[ "$key" =~ ^#.*$ ]] && continue
        [[ -z "$key" ]] && continue

        # Remove leading/trailing whitespace
        key=$(echo "$key" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
        value=$(echo "$value" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')

        # Remove quotes if present
        value="${value%\"}"
        value="${value#\"}"
        value="${value%\'}"
        value="${value#\'}"

        ENV_VALUES["$key"]="$value"
    done < "$env_file"
}

get_env_value() {
    local key="$1"
    echo "${ENV_VALUES[$key]:-}"
}

set_env_value() {
    local key="$1"
    local value="$2"
    ENV_VALUES["$key"]="$value"
}

# ============================================================================
# File Operations
# ============================================================================

backup_file() {
    local file="$1"
    if [[ -f "$file" ]]; then
        local backup="${file}.bak.$(date +%Y%m%d_%H%M%S)"
        cp "$file" "$backup"
        print_success "Backed up $file to $backup"
    fi
}

# ============================================================================
# Port Checking
# ============================================================================

check_port_in_use() {
    local port="$1"

    if command -v ss >/dev/null 2>&1; then
        ss -tuln | grep -q ":${port} "
    elif command -v netstat >/dev/null 2>&1; then
        netstat -tuln | grep -q ":${port} "
    elif command -v lsof >/dev/null 2>&1; then
        lsof -i ":${port}" >/dev/null 2>&1
    else
        # Can't check, assume available
        return 1
    fi
}

suggest_alternative_port() {
    local base_port="$1"
    local offset=100

    for i in {1..10}; do
        local alt_port=$((base_port + offset * i))
        if ! check_port_in_use "$alt_port"; then
            echo "$alt_port"
            return 0
        fi
    done

    echo "$((base_port + 10000))"
}
