#!/usr/bin/env bash

# ProtonVPN CLI Tool
# Path: ~/bin/protonvpn-cli or /usr/local/bin/protonvpn-cli
# 
# Purpose: Command-line interface for managing ProtonVPN WireGuard connections
# Provides nordvpn-like functionality: auto-connect, country selection, speed testing
# 
# Usage:
#   protonvpn-cli connect [country]     - Connect to fastest server (optionally from specific country)
#   protonvpn-cli disconnect           - Disconnect from current server
#   protonvpn-cli status               - Show connection status
#   protonvpn-cli list [countries]     - List available servers or countries
#   protonvpn-cli update               - Update server configurations
#   protonvpn-cli speedtest           - Test speeds of available servers
#   protonvpn-cli help                - Show this help message

set -euo pipefail

# Configuration
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly CONFIG_DIR="${HOME}/.config/protonvpn-cli"
readonly CONFIGS_DIR="${CONFIG_DIR}/configs"
readonly STATE_FILE="${CONFIG_DIR}/state.json"
readonly SPEED_CACHE="${CONFIG_DIR}/speeds.json"
readonly GENERATOR_SCRIPT="${SCRIPT_DIR}/protonvpn_generator.py"
readonly LOG_FILE="${CONFIG_DIR}/protonvpn-cli.log"

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

# Global variables
VERBOSE=false
DRY_RUN=false
CURRENT_CONNECTION=""

#######################################
# Print colored status messages to stderr
# Arguments:
#   $1 - log level (INFO, WARN, ERROR, SUCCESS)
#   $2+ - message
#######################################
print_status() {
    local level="$1"
    shift
    local timestamp
    timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    
    case "$level" in
        "INFO")
            echo -e "${BLUE}[INFO]${NC} $*" >&2
            ;;
        "WARN")
            echo -e "${YELLOW}[WARN]${NC} $*" >&2
            ;;
        "ERROR")
            echo -e "${RED}[ERROR]${NC} $*" >&2
            ;;
        "SUCCESS")
            echo -e "${GREEN}[SUCCESS]${NC} $*" >&2
            ;;
        "DEBUG")
            if [[ "$VERBOSE" == "true" ]]; then
                echo -e "${CYAN}[DEBUG]${NC} $*" >&2
            fi
            ;;
    esac
    
    # Log to file
    echo "[$timestamp] [$level] $*" >> "$LOG_FILE"
}

#######################################
# Check if a command exists
# Arguments:
#   $1 - command name
# Returns:
#   0 if command exists, 1 otherwise
#######################################
command_exists() {
    command -v "$1" >/dev/null 2>&1
}

#######################################
# Check system dependencies
#######################################
check_dependencies() {
    print_status "DEBUG" "Checking system dependencies..."
    
    local missing_deps=()
    
    # Essential commands
    for cmd in wg wg-quick jq curl ping; do
        if ! command_exists "$cmd"; then
            missing_deps+=("$cmd")
        fi
    done
    
    # Python for generator script
    if ! command_exists python3; then
        missing_deps+=("python3")
    fi
    
    if [[ ${#missing_deps[@]} -gt 0 ]]; then
        print_status "ERROR" "Missing required dependencies: ${missing_deps[*]}"
        print_status "INFO" "Install with: sudo apt update && sudo apt install wireguard-tools jq curl iputils-ping python3"
        return 1
    fi
    
    # Check if running as root for WireGuard operations
    if [[ $EUID -ne 0 ]] && [[ "${1:-}" != "list" ]] && [[ "${1:-}" != "status" ]] && [[ "${1:-}" != "help" ]]; then
        print_status "WARN" "WireGuard operations require root privileges"
        print_status "INFO" "Try: sudo $0 $*"
        return 1
    fi
    
    return 0
}

#######################################
# Initialize configuration directory
#######################################
init_config() {
    print_status "DEBUG" "Initializing configuration directory..."
    
    mkdir -p "$CONFIG_DIR" "$CONFIGS_DIR"
    
    # Create state file if it doesn't exist
    if [[ ! -f "$STATE_FILE" ]]; then
        echo '{"current_connection": "", "last_update": 0}' > "$STATE_FILE"
    fi
    
    # Create speed cache if it doesn't exist
    if [[ ! -f "$SPEED_CACHE" ]]; then
        echo '{}' > "$SPEED_CACHE"
    fi
    
    # Ensure log file exists
    touch "$LOG_FILE"
}

#######################################
# Get current connection status
# Returns:
#   Connection info or empty string
#######################################
get_connection_status() {
    local active_interface
    active_interface=$(wg show 2>/dev/null | grep -E '^interface:' | head -1 | cut -d' ' -f2 || true)
    
    if [[ -n "$active_interface" ]]; then
        # Extract server info from interface name (assuming format like wg-CH-01)
        local server_info
        server_info=$(echo "$active_interface" | sed 's/^wg-//')
        echo "$server_info"
    else
        echo ""
    fi
}

#######################################
# Parse WireGuard config to extract server info
# Arguments:
#   $1 - config file path
#######################################
parse_config_info() {
    local config_file="$1"
    local server_name endpoint country
    
    server_name=$(basename "$config_file" .conf)
    endpoint=$(grep -E '^Endpoint' "$config_file" | cut -d'=' -f2 | tr -d ' ' | cut -d':' -f1)
    
    # Extract country from filename (assuming format like wg-CH-01)
    country=$(echo "$server_name" | sed 's/^wg-//' | cut -d'-' -f1)
    
    echo "$server_name|$endpoint|$country"
}

#######################################
# List available servers or countries
# Arguments:
#   $1 - "countries" to list countries, otherwise list servers
#######################################
list_servers() {
    local list_type="${1:-servers}"
    
    if [[ ! -d "$CONFIGS_DIR" ]] || [[ -z "$(ls -A "$CONFIGS_DIR" 2>/dev/null)" ]]; then
        print_status "WARN" "No server configurations found. Run 'protonvpn update' first."
        return 1
    fi
    
    case "$list_type" in
        "countries")
            print_status "INFO" "Available countries:"
            local countries=()
            while IFS= read -r -d '' config; do
                local info
                info=$(parse_config_info "$config")
                local country
                country=$(echo "$info" | cut -d'|' -f3)
                countries+=("$country")
            done < <(find "$CONFIGS_DIR" -name "*.conf" -print0)
            
            # Sort and deduplicate countries
            printf '%s\n' "${countries[@]}" | sort -u | while read -r country; do
                printf "  %s\n" "$country"
            done
            ;;
        *)
            print_status "INFO" "Available servers:"
            printf "%-20s %-15s %-10s %s\n" "SERVER" "ENDPOINT" "COUNTRY" "STATUS"
            printf "%-20s %-15s %-10s %s\n" "------" "--------" "-------" "------"
            
            local current_server
            current_server=$(get_connection_status)
            
            while IFS= read -r -d '' config; do
                local info
                info=$(parse_config_info "$config")
                IFS='|' read -r server_name endpoint country <<< "$info"
                
                local status="Available"
                if [[ "$server_name" == *"$current_server"* ]] && [[ -n "$current_server" ]]; then
                    status="${GREEN}Connected${NC}"
                fi
                
                printf "%-20s %-15s %-10s %s\n" "$server_name" "$endpoint" "$country" "$status"
            done < <(find "$CONFIGS_DIR" -name "*.conf" -print0 | sort -z)
            ;;
    esac
}

#######################################
# Test server speed/latency
# Arguments:
#   $1 - server endpoint IP
# Returns:
#   Latency in milliseconds or 9999 if unreachable
#######################################
test_server_speed() {
    local endpoint="$1"
    local latency
    
    print_status "DEBUG" "Testing latency to $endpoint..."
    
    # Use ping to test latency (3 packets, 2 second timeout)
    if latency=$(ping -c 3 -W 2 "$endpoint" 2>/dev/null | tail -1 | awk -F '/' '{print $5}' 2>/dev/null); then
        if [[ -n "$latency" ]]; then
            printf "%.0f" "$latency"
            return 0
        fi
    fi
    
    # If ping fails, return high latency
    echo "9999"
    return 1
}

#######################################
# Find fastest server
# Arguments:
#   $1 - optional country filter
# Returns:
#   Path to fastest server config
#######################################
find_fastest_server() {
    local country_filter="${1:-}"
    local best_config=""
    local best_latency=9999
    local tested_count=0
    local max_tests=10  # Limit tests for performance
    
    print_status "INFO" "Finding fastest server${country_filter:+ in $country_filter}..."
    
    # Create array of matching configs
    local configs=()
    while IFS= read -r -d '' config; do
        if [[ -n "$country_filter" ]]; then
            local info
            info=$(parse_config_info "$config")
            local country
            country=$(echo "$info" | cut -d'|' -f3)
            if [[ "$country" == "$country_filter" ]]; then
                configs+=("$config")
            fi
        else
            configs+=("$config")
        fi
    done < <(find "$CONFIGS_DIR" -name "*.conf" -print0)
    
    if [[ ${#configs[@]} -eq 0 ]]; then
        print_status "ERROR" "No servers found${country_filter:+ for country $country_filter}"
        return 1
    fi
    
    # Shuffle array to test random subset
    local shuffled_configs=()
    while [[ ${#configs[@]} -gt 0 ]]; do
        local index=$((RANDOM % ${#configs[@]}))
        shuffled_configs+=("${configs[$index]}")
        unset 'configs[$index]'
        configs=("${configs[@]}")  # Re-index array
    done
    
    # Test servers
    for config in "${shuffled_configs[@]}"; do
        if [[ $tested_count -ge $max_tests ]]; then
            break
        fi
        
        local info
        info=$(parse_config_info "$config")
        local server_name endpoint
        IFS='|' read -r server_name endpoint _ <<< "$info"
        
        local latency
        latency=$(test_server_speed "$endpoint")
        
        print_status "DEBUG" "Server $server_name: ${latency}ms"
        
        if (( $(echo "$latency < $best_latency" | bc -l 2>/dev/null || echo "$latency -lt $best_latency") )); then
            best_latency=$latency
            best_config="$config"
        fi
        
        ((tested_count++))
    done
    
    if [[ -n "$best_config" ]]; then
        local info
        info=$(parse_config_info "$best_config")
        local server_name
        server_name=$(echo "$info" | cut -d'|' -f1)
        print_status "SUCCESS" "Fastest server: $server_name (${best_latency}ms)"
        echo "$best_config"
        return 0
    else
        print_status "ERROR" "Could not find accessible server"
        return 1
    fi
}

#######################################
# Connect to VPN server
# Arguments:
#   $1 - optional country or specific server
#######################################
connect_vpn() {
    local target="${1:-}"
    local config_file=""
    
    # Check if already connected
    local current_connection
    current_connection=$(get_connection_status)
    if [[ -n "$current_connection" ]]; then
        print_status "INFO" "Already connected to $current_connection"
        print_status "INFO" "Disconnect first with: protonvpn disconnect"
        return 1
    fi
    
    # Determine which server to connect to
    if [[ -z "$target" ]]; then
        # Auto-select fastest server
        config_file=$(find_fastest_server)
    elif [[ -f "$CONFIGS_DIR/wg-${target}.conf" ]]; then
        # Specific server by name
        config_file="$CONFIGS_DIR/wg-${target}.conf"
        print_status "INFO" "Using specific server: $target"
    elif [[ ${#target} -eq 2 ]] && [[ "$target" =~ ^[A-Z]{2}$ ]]; then
        # Country code - find fastest in country
        config_file=$(find_fastest_server "$target")
    else
        # Try to find server containing the target string
        local matches
        mapfile -t matches < <(find "$CONFIGS_DIR" -name "*${target}*" -name "*.conf")
        
        if [[ ${#matches[@]} -eq 1 ]]; then
            config_file="${matches[0]}"
        elif [[ ${#matches[@]} -gt 1 ]]; then
            print_status "ERROR" "Multiple servers match '$target':"
            for match in "${matches[@]}"; do
                local info
                info=$(parse_config_info "$match")
                printf "  %s\n" "$(echo "$info" | cut -d'|' -f1)" >&2
            done
            return 1
        else
            print_status "ERROR" "No server found matching '$target'"
            return 1
        fi
    fi
    
    if [[ -z "$config_file" ]]; then
        print_status "ERROR" "Failed to determine server configuration"
        return 1
    fi
    
    # Extract interface name from config file
    local interface_name
    interface_name=$(basename "$config_file" .conf)
    
    print_status "INFO" "Connecting to $(parse_config_info "$config_file" | cut -d'|' -f1)..."
    
    if [[ "$DRY_RUN" == "true" ]]; then
        print_status "INFO" "DRY RUN: Would execute: wg-quick up $config_file"
        return 0
    fi
    
    # Connect using wg-quick
    if wg-quick up "$config_file" >/dev/null 2>&1; then
        # Update state file
        jq --arg conn "$(parse_config_info "$config_file" | cut -d'|' -f1)" \
           '.current_connection = $conn' "$STATE_FILE" > "${STATE_FILE}.tmp" && \
        mv "${STATE_FILE}.tmp" "$STATE_FILE"
        
        print_status "SUCCESS" "Connected to $(parse_config_info "$config_file" | cut -d'|' -f1)"
        
        # Show connection info
        show_status
    else
        print_status "ERROR" "Failed to connect to VPN"
        return 1
    fi
}

#######################################
# Disconnect from VPN
#######################################
disconnect_vpn() {
    local current_connection
    current_connection=$(get_connection_status)
    
    if [[ -z "$current_connection" ]]; then
        print_status "INFO" "Not connected to any VPN server"
        return 0
    fi
    
    print_status "INFO" "Disconnecting from $current_connection..."
    
    if [[ "$DRY_RUN" == "true" ]]; then
        print_status "INFO" "DRY RUN: Would disconnect from all WireGuard interfaces"
        return 0
    fi
    
    # Disconnect all WireGuard interfaces
    local interfaces
    mapfile -t interfaces < <(wg show 2>/dev/null | grep -E '^interface:' | cut -d' ' -f2 || true)
    
    local disconnected=false
    for interface in "${interfaces[@]}"; do
        if wg-quick down "$interface" >/dev/null 2>&1; then
            print_status "SUCCESS" "Disconnected from $interface"
            disconnected=true
        fi
    done
    
    if [[ "$disconnected" == "true" ]]; then
        # Update state file
        jq '.current_connection = ""' "$STATE_FILE" > "${STATE_FILE}.tmp" && \
        mv "${STATE_FILE}.tmp" "$STATE_FILE"
    else
        print_status "WARN" "No active connections found to disconnect"
    fi
}

#######################################
# Show current connection status
#######################################
show_status() {
    local current_connection
    current_connection=$(get_connection_status)
    
    if [[ -n "$current_connection" ]]; then
        print_status "INFO" "Status: ${GREEN}Connected${NC}"
        printf "Server: %s\n" "$current_connection" >&2
        
        # Show WireGuard interface details
        if command_exists wg && [[ "$VERBOSE" == "true" ]]; then
            print_status "DEBUG" "WireGuard interface details:"
            wg show 2>/dev/null | while IFS= read -r line; do
                printf "  %s\n" "$line" >&2
            done
        fi
        
        # Test current connection
        if command_exists curl; then
            local public_ip
            if public_ip=$(curl -s --max-time 5 ifconfig.me 2>/dev/null); then
                printf "Public IP: %s\n" "$public_ip" >&2
            fi
        fi
    else
        print_status "INFO" "Status: ${RED}Disconnected${NC}"
    fi
}

#######################################
# Update server configurations
#######################################
update_configs() {
    if [[ ! -f "$GENERATOR_SCRIPT" ]]; then
        print_status "ERROR" "Generator script not found: $GENERATOR_SCRIPT"
        print_status "INFO" "Please ensure the ProtonVPN generator script is available"
        return 1
    fi
    
    print_status "INFO" "Updating server configurations..."
    print_status "WARN" "This may take a while due to API rate limiting..."
    
    if [[ "$DRY_RUN" == "true" ]]; then
        print_status "INFO" "DRY RUN: Would run generator script to update configs"
        return 0
    fi
    
    # Update OUTPUT_DIR in generator script to point to our configs directory
    local temp_script="${CONFIG_DIR}/temp_generator.py"
    sed "s|OUTPUT_DIR = \"./configs\"|OUTPUT_DIR = \"$CONFIGS_DIR\"|" "$GENERATOR_SCRIPT" > "$temp_script"
    
    if python3 "$temp_script"; then
        print_status "SUCCESS" "Server configurations updated"
        
        # Update state file with last update time
        jq --arg time "$(date +%s)" '.last_update = ($time | tonumber)' "$STATE_FILE" > "${STATE_FILE}.tmp" && \
        mv "${STATE_FILE}.tmp" "$STATE_FILE"
        
        # Clean up
        rm -f "$temp_script"
    else
        print_status "ERROR" "Failed to update configurations"
        rm -f "$temp_script"
        return 1
    fi
}

#######################################
# Run speed tests on available servers
#######################################
run_speedtest() {
    local country_filter="${1:-}"
    
    print_status "INFO" "Running speed tests${country_filter:+ for $country_filter}..."
    
    # Create results array
    declare -a results
    local count=0
    
    while IFS= read -r -d '' config; do
        local info
        info=$(parse_config_info "$config")
        IFS='|' read -r server_name endpoint country <<< "$info"
        
        # Apply country filter if specified
        if [[ -n "$country_filter" ]] && [[ "$country" != "$country_filter" ]]; then
            continue
        fi
        
        local latency
        latency=$(test_server_speed "$endpoint")
        
        results+=("$latency|$server_name|$endpoint|$country")
        ((count++))
        
        printf "." >&2  # Progress indicator
    done < <(find "$CONFIGS_DIR" -name "*.conf" -print0)
    
    printf "\n" >&2
    
    if [[ $count -eq 0 ]]; then
        print_status "WARN" "No servers found to test"
        return 1
    fi
    
    # Sort results by latency and display
    printf "\n%-20s %-15s %-10s %s\n" "SERVER" "ENDPOINT" "COUNTRY" "LATENCY" >&2
    printf "%-20s %-15s %-10s %s\n" "------" "--------" "-------" "-------" >&2
    
    printf '%s\n' "${results[@]}" | sort -t'|' -k1 -n | while IFS='|' read -r latency server_name endpoint country; do
        local latency_display
        if [[ "$latency" == "9999" ]]; then
            latency_display="${RED}Timeout${NC}"
        else
            latency_display="${latency}ms"
        fi
        printf "%-20s %-15s %-10s %s\n" "$server_name" "$endpoint" "$country" "$latency_display" >&2
    done
}

#######################################
# Show help message
#######################################
show_help() {
    cat >&2 << 'EOF'
ProtonVPN CLI - WireGuard VPN Manager

USAGE:
    protonvpn <command> [options]

COMMANDS:
    connect [target]       Connect to VPN server
                          - No target: auto-select fastest server
                          - Country code (e.g., US, CH): fastest in country  
                          - Server name: specific server
    
    disconnect            Disconnect from current VPN server
    
    status               Show current connection status
    
    list [countries]     List available servers or countries
    
    update               Update server configurations from ProtonVPN
    
    speedtest [country]  Test latency of available servers
    
    help                 Show this help message

GLOBAL OPTIONS:
    -v, --verbose        Enable verbose output
    -n, --dry-run        Show what would be done without executing
    -h, --help           Show help message

EXAMPLES:
    protonvpn connect              # Connect to fastest available server
    protonvpn connect US           # Connect to fastest US server
    protonvpn connect CH-01        # Connect to specific server
    protonvpn list countries       # List available countries
    protonvpn speedtest CH         # Test Swiss servers
    protonvpn status               # Show connection status
    protonvpn disconnect           # Disconnect from VPN

CONFIGURATION:
    Config files: ~/.config/protonvpn/
    WireGuard configs: ~/.config/protonvpn/configs/
    
REQUIREMENTS:
    - wireguard-tools (wg, wg-quick)
    - jq (JSON processing)
    - curl (IP detection)
    - bc (calculations)
    - Root privileges for connection operations

EOF
}

#######################################
# Main function
#######################################
main() {
    # Parse global options first
    while [[ $# -gt 0 ]]; do
        case $1 in
            -v|--verbose)
                VERBOSE=true
                shift
                ;;
            -n|--dry-run)
                DRY_RUN=true
                shift
                ;;
            -h|--help)
                show_help
                exit 0
                ;;
            -*)
                print_status "ERROR" "Unknown option: $1"
                show_help
                exit 1
                ;;
            *)
                break
                ;;
        esac
    done
    
    # Initialize configuration
    init_config
    
    # Get command
    local command="${1:-help}"
    shift || true
    
    # Check dependencies for most commands
    if ! check_dependencies "$command"; then
        exit 1
    fi
    
    # Execute command
    case "$command" in
        "connect"|"c")
            connect_vpn "$@"
            ;;
        "disconnect"|"d")
            disconnect_vpn
            ;;
        "status"|"s")
            show_status
            ;;
        "list"|"l")
            list_servers "$@"
            ;;
        "update"|"u")
            update_configs
            ;;
        "speedtest"|"speed"|"test")
            run_speedtest "$@"
            ;;
        "split")
            case "${1:-}" in
                "enable"|"disable")
                    jq --arg enabled "$([[ "$1" == "enable" ]] && echo true || echo false)" '.enabled = ($enabled == "true")' "$SPLIT_TUNNEL_CONFIG" > "${SPLIT_TUNNEL_CONFIG}.tmp" && mv "${SPLIT_TUNNEL_CONFIG}.tmp" "$SPLIT_TUNNEL_CONFIG"
                    print_status "SUCCESS" "Split tunneling $1d"
                    ;;
                "add-local")
                    local network="${2:-}"
                    if [[ -n "$network" ]]; thenupda
                        jq --arg net "$network" '.local_networks += [$net] | .local_networks |= unique' "$SPLIT_TUNNEL_CONFIG" > "${SPLIT_TUNNEL_CONFIG}.tmp" && mv "${SPLIT_TUNNEL_CONFIG}.tmp" "$SPLIT_TUNNEL_CONFIG"
                        print_status "SUCCESS" "Added local network: $network"
                    fi
                    ;;
                "list")
                    print_status "INFO" "Split tunneling configuration:"
                    jq '.' "$SPLIT_TUNNEL_CONFIG"
                    ;;
                *)
                    print_status "ERROR" "Unknown split command. Use: enable|disable|add-local|list"
                    ;;
            esac
            ;;
        "help"|"h"|"--help")
            show_help
            ;;
        *)
            print_status "ERROR" "Unknown command: $command"
            show_help
            exit 1
            ;;
    esac
}

# Execute main function if script is run directly
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
fi
