#!/usr/bin/env bash
# Configure Cloudflare Tunnel Routes and DNS Records via API
# Usage: ./configure-cloudflare-api.sh
# Requires: CLOUDFLARE_API_TOKEN and CLOUDFLARE_ZONE_ID environment variables

set -euo pipefail

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

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

info() { echo -e "${GREEN}[INFO]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
error() { echo -e "${RED}[ERROR]${NC} $1"; }
debug() { echo -e "${BLUE}[DEBUG]${NC} $1"; }

# Check for required tools
if ! command -v curl >/dev/null 2>&1; then
    error "curl is required but not installed"
    exit 1
fi

if ! command -v jq >/dev/null 2>&1; then
    error "jq is required but not installed. Install with: apt-get install jq"
    exit 1
fi

# Load environment variables
if [[ -f "$SCRIPT_DIR/../.env" ]]; then
    source "$SCRIPT_DIR/../.env"
fi

# Cloudflare API configuration (support multiple naming conventions)
CLOUDFLARE_API_TOKEN="${CLOUDFLARE_API_TOKEN:-}"
CLOUDFLARE_ZONE_ID="${CLOUDFLARE_ZONE_ID:-}"
CLOUDFLARE_ACCOUNT_ID="${CLOUDFLARE_ACCOUNT_ID:-}"
CLOUDFLARE_EMAIL="${CLOUDFLARE_EMAIL:-}"
CLOUDFLARE_API_KEY="${CLOUDFLARE_API_KEY:-}"
DOMAIN="${DOMAIN:-${CLOUDFLARE_DOMAIN:-d-bis.org}}"

# Tunnel configuration (support multiple naming conventions)
# Prefer JWT token from installed service, then env vars
INSTALLED_TOKEN=""
if command -v ssh >/dev/null 2>&1; then
    INSTALLED_TOKEN=$(ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no root@${PROXMOX_HOST:-192.168.11.10} \
        "pct exec 102 -- cat /etc/systemd/system/cloudflared.service 2>/dev/null | grep -o 'tunnel run --token [^ ]*' | cut -d' ' -f3" 2>/dev/null || echo "")
fi

TUNNEL_TOKEN="${INSTALLED_TOKEN:-${TUNNEL_TOKEN:-${CLOUDFLARE_TUNNEL_TOKEN:-eyJhIjoiNTJhZDU3YTcxNjcxYzVmYzAwOWVkZjA3NDQ2NTgxOTYiLCJ0IjoiMTBhYjIyZGEtOGVhMy00ZTJlLWE4OTYtMjdlY2UyMjExYTA1IiwicyI6IlptRXlOMkkyTVRrdE1EZzFNeTAwTkRBNExXSXhaalF0Wm1KaE5XVmpaVEEzTVdGbCJ9}}}"

# RPC endpoint configuration
# Public endpoints route to VMID 2502 (NO JWT authentication)
# Private endpoints route to VMID 2501 (JWT authentication required)
declare -A RPC_ENDPOINTS=(
    [rpc-http-pub]="https://192.168.11.252:443"
    [rpc-ws-pub]="https://192.168.11.252:443"
    [rpc-http-prv]="https://192.168.11.251:443"
    [rpc-ws-prv]="https://192.168.11.251:443"
)

# API base URLs
CF_API_BASE="https://api.cloudflare.com/client/v4"
CF_ZERO_TRUST_API="https://api.cloudflare.com/client/v4/accounts"

# Function to make Cloudflare API request
cf_api_request() {
    local method="$1"
    local endpoint="$2"
    local data="${3:-}"
    
    local url="${CF_API_BASE}${endpoint}"
    local headers=()
    
    if [[ -n "$CLOUDFLARE_API_TOKEN" ]]; then
        headers+=("-H" "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}")
    elif [[ -n "$CLOUDFLARE_API_KEY" ]]; then
        # Global API Keys are typically 40 chars, API Tokens are longer
        # If no email provided, assume it's an API Token
        if [[ -z "$CLOUDFLARE_EMAIL" ]] || [[ ${#CLOUDFLARE_API_KEY} -gt 50 ]]; then
            headers+=("-H" "Authorization: Bearer ${CLOUDFLARE_API_KEY}")
        else
            headers+=("-H" "X-Auth-Email: ${CLOUDFLARE_EMAIL}")
            headers+=("-H" "X-Auth-Key: ${CLOUDFLARE_API_KEY}")
        fi
    else
        error "Cloudflare API credentials not found!"
        error "Set CLOUDFLARE_API_TOKEN or CLOUDFLARE_EMAIL + CLOUDFLARE_API_KEY"
        exit 1
    fi
    
    headers+=("-H" "Content-Type: application/json")
    
    local response
    if [[ -n "$data" ]]; then
        response=$(curl -s -X "$method" "$url" "${headers[@]}" -d "$data")
    else
        response=$(curl -s -X "$method" "$url" "${headers[@]}")
    fi
    
    # Check if response is valid JSON
    if ! echo "$response" | jq -e . >/dev/null 2>&1; then
        error "Invalid JSON response from API"
        debug "Response: $response"
        return 1
    fi
    
    # Check for API errors
    local success=$(echo "$response" | jq -r '.success // false' 2>/dev/null)
    if [[ "$success" != "true" ]]; then
        local errors=$(echo "$response" | jq -r '.errors[]?.message // .error // "Unknown error"' 2>/dev/null | head -3)
        if [[ -z "$errors" ]]; then
            errors="API request failed (check response)"
        fi
        error "API request failed: $errors"
        debug "Response: $response"
        return 1
    fi
    
    echo "$response"
}

# Function to get zone ID from domain
get_zone_id() {
    if [[ -n "$CLOUDFLARE_ZONE_ID" ]]; then
        echo "$CLOUDFLARE_ZONE_ID"
        return 0
    fi
    
    info "Getting zone ID for domain: $DOMAIN"
    local response=$(cf_api_request "GET" "/zones?name=${DOMAIN}")
    local zone_id=$(echo "$response" | jq -r '.result[0].id // empty')
    
    if [[ -z "$zone_id" ]]; then
        error "Zone not found for domain: $DOMAIN"
        exit 1
    fi
    
    info "Zone ID: $zone_id"
    echo "$zone_id"
}

# Function to get account ID (needed for Zero Trust API)
get_account_id() {
    info "Getting account ID..."
    
    # Try to get from token verification
    local response=$(cf_api_request "GET" "/user/tokens/verify")
    local account_id=$(echo "$response" | jq -r '.result.id // empty')
    
    if [[ -z "$account_id" ]]; then
        # Try alternative: get from accounts list
        response=$(cf_api_request "GET" "/accounts")
        account_id=$(echo "$response" | jq -r '.result[0].id // empty')
    fi
    
    if [[ -z "$account_id" ]]; then
        # Last resort: try to get from zone
        local zone_id=$(get_zone_id)
        response=$(cf_api_request "GET" "/zones/${zone_id}")
        account_id=$(echo "$response" | jq -r '.result.account.id // empty')
    fi
    
    if [[ -z "$account_id" ]]; then
        error "Could not determine account ID"
        error "You may need to specify CLOUDFLARE_ACCOUNT_ID in .env file"
        exit 1
    fi
    
    info "Account ID: $account_id"
    echo "$account_id"
}

# Function to extract tunnel ID from token
get_tunnel_id_from_token() {
    local token="$1"
    
    # Check if it's a JWT token (has dots)
    if [[ "$token" == *.*.* ]]; then
        # Decode JWT token (basic base64 decode of payload)
        local payload=$(echo "$token" | cut -d'.' -f2)
        # Add padding if needed
        local padding=$((4 - ${#payload} % 4))
        if [[ $padding -ne 4 ]]; then
            payload="${payload}$(printf '%*s' $padding | tr ' ' '=')"
        fi
        # Decode and extract tunnel ID (field 't' contains tunnel ID)
        if command -v python3 >/dev/null 2>&1; then
            echo "$payload" | python3 -c "import sys, base64, json; payload=sys.stdin.read().strip(); padding=4-len(payload)%4; payload+=('='*padding if padding<4 else ''); data=json.loads(base64.b64decode(payload)); print(data.get('t', ''))" 2>/dev/null || echo ""
        else
            echo "$payload" | base64 -d 2>/dev/null | jq -r '.t // empty' 2>/dev/null || echo ""
        fi
    else
        # Not a JWT token, return empty
        echo ""
    fi
}

# Function to get tunnel ID
get_tunnel_id() {
    local account_id="$1"
    local token="$2"
    
    # Try to extract from JWT token first
    local tunnel_id=$(get_tunnel_id_from_token "$token")
    if [[ -n "$tunnel_id" ]]; then
        info "Tunnel ID from token: $tunnel_id"
        echo "$tunnel_id"
        return 0
    fi
    
    # Fallback: list tunnels and find the one
    warn "Could not extract tunnel ID from token, listing tunnels..."
    local response=$(cf_api_request "GET" "/accounts/${account_id}/cfd_tunnel" 2>/dev/null)
    
    if [[ -z "$response" ]]; then
        error "Failed to list tunnels. Check API credentials."
        exit 1
    fi
    
    local tunnel_id=$(echo "$response" | jq -r '.result[0].id // empty' 2>/dev/null)
    
    if [[ -z "$tunnel_id" ]]; then
        error "Could not find tunnel ID"
        debug "Response: $response"
        exit 1
    fi
    
    info "Tunnel ID: $tunnel_id"
    echo "$tunnel_id"
}

# Function to get tunnel name
get_tunnel_name() {
    local account_id="$1"
    local tunnel_id="$2"
    
    local response=$(cf_api_request "GET" "/accounts/${account_id}/cfd_tunnel/${tunnel_id}")
    local tunnel_name=$(echo "$response" | jq -r '.result.name // empty')
    echo "$tunnel_name"
}

# Function to configure tunnel routes
configure_tunnel_routes() {
    local account_id="$1"
    local tunnel_id="$2"
    local tunnel_name="$3"
    
    info "Configuring tunnel routes for: $tunnel_name"
    
    # Build ingress rules array
    local ingress_array="["
    local first=true
    
    for subdomain in "${!RPC_ENDPOINTS[@]}"; do
        local service="${RPC_ENDPOINTS[$subdomain]}"
        local hostname="${subdomain}.${DOMAIN}"
        
        if [[ "$first" == "true" ]]; then
            first=false
        else
            ingress_array+=","
        fi
        
        # Determine if WebSocket
        local is_ws=false
        if [[ "$subdomain" == *"ws"* ]]; then
            is_ws=true
        fi
        
        # Build ingress rule
        # Add noTLSVerify to skip certificate validation (certificates don't have IP SANs)
        if [[ "$is_ws" == "true" ]]; then
            ingress_array+="{\"hostname\":\"${hostname}\",\"service\":\"${service}\",\"originRequest\":{\"httpHostHeader\":\"${hostname}\",\"noTLSVerify\":true}}"
        else
            ingress_array+="{\"hostname\":\"${hostname}\",\"service\":\"${service}\",\"originRequest\":{\"noTLSVerify\":true}}"
        fi
        
        info "  Adding route: ${hostname} → ${service}"
    done
    
    # Add catch-all (must be last)
    ingress_array+=",{\"service\":\"http_status:404\"}]"
    
    # Create config JSON
    local config_data=$(echo "$ingress_array" | jq -c '{
        config: {
            ingress: .
        }
    }')
    
    info "Updating tunnel configuration..."
    local response=$(cf_api_request "PUT" "/accounts/${account_id}/cfd_tunnel/${tunnel_id}/configurations" "$config_data")
    
    if echo "$response" | jq -e '.success' >/dev/null 2>&1; then
        info "✓ Tunnel routes configured successfully"
    else
        local errors=$(echo "$response" | jq -r '.errors[]?.message // "Unknown error"' | head -3)
        error "Failed to configure tunnel routes: $errors"
        debug "Response: $response"
        return 1
    fi
}

# Function to create or update DNS record
create_or_update_dns_record() {
    local zone_id="$1"
    local name="$2"
    local target="$3"
    local proxied="${4:-true}"
    
    # Check if record exists
    local response=$(cf_api_request "GET" "/zones/${zone_id}/dns_records?name=${name}.${DOMAIN}&type=CNAME")
    local record_id=$(echo "$response" | jq -r '.result[0].id // empty')
    
    local data=$(jq -n \
        --arg name "${name}.${DOMAIN}" \
        --arg target "$target" \
        --argjson proxied "$proxied" \
        '{
            type: "CNAME",
            name: $name,
            content: $target,
            proxied: $proxied,
            ttl: 1
        }')
    
    if [[ -n "$record_id" ]]; then
        info "  Updating existing DNS record: ${name}.${DOMAIN}"
        response=$(cf_api_request "PUT" "/zones/${zone_id}/dns_records/${record_id}" "$data")
    else
        info "  Creating DNS record: ${name}.${DOMAIN}"
        response=$(cf_api_request "POST" "/zones/${zone_id}/dns_records" "$data")
    fi
    
    if echo "$response" | jq -e '.success' >/dev/null 2>&1; then
        info "    ✓ DNS record configured"
    else
        error "    ✗ Failed to configure DNS record"
        return 1
    fi
}

# Function to configure DNS records
configure_dns_records() {
    local zone_id="$1"
    local tunnel_id="$2"
    local tunnel_target="${tunnel_id}.cfargotunnel.com"
    
    info "Configuring DNS records..."
    info "Tunnel target: $tunnel_target"
    
    for subdomain in "${!RPC_ENDPOINTS[@]}"; do
        create_or_update_dns_record "$zone_id" "$subdomain" "$tunnel_target" "true"
    done
}

# Main execution
main() {
    info "Cloudflare API Configuration Script"
    info "===================================="
    echo ""
    
    # Validate credentials
    if [[ -z "$CLOUDFLARE_API_TOKEN" ]] && [[ -z "$CLOUDFLARE_EMAIL" ]] && [[ -z "$CLOUDFLARE_API_KEY" ]]; then
        error "Cloudflare API credentials required!"
        echo ""
        echo "Set one of:"
        echo "  export CLOUDFLARE_API_TOKEN='your-api-token'"
        echo "  OR"
        echo "  export CLOUDFLARE_EMAIL='your-email@example.com'"
        echo "  export CLOUDFLARE_API_KEY='your-api-key'"
        echo ""
        echo "You can also create a .env file in the project root with these variables."
        exit 1
    fi
    
    # If API_KEY is provided but no email, we need email for Global API Key
    if [[ -n "$CLOUDFLARE_API_KEY" ]] && [[ -z "$CLOUDFLARE_EMAIL" ]] && [[ -z "$CLOUDFLARE_API_TOKEN" ]]; then
        error "CLOUDFLARE_API_KEY requires CLOUDFLARE_EMAIL"
        error "Please add CLOUDFLARE_EMAIL to your .env file"
        error ""
        error "OR create an API Token instead:"
        error "  1. Go to: https://dash.cloudflare.com/profile/api-tokens"
        error "  2. Create token with: Zone:DNS:Edit, Account:Cloudflare Tunnel:Edit"
        error "  3. Set CLOUDFLARE_API_TOKEN in .env"
        exit 1
    fi
    
    # Get zone ID
    local zone_id=$(get_zone_id)
    
    # Get account ID
    local account_id="${CLOUDFLARE_ACCOUNT_ID:-}"
    if [[ -z "$account_id" ]]; then
        account_id=$(get_account_id)
    else
        info "Using provided Account ID: $account_id"
    fi
    
    # Get tunnel ID - try from .env first, then extraction, then API
    local tunnel_id="${CLOUDFLARE_TUNNEL_ID:-}"
    
    # If not in .env, try to extract from JWT token
    if [[ -z "$tunnel_id" ]] && [[ "$TUNNEL_TOKEN" == *.*.* ]]; then
        local payload=$(echo "$TUNNEL_TOKEN" | cut -d'.' -f2)
        local padding=$((4 - ${#payload} % 4))
        if [[ $padding -ne 4 ]]; then
            payload="${payload}$(printf '%*s' $padding | tr ' ' '=')"
        fi
        if command -v python3 >/dev/null 2>&1; then
            tunnel_id=$(echo "$payload" | python3 -c "import sys, base64, json; payload=sys.stdin.read().strip(); padding=4-len(payload)%4; payload+=('='*padding if padding<4 else ''); data=json.loads(base64.b64decode(payload)); print(data.get('t', ''))" 2>/dev/null || echo "")
        fi
    fi
    
    # If extraction failed, try API (but don't fail if API doesn't work)
    if [[ -z "$tunnel_id" ]]; then
        tunnel_id=$(get_tunnel_id "$account_id" "$TUNNEL_TOKEN" 2>/dev/null || echo "")
    fi
    
    if [[ -z "$tunnel_id" ]]; then
        error "Could not determine tunnel ID"
        error "Please set CLOUDFLARE_TUNNEL_ID in .env file"
        error "Or ensure API credentials are valid to fetch it automatically"
        exit 1
    fi
    
    info "Using Tunnel ID: $tunnel_id"
    local tunnel_name=$(get_tunnel_name "$account_id" "$tunnel_id" 2>/dev/null || echo "tunnel-${tunnel_id:0:8}")
    
    echo ""
    info "Configuration Summary:"
    echo "  Domain: $DOMAIN"
    echo "  Zone ID: $zone_id"
    echo "  Account ID: $account_id"
    echo "  Tunnel: $tunnel_name (ID: $tunnel_id)"
    echo ""
    
    # Configure tunnel routes
    echo "=========================================="
    info "Step 1: Configuring Tunnel Routes"
    echo "=========================================="
    configure_tunnel_routes "$account_id" "$tunnel_id" "$tunnel_name"
    
    echo ""
    echo "=========================================="
    info "Step 2: Configuring DNS Records"
    echo "=========================================="
    configure_dns_records "$zone_id" "$tunnel_id"
    
    echo ""
    echo "=========================================="
    info "Configuration Complete!"
    echo "=========================================="
    echo ""
    info "Next steps:"
    echo "  1. Wait 1-2 minutes for DNS propagation"
    echo "  2. Test endpoints:"
    echo "     curl https://rpc-http-pub.d-bis.org/health"
    echo "  3. Verify in Cloudflare Dashboard:"
    echo "     - Zero Trust → Networks → Tunnels → Check routes"
    echo "     - DNS → Records → Verify CNAME records"
}

# Run main function
main

