<?php

namespace FleekDash\API;

// Load debug helpers
if (!function_exists('fleekdash_log_debug')) {
    require_once FLEEKDASH_PLUGIN_DIR . 'includes/Core/debug-helpers.php';
}

/**
 * Update Job Manager
 *
 * Handles async processing of WordPress updates
 * (Core, Plugins, Themes, Translations)
 *
 * @package FleekDash\API
 */
class UpdateJobManager
{
    private const MSG_UPDATE_FAILED = 'Update failed';
    private const MSG_FILESYSTEM_INIT_FAILED = 'Filesystem initialization failed';
    private const MSG_UNKNOWN_UPDATE_ERROR = 'Unknown error during update';
    private const PATH_ADMIN_PHP = 'wp-admin/includes/admin.php';
    private const PATH_CLASS_WP_UPGRADER = 'wp-admin/includes/class-wp-upgrader.php';
    private const PATH_FILE_PHP = 'wp-admin/includes/file.php';
    private const PATH_PLUGIN_PHP = 'wp-admin/includes/plugin.php';
    private const CLASS_FILESYSTEM_HELPER = '\\FleekDash\\Core\\FilesystemHelper';
    private const PATH_FILESYSTEM_HELPER = 'includes/Core/FilesystemHelper.php';
    private const CLASS_PLUGIN_HELPER = '\\FleekDash\\Core\\PluginHelper';

    /**
     * Initialize update job hooks
     */
    public static function init(): void
    {
        \add_action('fleekdash_process_update', array(__CLASS__, 'processUpdateJob'));
    }

    /**
     * Process update job (runs async)
     */
    public static function processUpdateJob(string|int $job_id): void
    {
        try {
            // Get job data
            $job_status = \get_transient('fleekdash_update_job_' . $job_id);

            if ($job_status === false) {
                fleekdash_log_debug('FleekDash: Update job not found: ' . $job_id);
                return;
            }

            $type = $job_status['type'];
            $item = $job_status['item'];

            // Update status: Starting
            self::updateJobStatus($job_id, 'downloading', 10, 'Starting update process...');

            // Route to appropriate update handler
            switch ($type) {
                case 'core':
                    self::updateCore($job_id);
                    break;

                case 'plugin':
                    self::updatePlugin($job_id, $item);
                    break;

                case 'theme':
                    self::updateTheme($job_id, $item);
                    break;

                case 'translation':
                    self::updateTranslation($job_id, $item);
                    break;

                default:
                    self::updateJobStatus($job_id, 'failed', 0, 'Unknown update type', 'Invalid update type: ' . $type);
                    return;
            }

        } catch (\Exception $e) {
            fleekdash_log_error('FleekDash: Update job exception: ' . $e->getMessage());
            self::updateJobStatus($job_id, 'failed', 0, self::MSG_UPDATE_FAILED, $e->getMessage());
        }
    }

    /**
     * Update WordPress Core
     */
    private static function updateCore(string|int $job_id): void
    {
        try {
            self::updateJobStatus($job_id, 'downloading', 20, 'Checking for WordPress updates...');

            require_once ABSPATH . self::PATH_ADMIN_PHP;
            require_once ABSPATH . 'wp-admin/includes/upgrade.php';
            require_once ABSPATH . self::PATH_CLASS_WP_UPGRADER;
            require_once ABSPATH . self::PATH_FILE_PHP;

            // Check for updates
            \wp_version_check(array(), true);
            $updates = \get_core_updates();

            if (empty($updates) || $updates[0]->response === 'latest') {
                self::updateJobStatus($job_id, 'complete', 100, 'WordPress is already up to date');
                return;
            }

            $update = $updates[0];

            self::updateJobStatus($job_id, 'downloading', 40, 'Downloading WordPress ' . $update->current . '...');

            if (!self::ensureFilesystemInit($job_id)) {
                return;
            }

            // Create upgrader
            $skin = new \WP_Ajax_Upgrader_Skin();
            $upgrader = new \Core_Upgrader($skin);

            self::updateJobStatus($job_id, 'installing', 60, 'Installing WordPress ' . $update->current . '...');

            $result = $upgrader->upgrade($update);
            if (!self::handleUpgradeResult($job_id, $result, 'WordPress updated to ' . $update->current . ' successfully!', false)) {
                return;
            }

            self::updateJobStatus($job_id, 'upgrading', 90, 'Upgrading database...');

            // Run database upgrade
            wp_upgrade();

            self::updateJobStatus($job_id, 'complete', 100, 'WordPress updated to ' . $update->current . ' successfully!');

            // Clear update caches
            \delete_transient('fleekdash_all_updates');
            \delete_site_transient('update_core');

        } catch (\Exception $e) {
            self::updateJobStatus($job_id, 'failed', 0, 'Core update failed', $e->getMessage());
        }
    }

    /**
     * Update plugin
     */
    private static function updatePlugin(string|int $job_id, string $plugin_file): void
    {
        try {
            self::updateJobStatus($job_id, 'downloading', 20, 'Checking plugin updates...');

            require_once ABSPATH . self::PATH_ADMIN_PHP;
            require_once ABSPATH . self::PATH_CLASS_WP_UPGRADER;
            self::ensurePluginFunctionsLoaded();
            require_once ABSPATH . self::PATH_FILE_PHP;

            // Get plugin data
            if (!\function_exists('get_plugin_data')) {
                self::ensurePluginFunctionsLoaded();
            }

            $plugin_data = \get_plugin_data(WP_PLUGIN_DIR . '/' . $plugin_file, false, false);

            if (empty($plugin_data)) {
                self::updateJobStatus($job_id, 'failed', 0, 'Plugin not found', 'Plugin file not found: ' . $plugin_file);
                return;
            }

            self::updateJobStatus($job_id, 'downloading', 40, 'Downloading ' . $plugin_data['Name'] . '...');

            if (!self::ensureFilesystemInit($job_id)) {
                return;
            }

            // Create upgrader (plugin API already loaded above)
            $skin = new \WP_Ajax_Upgrader_Skin();
            $upgrader = new \Plugin_Upgrader($skin);

            self::updateJobStatus($job_id, 'installing', 60, 'Installing ' . $plugin_data['Name'] . '...');

            $result = $upgrader->upgrade($plugin_file);
            if (!self::handleUpgradeResult($job_id, $result, $plugin_data['Name'] . ' updated successfully!')) {
                return;
            }

            // Clear update caches
            \delete_transient('fleekdash_all_updates');
            \delete_site_transient('update_plugins');

        } catch (\Exception $e) {
            self::updateJobStatus($job_id, 'failed', 0, 'Plugin update failed', $e->getMessage());
        }
    }

    /**
     * Update theme
     */
    private static function updateTheme(string|int $job_id, string $theme_slug): void
    {
        try {
            self::updateJobStatus($job_id, 'downloading', 20, 'Checking theme updates...');

            require_once ABSPATH . self::PATH_ADMIN_PHP;
            require_once ABSPATH . self::PATH_CLASS_WP_UPGRADER;
            require_once ABSPATH . 'wp-admin/includes/theme.php';
            require_once ABSPATH . self::PATH_FILE_PHP;

            $theme = wp_get_theme($theme_slug);

            if (!$theme->exists()) {
                self::updateJobStatus($job_id, 'failed', 0, 'Theme not found', 'Theme not found: ' . $theme_slug);
                return;
            }

            self::updateJobStatus($job_id, 'downloading', 40, 'Downloading ' . $theme->get('Name') . '...');

            if (!self::ensureFilesystemInit($job_id)) {
                return;
            }

            // Create upgrader
            $skin = new \WP_Ajax_Upgrader_Skin();
            $upgrader = new \Theme_Upgrader($skin);

            self::updateJobStatus($job_id, 'installing', 60, 'Installing ' . $theme->get('Name') . '...');

            // Perform upgrade
            $result = $upgrader->upgrade($theme_slug);
            if (!self::handleUpgradeResult($job_id, $result, $theme->get('Name') . ' updated successfully!')) {
                return;
            }

            // Clear update caches
            \delete_transient('fleekdash_all_updates');
            \delete_site_transient('update_themes');

        } catch (\Exception $e) {
            self::updateJobStatus($job_id, 'failed', 0, 'Theme update failed', $e->getMessage());
        }
    }

    /**
     * Update translation
     */
    private static function updateTranslation(string|int $job_id, string $translation_slug): void
    {
        try {
            self::updateJobStatus($job_id, 'downloading', 30, 'Downloading translation...');

            require_once ABSPATH . self::PATH_ADMIN_PHP;
            require_once ABSPATH . self::PATH_CLASS_WP_UPGRADER;
            require_once ABSPATH . 'wp-admin/includes/translation-install.php';
            require_once ABSPATH . self::PATH_FILE_PHP;

            if (!self::ensureFilesystemInit($job_id)) {
                return;
            }

            self::updateJobStatus($job_id, 'installing', 60, 'Installing translation...');

            $skin = new \WP_Ajax_Upgrader_Skin();
            $upgrader = new \Language_Pack_Upgrader($skin);
            $updates = \wp_get_translation_updates();
            $translation = self::findTranslationInUpdates($updates, $translation_slug);

            if (!$translation) {
                self::handleTranslationNotFound($job_id, $updates, $translation_slug);
                return;
            }

            $result = $upgrader->upgrade($translation);
            if (!self::handleUpgradeResult($job_id, $result, 'Translation updated successfully!')) {
                return;
            }

            \delete_transient('fleekdash_all_updates');

        } catch (\Exception $e) {
            self::updateJobStatus($job_id, 'failed', 0, 'Translation update failed', $e->getMessage());
        }
    }

    /**
     * Find translation object in updates array by slug.
     *
     * @param array<int, object> $updates Translation updates from wp_get_translation_updates()
     * @param string $translation_slug Target slug
     * @return object|null Translation object or null
     */
    private static function findTranslationInUpdates(array $updates, string $translation_slug): ?object
    {
        if (empty($updates)) {
            return null;
        }
        foreach ($updates as $update) {
            if ($update->slug === $translation_slug) {
                return $update;
            }
        }
        return null;
    }

    /**
     * Handle translation not found: set status (complete if no updates, failed otherwise).
     *
     * @param string|int $job_id Job ID
     * @param array<int, object> $updates Translation updates (may be empty)
     * @param string $translation_slug Requested slug
     */
    private static function handleTranslationNotFound(string|int $job_id, array $updates, string $translation_slug): void
    {
        $hasUpdates = !empty($updates);
        $step = $hasUpdates ? 'Translation not found' : 'No translation updates available';
        $error = $hasUpdates ? 'Translation not found: ' . $translation_slug : null;
        self::updateJobStatus(
            $job_id,
            $hasUpdates ? 'failed' : 'complete',
            $hasUpdates ? 0 : 100,
            $step,
            $error
        );
    }

    /**
     * Ensures plugin functions (get_plugin_data, is_plugin_active) are loaded.
     */
    private static function ensurePluginFunctionsLoaded(): void
    {
        if (\class_exists(self::CLASS_PLUGIN_HELPER)) {
            \FleekDash\Core\PluginHelper::ensurePluginApiLoaded();
            return;
        }
        if (!\function_exists('get_plugins')) {
            require_once ABSPATH . self::PATH_PLUGIN_PHP;
        }
    }

    /**
     * Ensures FilesystemHelper is loaded and initialized.
     *
     * @return bool True on success, false on failure (status already updated).
     */
    private static function ensureFilesystemInit(string|int $job_id): bool
    {
        if (!class_exists(self::CLASS_FILESYSTEM_HELPER)) {
            require_once FLEEKDASH_PLUGIN_DIR . self::PATH_FILESYSTEM_HELPER;
        }
        if (!\FleekDash\Core\FilesystemHelper::safe_init()) {
            self::updateJobStatus($job_id, 'failed', 0, self::MSG_FILESYSTEM_INIT_FAILED);
            return false;
        }
        return true;
    }

    /**
     * Handles upgrade result (WP_Error or false = failure).
     * On success, optionally sets complete status (caller sets it when false, e.g. core needs db upgrade first).
     *
     * @return bool True on success, false on failure (status already updated).
     */
    private static function handleUpgradeResult(
        string|int $job_id,
        mixed $result,
        string $success_message,
        bool $set_complete = true,
    ): bool
    {
        if (is_wp_error($result)) {
            self::updateJobStatus($job_id, 'failed', 0, self::MSG_UPDATE_FAILED, $result->get_error_message());
            return false;
        }
        if ($result === false) {
            self::updateJobStatus($job_id, 'failed', 0, self::MSG_UPDATE_FAILED, self::MSG_UNKNOWN_UPDATE_ERROR);
            return false;
        }
        if ($set_complete) {
            self::updateJobStatus($job_id, 'complete', 100, $success_message);
        }
        return true;
    }

    /**
     * Update job status
     */
    private static function updateJobStatus(
        string|int $job_id,
        string $state,
        int $progress,
        string $step,
        ?string $error = null,
    ): void
    {
        $job_status = \get_transient('fleekdash_update_job_' . $job_id);

        if ($job_status === false) {
            $job_status = array(
                'logs' => array(),
            );
        }

        $job_status['state'] = $state;
        $job_status['progress'] = $progress;
        $job_status['step'] = $step;

        // Add log entry
        $log_entry = '[' . \current_time('mysql') . '] ' . $step;
        if ($error) {
            $log_entry .= ' - Error: ' . $error;
            $job_status['error'] = $error;
        }
        $job_status['logs'][] = $log_entry;

        // Update transient
        \set_transient('fleekdash_update_job_' . $job_id, $job_status, 3600);

        // Log for debugging
        fleekdash_log_debug("FleekDash Update [{$job_id}]: {$state} - {$progress}% - {$step}");
    }
}

// Initialize
UpdateJobManager::init();
