#!/usr/bin/env php
<?php
/**
 * WordPress Auto-Update Script
 * 
 * Runs scheduled WordPress auto-updates for sites with auto-update enabled.
 * Creates backups before updates and logs all activities.
 * 
 * Usage: php wordpress-auto-update.php [--dry-run] [--site-id=ID] [--type=core|plugins|themes]
 */

// Add the web-files directory to include path
ini_set('include_path', ini_get('include_path') . PATH_SEPARATOR . '/docker/whp/web');

// Set up CLI-friendly environment variables before including auto-prepend
if (php_sapi_name() === 'cli') {
    $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
    $_SERVER['REQUEST_URI'] = '/cli-script';
    $_SERVER['SERVER_NAME'] = 'localhost';
    $_SERVER['SCRIPT_FILENAME'] = __FILE__;
}

// Include the auto-prepend configuration (needed for database constants)
require_once('/docker/whp/web/auto-prepend.php');

require_once('/docker/whp/web/libs/mysqlmgmt.php');
require_once('/docker/whp/web/libs/WordPressManager.php');
require_once('/docker/whp/web/libs/BackupEngine.php');

use WHP\WordPressManager;
use WHPBackup\BackupEngine;

class WordPressAutoUpdater {
    private $db;
    private $wpManager;
    private $backupEngine;
    private $dryRun = false;
    private $logFile;
    
    public function __construct($dryRun = false) {
        $this->dryRun = $dryRun;
        $this->logFile = '/var/log/wordpress-auto-update.log';
        
        // Initialize database connection
        $mysql = new mysqlmgmt();
        $this->db = $mysql->getMySQLConnection();
        $this->db->exec("USE whp");
        
        // Initialize managers
        $this->wpManager = new WordPressManager();
        $this->backupEngine = new BackupEngine();
        
        $this->log("WordPress Auto-Updater started" . ($this->dryRun ? " (DRY RUN)" : ""));
    }
    
    /**
     * Log messages to file and optionally to stdout
     */
    private function log($message, $level = 'INFO') {
        $timestamp = date('Y-m-d H:i:s');
        $logMessage = "[{$timestamp}] [{$level}] {$message}" . PHP_EOL;
        
        // Write to log file
        file_put_contents($this->logFile, $logMessage, FILE_APPEND | LOCK_EX);
        
        // Only output to stdout if running interactively (not from cron)
        if (php_sapi_name() === 'cli' && function_exists('posix_isatty') && posix_isatty(STDOUT)) {
            echo $logMessage;
        } elseif (php_sapi_name() === 'cli') {
            // Fallback for systems without POSIX functions - always output for CLI
            echo $logMessage;
        }
    }
    
    /**
     * Get sites that have auto-updates enabled
     */
    public function getSitesWithAutoUpdates($requestedTypes = null, $siteId = null) {
        $conditions = ["status = 'active'"];
        $params = [];

        // Normalize requestedTypes to array
        if (is_string($requestedTypes)) {
            $requestedTypes = [$requestedTypes];
        }

        if ($requestedTypes) {
            $typeConditions = [];
            foreach ($requestedTypes as $updateType) {
                switch ($updateType) {
                    case 'core':
                        $typeConditions[] = "auto_update_core = 1";
                        break;
                    case 'plugins':
                        $typeConditions[] = "auto_update_plugins = 1";
                        break;
                    case 'themes':
                        $typeConditions[] = "auto_update_themes = 1";
                        break;
                    default:
                        throw new Exception("Invalid update type: {$updateType}");
                }
            }
            if (!empty($typeConditions)) {
                $conditions[] = "(" . implode(' OR ', $typeConditions) . ")";
            }
        } else {
            $conditions[] = "(auto_update_core = 1 OR auto_update_plugins = 1 OR auto_update_themes = 1)";
        }
        
        if ($siteId) {
            $conditions[] = "id = ?";
            $params[] = $siteId;
        }
        
        $sql = "SELECT * FROM wordpress_sites WHERE " . implode(' AND ', $conditions) . " ORDER BY user, domain";
        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);
        
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
    
    /**
     * Create backup before update and wait for completion
     */
    private function createAndWaitForBackup($site) {
        if (!$site['auto_backup_before_update']) {
            $this->log("Skipping backup for {$site['domain']} - auto backup disabled", 'INFO');
            return true;
        }

        $this->log("Creating backup for {$site['domain']} before update", 'INFO');

        if ($this->dryRun) {
            $this->log("DRY RUN: Would create backup for site {$site['id']} and wait for completion", 'INFO');
            return true;
        }

        try {
            // Create site backup using the backup engine with auto-update prefix
            $backupResult = $this->backupEngine->createWordPressBackup(
                $site['id'],
                'auto_update_' . date('Y-m-d_H-i-s'), // Will be combined with domain in backup engine
                $site['preferred_backup_target_id']
            );

            if ($backupResult['success']) {
                $backupId = $backupResult['backup_id'];
                $this->log("Backup created successfully for {$site['domain']}: {$backupId}", 'SUCCESS');

                // Wait for backup to complete
                $this->log("Waiting for backup {$backupId} to complete before starting updates...", 'INFO');
                if ($this->waitForBackupCompletion($backupId, $site['domain'])) {
                    $this->log("Backup {$backupId} completed successfully, proceeding with updates", 'SUCCESS');
                    return true;
                } else {
                    $this->log("Backup {$backupId} failed or timed out, aborting updates", 'ERROR');
                    return false;
                }
            } else {
                $this->log("Backup failed for {$site['domain']}: " . $backupResult['error'], 'ERROR');
                return false;
            }
        } catch (Exception $e) {
            // Check if this is a "no backup target available" error
            if (strpos($e->getMessage(), 'No backup target available') !== false) {
                $this->log("Warning: No backup targets configured for {$site['domain']} - skipping backup and continuing with update", 'WARNING');
                return true; // Continue with update even without backup
            }
            $this->log("Backup exception for {$site['domain']}: " . $e->getMessage(), 'ERROR');
            return false;
        }
    }

    /**
     * Wait for backup to complete with timeout
     */
    private function waitForBackupCompletion($backupId, $domain, $maxWaitMinutes = 30) {
        $maxWaitSeconds = $maxWaitMinutes * 60;
        $checkInterval = 10; // Check every 10 seconds
        $elapsed = 0;

        $mysql = new mysqlmgmt();
        $db = $mysql->getMySQLConnection();
        $db->exec("USE whp");

        while ($elapsed < $maxWaitSeconds) {
            // Check backup status
            $stmt = $db->prepare("SELECT status, error_message FROM backup_history WHERE id = ?");
            $stmt->execute([$backupId]);
            $backup = $stmt->fetch(PDO::FETCH_ASSOC);

            if (!$backup) {
                $this->log("Backup {$backupId} not found in database", 'ERROR');
                return false;
            }

            switch ($backup['status']) {
                case 'completed':
                    return true;

                case 'failed':
                    $errorMsg = $backup['error_message'] ? ": " . $backup['error_message'] : "";
                    $this->log("Backup {$backupId} failed{$errorMsg}", 'ERROR');
                    return false;

                case 'pending':
                case 'claimed':
                case 'running':
                    // Still in progress, continue waiting
                    $this->log("Backup {$backupId} status: {$backup['status']}, waiting... ({$elapsed}s elapsed)", 'INFO');
                    break;

                default:
                    $this->log("Backup {$backupId} has unknown status: {$backup['status']}", 'WARNING');
                    break;
            }

            sleep($checkInterval);
            $elapsed += $checkInterval;
        }

        // Timeout reached
        $this->log("Backup {$backupId} timed out after {$maxWaitMinutes} minutes", 'ERROR');
        return false;
    }
    
    /**
     * Update WordPress core
     */
    private function updateCore($site) {
        if (!$site['auto_update_core']) {
            return true;
        }
        
        $this->log("Updating WordPress core for {$site['domain']}", 'INFO');
        
        if ($this->dryRun) {
            $this->log("DRY RUN: Would update core for {$site['domain']}", 'INFO');
            return true;
        }
        
        try {
            // Check if update is available first
            $checkResult = $this->wpManager->executeWpCliCommand(
                $site['container_name'],
                $site['user'],
                "wp core check-update --format=json"
            );
            
            if ($checkResult['success']) {
                $updateInfo = json_decode($checkResult['output'], true);
                if (empty($updateInfo)) {
                    $this->log("WordPress core is already up to date for {$site['domain']}", 'INFO');
                    return true;
                }
            }
            
            // Perform the update
            $updateResult = $this->wpManager->executeWpCliCommand(
                $site['container_name'],
                $site['user'],
                "wp core update --quiet"
            );
            
            if ($updateResult['success']) {
                $this->log("WordPress core updated successfully for {$site['domain']}", 'SUCCESS');
                return true;
            } else {
                $this->log("WordPress core update failed for {$site['domain']}: " . $updateResult['output'], 'ERROR');
                return false;
            }
        } catch (Exception $e) {
            $this->log("Core update exception for {$site['domain']}: " . $e->getMessage(), 'ERROR');
            return false;
        }
    }
    
    /**
     * Update WordPress plugins
     */
    private function updatePlugins($site) {
        if (!$site['auto_update_plugins']) {
            return true;
        }
        
        $this->log("Updating WordPress plugins for {$site['domain']}", 'INFO');
        
        if ($this->dryRun) {
            $this->log("DRY RUN: Would update plugins for {$site['domain']}", 'INFO');
            return true;
        }
        
        try {
            // Check for plugin updates first
            $checkResult = $this->wpManager->executeWpCliCommand(
                $site['container_name'],
                $site['user'],
                "wp plugin list --update=available --format=count"
            );
            
            if ($checkResult['success'] && trim($checkResult['output']) === '0') {
                $this->log("All plugins are up to date for {$site['domain']}", 'INFO');
                return true;
            }
            
            // Update all plugins
            $updateResult = $this->wpManager->executeWpCliCommand(
                $site['container_name'],
                $site['user'],
                "wp plugin update --all --quiet"
            );
            
            if ($updateResult['success']) {
                $this->log("WordPress plugins updated successfully for {$site['domain']}", 'SUCCESS');
                return true;
            } else {
                $this->log("WordPress plugins update failed for {$site['domain']}: " . $updateResult['output'], 'ERROR');
                return false;
            }
        } catch (Exception $e) {
            $this->log("Plugin update exception for {$site['domain']}: " . $e->getMessage(), 'ERROR');
            return false;
        }
    }
    
    /**
     * Update WordPress themes
     */
    private function updateThemes($site) {
        if (!$site['auto_update_themes']) {
            return true;
        }
        
        $this->log("Updating WordPress themes for {$site['domain']}", 'INFO');
        
        if ($this->dryRun) {
            $this->log("DRY RUN: Would update themes for {$site['domain']}", 'INFO');
            return true;
        }
        
        try {
            // Check for theme updates first
            $checkResult = $this->wpManager->executeWpCliCommand(
                $site['container_name'],
                $site['user'],
                "wp theme list --update=available --format=count"
            );
            
            if ($checkResult['success'] && trim($checkResult['output']) === '0') {
                $this->log("All themes are up to date for {$site['domain']}", 'INFO');
                return true;
            }
            
            // Update all themes
            $updateResult = $this->wpManager->executeWpCliCommand(
                $site['container_name'],
                $site['user'],
                "wp theme update --all --quiet"
            );
            
            if ($updateResult['success']) {
                $this->log("WordPress themes updated successfully for {$site['domain']}", 'SUCCESS');
                return true;
            } else {
                $this->log("WordPress themes update failed for {$site['domain']}: " . $updateResult['output'], 'ERROR');
                return false;
            }
        } catch (Exception $e) {
            $this->log("Theme update exception for {$site['domain']}: " . $e->getMessage(), 'ERROR');
            return false;
        }
    }
    
    /**
     * Check if updates are available for the requested types
     * @param array $site The site data
     * @param array $updatesToRun The update types to check
     * @return array Array of update types that have available updates
     */
    private function checkAvailableUpdates($site, $updatesToRun) {
        $availableUpdates = [];

        foreach ($updatesToRun as $type) {
            switch ($type) {
                case 'core':
                    try {
                        $checkResult = $this->wpManager->executeWpCliCommand(
                            $site['container_name'],
                            $site['user'],
                            "wp core check-update --format=json"
                        );

                        if ($checkResult['success']) {
                            $updateInfo = json_decode($checkResult['output'], true);
                            if (!empty($updateInfo)) {
                                $availableUpdates[] = 'core';
                                $this->log("Core updates available for {$site['domain']}", 'INFO');
                            }
                        }
                    } catch (Exception $e) {
                        $this->log("Failed to check core updates for {$site['domain']}: " . $e->getMessage(), 'ERROR');
                    }
                    break;

                case 'plugins':
                    try {
                        $checkResult = $this->wpManager->executeWpCliCommand(
                            $site['container_name'],
                            $site['user'],
                            "wp plugin list --update=available --format=count"
                        );

                        if ($checkResult['success'] && trim($checkResult['output']) !== '0') {
                            $availableUpdates[] = 'plugins';
                            $this->log("Plugin updates available for {$site['domain']}: " . trim($checkResult['output']) . " plugins", 'INFO');
                        }
                    } catch (Exception $e) {
                        $this->log("Failed to check plugin updates for {$site['domain']}: " . $e->getMessage(), 'ERROR');
                    }
                    break;

                case 'themes':
                    try {
                        $checkResult = $this->wpManager->executeWpCliCommand(
                            $site['container_name'],
                            $site['user'],
                            "wp theme list --update=available --format=count"
                        );

                        if ($checkResult['success'] && trim($checkResult['output']) !== '0') {
                            $availableUpdates[] = 'themes';
                            $this->log("Theme updates available for {$site['domain']}: " . trim($checkResult['output']) . " themes", 'INFO');
                        }
                    } catch (Exception $e) {
                        $this->log("Failed to check theme updates for {$site['domain']}: " . $e->getMessage(), 'ERROR');
                    }
                    break;
            }
        }

        return $availableUpdates;
    }

    /**
     * Process updates for a single site
     * @param array $site The site data
     * @param array|string|null $requestedTypes Specific update types to run, or null for all
     */
    public function processSite($site, $requestedTypes = null) {
        $this->log("Processing site: {$site['domain']} (ID: {$site['id']})", 'INFO');

        // Normalize requestedTypes to array
        if (is_string($requestedTypes)) {
            $requestedTypes = [$requestedTypes];
        }

        // Determine which updates will actually run
        $updatesToRun = [];
        if ($requestedTypes) {
            // Specific update types requested
            foreach ($requestedTypes as $type) {
                switch ($type) {
                    case 'core':
                        if ($site['auto_update_core']) $updatesToRun[] = 'core';
                        break;
                    case 'plugins':
                        if ($site['auto_update_plugins']) $updatesToRun[] = 'plugins';
                        break;
                    case 'themes':
                        if ($site['auto_update_themes']) $updatesToRun[] = 'themes';
                        break;
                }
            }
        } else {
            // All enabled update types
            if ($site['auto_update_core']) $updatesToRun[] = 'core';
            if ($site['auto_update_plugins']) $updatesToRun[] = 'plugins';
            if ($site['auto_update_themes']) $updatesToRun[] = 'themes';
        }

        if (empty($updatesToRun)) {
            $this->log("No updates enabled for {$site['domain']}, skipping", 'INFO');
            return true;
        }

        $this->log("Updates to run for {$site['domain']}: " . implode(', ', $updatesToRun), 'INFO');

        // Check if there are actually updates available before creating backup
        $availableUpdates = $this->checkAvailableUpdates($site, $updatesToRun);

        if (empty($availableUpdates)) {
            $this->log("No updates available for {$site['domain']}, skipping backup and updates", 'INFO');
            return true;
        }

        $this->log("Available updates for {$site['domain']}: " . implode(', ', $availableUpdates), 'INFO');

        // Create backup ONCE and wait for completion before ANY updates
        if (!$this->createAndWaitForBackup($site)) {
            $this->log("Skipping updates for {$site['domain']} due to backup failure", 'ERROR');
            return false;
        }

        // Now run only the updates that are actually available
        $success = true;
        foreach ($availableUpdates as $type) {
            switch ($type) {
                case 'core':
                    if (!$this->updateCore($site)) {
                        $success = false;
                    }
                    break;
                case 'plugins':
                    if (!$this->updatePlugins($site)) {
                        $success = false;
                    }
                    break;
                case 'themes':
                    if (!$this->updateThemes($site)) {
                        $success = false;
                    }
                    break;
            }
        }

        if ($success) {
            $this->log("All updates completed successfully for {$site['domain']}", 'SUCCESS');
        } else {
            $this->log("Some updates failed for {$site['domain']}", 'WARNING');
        }

        return $success;
    }
    
    /**
     * Run auto-updates for all eligible sites
     */
    public function runAutoUpdates($requestedTypes = null, $siteId = null) {
        $sites = $this->getSitesWithAutoUpdates($requestedTypes, $siteId);
        
        if (empty($sites)) {
            $this->log("No sites found with auto-updates enabled", 'INFO');
            return;
        }
        
        $this->log("Found " . count($sites) . " site(s) with auto-updates enabled", 'INFO');
        
        $successCount = 0;
        $failureCount = 0;
        
        foreach ($sites as $site) {
            if ($this->processSite($site, $requestedTypes)) {
                $successCount++;
            } else {
                $failureCount++;
            }

            // Add a small delay between sites to prevent overwhelming the system
            if (!$this->dryRun) {
                sleep(2);
            }
        }
        
        $this->log("Auto-update run completed. Success: {$successCount}, Failures: {$failureCount}", 'INFO');
    }
}

// Parse command line arguments
$options = getopt('', ['dry-run', 'site-id:', 'type:']);
$dryRun = isset($options['dry-run']);
$siteId = $options['site-id'] ?? null;
$updateTypes = $options['type'] ?? null;

// Parse and validate update types
$validTypes = ['core', 'plugins', 'themes'];
$requestedTypes = [];

if ($updateTypes) {
    // Split comma-separated types
    $typeList = array_map('trim', explode(',', $updateTypes));

    // Validate each type
    foreach ($typeList as $type) {
        if (!in_array($type, $validTypes)) {
            echo "Error: Invalid update type '$type'. Must be one of: " . implode(', ', $validTypes) . "\n";
            echo "Usage: php wordpress-auto-update.php [--dry-run] [--site-id=ID] [--type=TYPE1,TYPE2,...]\n";
            echo "\n";
            echo "Examples:\n";
            echo "  php wordpress-auto-update.php                          # Run all enabled updates for all sites\n";
            echo "  php wordpress-auto-update.php --type=core              # Run only core updates\n";
            echo "  php wordpress-auto-update.php --type=core,plugins      # Run core and plugin updates\n";
            echo "  php wordpress-auto-update.php --type=plugins,themes    # Run plugin and theme updates\n";
            echo "  php wordpress-auto-update.php --site-id=123            # Run updates for specific site\n";
            echo "  php wordpress-auto-update.php --site-id=123 --type=core,plugins  # Specific site, core+plugins only\n";
            echo "  php wordpress-auto-update.php --dry-run                # Show what would be done without executing\n";
            exit(1);
        }
    }

    // Remove duplicates and sort
    $requestedTypes = array_unique($typeList);
    sort($requestedTypes);
}

try {
    $updater = new WordPressAutoUpdater($dryRun);

    // Log what types were requested
    if ($requestedTypes) {
        echo "Running WordPress auto-updates for types: " . implode(', ', $requestedTypes) . "\n";
    } else {
        echo "Running WordPress auto-updates for all enabled types\n";
    }

    if ($siteId) {
        echo "Limiting to site ID: $siteId\n";
    }

    echo "\n";

    $updater->runAutoUpdates($requestedTypes, $siteId);
} catch (Exception $e) {
    echo "Fatal error: " . $e->getMessage() . "\n";
    exit(1);
}