#!/bin/bash

# WordPress Version Check and Update Script
# Copyright 2024 A.D. Federated Computer, Inc.
# saint@federated.computer
# ---------------------------------------
# This script checks the running WordPress version in a Docker container and updates
# the IMAGE_VERSION in the .env file to match if needed.
#
# Version Detection Methods (in order):
# 1. WP-CLI: Uses 'wp core version'
# 2. PHP Direct: Uses wp_eval to get bloginfo('version')
# 3. File Check: Reads version from /bitnami/wordpress/wp-includes/version.php
# 4. Database Primary: Reads from wp_options._transient_wp_core_block_css_files
# 5. Database Backup: Reads from wp_options._site_transient_update_core
#
# The script will:
# - Use the highest valid version found from any method
# - Check if that version is newer than the one in .env
# - If newer, look for matching Docker image on Docker Hub
# - If exact version not found, use next newer version or closest older version
# - Update .env with the new version and add IMAGE_VERSION_HOLD=true if needed

# Enable debug output
## set -x

# Set your container name and .env file path
CONTAINER_NAME="wordpress"
APP_DIR="/federated/apps/$CONTAINER_NAME"
ENV_FILE="$APP_DIR/.env"
DOCKER_COMPOSE_FILE="$APP_DIR/docker-compose.yml"

# Function to validate version string
is_valid_version() {
    local version=$1
    # Allow x, x.y, x.y.z, or x.y.z.a where all parts are integers
    [[ $version =~ ^[0-9]+(\.[0-9]+)*$ ]] || return 1
    # Count parts
    local parts
    IFS='.' read -ra parts <<< "$version"
    # Allow 1 to 4 parts
    [ ${#parts[@]} -le 4 ] || return 1
    return 0
}

echo "Starting WordPress version check and update script..."

# Function to get version from different sources
get_wp_version() {
    local wpcli_version php_version file_version db_version admin_version highest_version

    # 1. Try wp-cli version
    wpcli_version=$(docker exec "$CONTAINER_NAME" wp core version --allow-root) || true
    if ! is_valid_version "$wpcli_version"; then wpcli_version=""; fi

    # 2. Try version.php direct access
    php_version=$(docker exec "$CONTAINER_NAME" wp eval 'echo get_bloginfo("version");' --allow-root) || true
    if ! is_valid_version "$php_version"; then php_version=""; fi

    # 3. Try direct file content check (fixed quotes)
    file_version=$(docker exec "$CONTAINER_NAME" bash -c "grep '\\\$wp_version = ' /bitnami/wordpress/wp-includes/version.php | sed -n 's/.*= .\\([0-9.]*\\).*/\\1/p'") || true
    if [ -z "$file_version" ]; then
        file_version=$(docker exec "$CONTAINER_NAME" bash -c "grep '\\\$wp_version = ' /opt/bitnami/wordpress/wp-includes/version.php | sed -n 's/.*= .\\([0-9.]*\\).*/\\1/p'") || true
    fi
    if ! is_valid_version "$file_version"; then file_version=""; fi

    # 4. Try database version through wp-cli with socket path
    db_version=$(docker exec "$CONTAINER_NAME" bash -c '
        is_valid_version() {
            local version=$1
            # Allow x, x.y, x.y.z, or x.y.z.a formats
            [[ $version =~ ^[0-9]+(\.[0-9]+)*$ ]] || return 1
            # Count parts and ensure not more than 4
            local parts
            IFS='.' read -ra parts <<< "$version"
            [ ${#parts[@]} -le 4 ] || return 1
            return 0
        }

        if [ -f /opt/bitnami/mysql/bin/mariadb ]; then
            MYSQL_CMD="/opt/bitnami/mysql/bin/mariadb"
        else
            MYSQL_CMD="/opt/bitnami/mysql/bin/mysql"
        fi
        export MYSQL_PWD="$WORDPRESS_DATABASE_PASSWORD"

        # First try _transient_wp_core_block_css_files as it has simpler structure
        version=$($MYSQL_CMD -u "$WORDPRESS_DATABASE_USER" \
                  -h "$WORDPRESS_DATABASE_HOST" \
                  --skip-ssl \
                  "$WORDPRESS_DATABASE_NAME" \
                  -N -e "SELECT option_value FROM wp_options WHERE option_name = \"_transient_wp_core_block_css_files\";" | \
                  sed -n "s/.*s:7:\"version\";s:[0-9]*:\"\([0-9][0-9.]*\)\".*/\1/p")

        # Validate first version before accepting it
        if ! is_valid_version "$version"; then
            # If first attempt fails or gives invalid version, try _site_transient_update_core
            version=$($MYSQL_CMD -u "$WORDPRESS_DATABASE_USER" \
                      -h "$WORDPRESS_DATABASE_HOST" \
                      --skip-ssl \
                      "$WORDPRESS_DATABASE_NAME" \
                      -N -e "SELECT option_value FROM wp_options WHERE option_name = \"_site_transient_update_core\";" | \
                      sed -n "s/.*\"version_checked\":\"\([0-9][0-9.]*\)\".*/\1/p")
        fi

        # Only output version if it is valid
        if is_valid_version "$version"; then
            echo "$version"
        fi
    ') || true
    if ! is_valid_version "$db_version"; then db_version=""; fi

    # 5. Try reading version from version.php using PHP
    admin_version=$(docker exec "$CONTAINER_NAME" bash -c "php -r '
        \$files = array(\"/bitnami/wordpress/wp-includes/version.php\", \"/opt/bitnami/wordpress/wp-includes/version.php\");
        foreach (\$files as \$file) {
            if (file_exists(\$file)) {
                include \$file;
                echo \$wp_version;
                break;
            }
        }
    '") || true
    if ! is_valid_version "$admin_version"; then admin_version=""; fi

    # Log all found versions (to stderr for debugging)
    {
        echo "WP-CLI reports version: ${wpcli_version:-not found}" >&2
        echo "wp eval reports version: ${php_version:-not found}" >&2
        echo "File check reports version: ${file_version:-not found}" >&2
        echo "Database reports version: ${db_version:-not found}" >&2
        echo "Admin page reports version: ${admin_version:-not found}" >&2
    }

    # Compare all versions and take the highest one, excluding db version
    local versions=()
    [ ! -z "$wpcli_version" ] && versions+=("$wpcli_version")
    [ ! -z "$php_version" ] && versions+=("$php_version")
    [ ! -z "$file_version" ] && versions+=("$file_version")
    [ ! -z "$admin_version" ] && versions+=("$admin_version")

    if [ ${#versions[@]} -eq 0 ]; then
        echo "Error: Could not determine WordPress version from any source" >&2
        exit 1
    fi

    # Return just the version number
    printf '%s\n' "${versions[@]}" | sort -V | tail -n 1
}

# Function to find closest available version on Docker Hub
find_closest_version() {
    local target_version=$1
    local available_versions=""
    local page=1
    local page_size=100
    local next_url="https://hub.docker.com/v2/repositories/bitnami/wordpress/tags?page_size=$page_size"

    echo "Retrieving available WordPress versions from Docker Hub..." >&2

    # Keep fetching pages until we hit an empty page or no next URL
    while [ ! -z "$next_url" ]; do
        echo "Fetching page $page..." >&2

        # Get the current page and extract versions and next URL
        local response=$(curl -s "$next_url")

        # Extract versions from this page
        local page_versions=$(echo "$response" | \
            grep -o '"name":"[^"]*"' | \
            grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+' | \
            sort -V | uniq)

        # If we got no versions, break
        if [ -z "$page_versions" ]; then
            break
        fi

        # Append to our list of versions
        available_versions="$available_versions"$'\n'"$page_versions"

        # Get next page URL and decode \u0026 to &
        next_url=$(echo "$response" | \
                  grep -o '"next":"[^"]*"' | \
                  cut -d'"' -f4 | \
                  sed 's/\\u0026/\&/g' )

        # If no next URL or we've hit page limit, break
        if [ -z "$next_url" ] || [ $page -ge 10 ]; then
            break
        fi

        ((page++))
    done

    # Clean up and sort the final list
    available_versions=$(echo "$available_versions" | grep -v '^$' | sort -V | uniq)

    if [ -z "$available_versions" ]; then
        echo "Failed to get versions from Docker Hub" >&2
        return 1
    fi

    # Print all versions for debugging
    echo "Available versions:" >&2
    echo "$available_versions" >&2

    # First, check if target version exists
    if echo "$available_versions" | grep -Fxq "$target_version"; then
        printf '%s\n' "$target_version"
        return 0
    fi

    # If not, find next newer version
    local newer_version
    newer_version=$(echo "$available_versions" | awk -v ver="$target_version" '$1 > ver' | head -n1)

    # If no newer version, find closest older version
    if [ -z "$newer_version" ]; then
        echo "$available_versions" | awk -v ver="$target_version" '$1 <= ver' | tail -n1
    else
        printf '%s\n' "$newer_version"
    fi
}

# Function to compare version numbers
version_greater_than() {
    local ver1=$1
    local ver2=$2

    if ! is_valid_version "$ver1" || ! is_valid_version "$ver2"; then
        echo "Invalid version format" >&2
        return 1
    fi

    # Convert versions to arrays
    local v1_parts=() v2_parts=()
    IFS='.' read -ra v1_parts <<< "$ver1"
    IFS='.' read -ra v2_parts <<< "$ver2"

    # Pad shorter version with zeros
    while [ ${#v1_parts[@]} -lt 4 ]; do
        v1_parts+=("0")
    done
    while [ ${#v2_parts[@]} -lt 4 ]; do
        v2_parts+=("0")
    done

    # Compare each part numerically
    for i in {0..3}; do
        if [ "${v1_parts[i]:-0}" -gt "${v2_parts[i]:-0}" ]; then
            return 0  # ver1 is greater
        elif [ "${v1_parts[i]:-0}" -lt "${v2_parts[i]:-0}" ]; then
            return 1  # ver1 is not greater
        fi
    done
    return 1  # versions are equal
}

# Test cases (can be commented out in production)
test_versions() {
    local test_pairs=(
        "6.7 6.6.2"      # should return 0 (true)
        "7 6.7"          # should return 0 (true)
        "6.6.2 6.6.2"    # should return 1 (false)
        "6.6.1 6.6.2"    # should return 1 (false)
        "6.7.0 6.7"      # should return 1 (false)
        "6.7.1 6.7"      # should return 0 (true)
        "6.7.0.1 6.7"    # should return 0 (true)
    )

    echo "Running version comparison tests..."
    for pair in "${test_pairs[@]}"; do
        read -r v1 v2 <<< "$pair"
        if version_greater_than "$v1" "$v2"; then
            echo "$v1 > $v2 (OK)"
        else
            echo "$v1 <= $v2"
        fi
    done

    echo "Running version validation tests..."
    local test_versions=(
        "6"        # valid
        "6.7"      # valid
        "6.7.2"    # valid
        "6.7.2.1"  # valid
        "6.7.2.1.5"  # invalid
        "6.7.2a"   # invalid
        "6.7."     # invalid
        ".6.7"     # invalid
        "a.b.c"    # invalid
    )

    for ver in "${test_versions[@]}"; do
        if is_valid_version "$ver"; then
            echo "$ver is valid"
        else
            echo "$ver is invalid"
        fi
    done
}

# Uncomment to run tests
## test_versions

# Get the current WordPress version
current_wp_version=$(get_wp_version | tr -d '\r')
if [ -z "$current_wp_version" ] || ! is_valid_version "$current_wp_version"; then
    echo "Failed to get valid WordPress version"
    exit 1
fi
echo "Using WordPress version: $current_wp_version"

## current_wp_version="6.7.0"

# Check the current IMAGE_VERSION from the .env file
echo "Reading IMAGE_VERSION from .env file..."
env_wp_version=$(grep '^IMAGE_VERSION=' "$ENV_FILE" | cut -d= -f2 | tr -d '"' | tr -d "'")
if ! is_valid_version "$env_wp_version"; then
    echo "Invalid version in .env file"
    exit 1
fi
echo "WordPress version in .env file (IMAGE_VERSION): $env_wp_version"

# Check if current_wp_version is greater than env_wp_version
if version_greater_than "$current_wp_version" "$env_wp_version"; then
    echo "Upgrade detected: WordPress ($current_wp_version) is newer than .env ($env_wp_version)"

    # Find the best available version to use
    target_version=$(find_closest_version "$current_wp_version" | tr -d '\r')

    if [ -n "$target_version" ]; then
        echo "Found appropriate Docker image version: $target_version"

        # Backup current .env file
        echo "Backing up current .env file..."
        cp "$ENV_FILE" "${ENV_FILE}.bck-versionprotect"

        # Update the IMAGE_VERSION in the .env file
        echo "Updating IMAGE_VERSION in .env file to: $target_version"
        sed -i "s/^IMAGE_VERSION=.*$/IMAGE_VERSION=\"$target_version\"/" "$ENV_FILE"

        # Check if IMAGE_VERSION_HOLD exists, add it if it doesn't
        if ! grep -q "^IMAGE_VERSION_HOLD=" "$ENV_FILE"; then
            echo "Adding IMAGE_VERSION_HOLD=true after IMAGE_VERSION..."
            sed -i "/^IMAGE_VERSION=.*/a IMAGE_VERSION_HOLD=true" "$ENV_FILE"
        fi

        echo "Successfully updated .env to version $target_version"
    else
        echo "Failed to find an appropriate version on Docker Hub"
        exit 1
    fi
else
    echo "No update required. WordPress version ($current_wp_version) is not newer than .env version ($env_wp_version)."
fi

echo "WordPress version check and update script completed."

