<?php

namespace FleekDash\API;

use FleekDash\Core\FilesystemHelper;
use FleekDash\Core\FleekDashRollbackRegistry;
use FleekDash\Core\FleekDashPackageSynchronizer;
use FleekDash\Core\PluginHelper;

if (!\function_exists('fleekdash_log_debug')) {
    require_once FLEEKDASH_PLUGIN_DIR . 'includes/Core/debug-helpers.php';
}
if (!\class_exists(FleekDashRollbackRegistry::class)) {
    require_once FLEEKDASH_PLUGIN_DIR . 'includes/Core/FleekDashRollbackRegistry.php';
}
if (!\class_exists(FleekDashPackageSynchronizer::class)) {
    require_once FLEEKDASH_PLUGIN_DIR . 'includes/Core/FleekDashPackageSynchronizer.php';
}

/**
 * Rollback Job Manager
 *
 * Handles async processing of plugin/theme rollback operations
 *
 * @package FleekDash\API
 */
class RollbackJobManager
{
    private const STEP_ROLLBACK_FAILED = 'Rollback failed';

    private const STEP_FILESYSTEM_INIT_FAILED = 'Filesystem initialization failed';

    private const STEP_INSTALLATION_FAILED = 'Installation failed';

    private const INSTALL_UNKNOWN_ERROR = 'Unknown error during installation';

    private const STEP_RESTORE_FAILED = 'Restore failed';

    private const WP_ADMIN_FILE = 'wp-admin/includes/file.php';

    private const WP_ADMIN_UPGRADER = 'wp-admin/includes/class-wp-upgrader.php';

    private const WP_ADMIN_PLUGIN = 'wp-admin/includes/plugin.php';

    private const WP_ADMIN_THEME = 'wp-admin/includes/theme.php';

    private const WP_ADMIN_PLUGIN_INSTALL = 'wp-admin/includes/plugin-install.php';

    private const FLEEKDASH_FILESYSTEM_HELPER_REL = 'includes/Core/FilesystemHelper.php';

    /**
     * Initialize rollback job hooks
     */
    public static function init(): void
    {
        \add_action('fleekdash_process_rollback', array(__CLASS__, 'processRollbackJob'));
        \add_action('fleekdash_process_restore', array(__CLASS__, 'processRestoreJob'), 10, 2);
    }

    /**
     * Process rollback job (wp-cron hook: fleekdash_process_rollback)
     */
    public static function processRollbackJob(string $job_id): void
    {
        try {
            @\set_time_limit(0);
            if (\function_exists('wp_raise_memory_limit')) {
                \wp_raise_memory_limit('admin');
            }
            $job_status = \get_transient('fleekdash_rollback_job_' . $job_id);

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

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

            if ($backup) {
                self::updateJobStatus($job_id, 'backing_up', 10, 'Creating backup of current version...');

                $backup_result = $type === 'plugin'
                    ? self::backupPlugin($item, $version)
                    : self::backupTheme($item, $version);

                if (\is_wp_error($backup_result)) {
                    self::updateJobStatus($job_id, 'failed', 0, 'Backup failed', $backup_result->get_error_message());
                    return;
                }

                self::updateJobStatus($job_id, 'backed_up', 20, 'Backup created successfully');
            }

            self::updateJobStatus($job_id, 'downloading', 30, "Downloading version {$version}...");

            $download_url = isset($job_status['download_url']) && \is_string($job_status['download_url'])
                ? $job_status['download_url']
                : self::getDownloadUrl($type, $item, $version);

            if (!$download_url) {
                self::updateJobStatus($job_id, 'failed', 0, 'Download URL not found', 'Could not find download URL for version ' . $version);
                return;
            }

            if ($type === 'plugin') {
                self::rollbackPlugin($job_id, $item, $version, $download_url);
            } else {
                self::rollbackTheme($job_id, $item, $version, $download_url);
            }
        } catch (\Exception $e) {
            fleekdash_log_error('FleekDash: Rollback job exception: ' . $e->getMessage());
            self::updateJobStatus($job_id, 'failed', 0, self::STEP_ROLLBACK_FAILED, $e->getMessage());
        }
    }

    private static function requirePathUnderAbspath(string $relative): void
    {
        require_once \ABSPATH . $relative;
    }

    private static function loadFilesystemHelperClass(): void
    {
        if (!\class_exists(FilesystemHelper::class)) {
            require_once FLEEKDASH_PLUGIN_DIR . self::FLEEKDASH_FILESYSTEM_HELPER_REL;
        }
    }

    private static function initFilesystemForRollbackOrFail(string $job_id): bool
    {
        self::loadFilesystemHelperClass();
        if (!FilesystemHelper::safe_init()) {
            self::updateJobStatus($job_id, 'failed', 0, self::STEP_FILESYSTEM_INIT_FAILED);
            return false;
        }
        return true;
    }

    private static function loadPluginPhpDependenciesForRollback(): void
    {
        if (\class_exists(PluginHelper::class)) {
            PluginHelper::ensurePluginApiLoaded();
            return;
        }
        self::requirePathUnderAbspath(self::WP_ADMIN_PLUGIN);
    }

    private static function ensureIsPluginActiveLoaded()
    {
        if (!\function_exists('is_plugin_active')) {
            self::loadPluginPhpDependenciesForRollback();
        }
    }

    /**
     * @param mixed $result Plugin/theme upgrader install result
     */
    private static function failJobIfInstallUnsuccessful(string $job_id, mixed $result): bool
    {
        if (\is_wp_error($result) || $result === false) {
            $detail = \is_wp_error($result) ? $result->get_error_message() : self::INSTALL_UNKNOWN_ERROR;
            self::updateJobStatus($job_id, 'failed', 0, self::STEP_INSTALLATION_FAILED, $detail);
            return true;
        }
        return false;
    }

    private static function reactivatePluginAfterRollback(string $job_id, string $plugin_file): bool
    {
        self::updateJobStatus($job_id, 'activating', 90, 'Reactivating plugin...');

        if (!\function_exists('activate_plugin')) {
            self::loadPluginPhpDependenciesForRollback();
        }

        $activate_result = \activate_plugin($plugin_file);

        if (\is_wp_error($activate_result)) {
            self::updateJobStatus(
                $job_id,
                'complete',
                100,
                'Rollback complete but reactivation failed',
                $activate_result->get_error_message(),
            );
            return false;
        }
        return true;
    }

    /**
     * Rollback plugin to specific version
     */
    private static function rollbackPlugin(string $job_id, string $plugin_file, string $version, string $download_url): void
    {
        try {
            self::updateJobStatus($job_id, 'installing', 50, "Installing plugin version {$version}...");

            $supported_plugin = FleekDashRollbackRegistry::isFleekDashPluginFile($plugin_file);
            if (!$supported_plugin) {
                self::updateJobStatus($job_id, 'failed', 0, self::STEP_INSTALLATION_FAILED, 'Rollback currently supports only FleekDash');
            } else {
                self::loadPluginPhpDependenciesForRollback();
                $active_plugins = \get_option('active_plugins', array());
                $active_plugins = \is_array($active_plugins) ? $active_plugins : array();
                $was_active = \in_array($plugin_file, $active_plugins, true);

                self::updateJobStatus($job_id, 'installing', 55, 'Synchronizing FleekDash package files...');
                $sync_result = FleekDashPackageSynchronizer::synchronizePluginFromRemoteZip(
                    $download_url,
                    FleekDashRollbackRegistry::PLUGIN_SLUG
                );

                if (\is_wp_error($sync_result)) {
                    self::updateJobStatus($job_id, 'failed', 0, self::STEP_INSTALLATION_FAILED, $sync_result->get_error_message());
                } else {
                    self::ensureIsPluginActiveLoaded();
                    $needs_reactivation = $was_active && !\is_plugin_active($plugin_file);
                    $reactivation_ok = !$needs_reactivation || self::reactivatePluginAfterRollback($job_id, $plugin_file);
                    if ($reactivation_ok) {
                        self::updateJobStatus($job_id, 'complete', 100, "Plugin rolled back to version {$version} successfully!");

                        \delete_transient('fleekdash_all_updates');
                        \delete_site_transient('update_plugins');
                    }
                }
            }
        } catch (\Exception $e) {
            self::updateJobStatus($job_id, 'failed', 0, self::STEP_ROLLBACK_FAILED, $e->getMessage());
        }
    }

    /**
     * Rollback theme to specific version
     */
    private static function rollbackTheme(string $job_id, string $theme_slug, string $version, string $download_url): void
    {
        try {
            self::updateJobStatus($job_id, 'installing', 50, "Installing theme version {$version}...");

            self::requirePathUnderAbspath(self::WP_ADMIN_FILE);
            self::requirePathUnderAbspath(self::WP_ADMIN_UPGRADER);
            self::requirePathUnderAbspath(self::WP_ADMIN_THEME);

            $current_theme = \wp_get_theme();
            $is_active = ($current_theme->get_stylesheet() === $theme_slug);

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

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

            $result = $upgrader->install($download_url, array(
                'overwrite_package' => true,
            ));

            if (self::failJobIfInstallUnsuccessful($job_id, $result)) {
                return;
            }

            $message = "Theme rolled back to version {$version} successfully!";
            if ($is_active) {
                $message .= ' (Active theme - may require cache clear)';
            }

            self::updateJobStatus($job_id, 'complete', 100, $message);

            \delete_transient('fleekdash_all_updates');
            \delete_site_transient('update_themes');
        } catch (\Exception $e) {
            self::updateJobStatus($job_id, 'failed', 0, self::STEP_ROLLBACK_FAILED, $e->getMessage());
        }
    }

    /**
     * @return \WP_Error|string|null
     */
    private static function zipDirectoryToFleekdashBackup(
        string $absolute_dir,
        string $backup_segment,
        string $slug,
        string $version,
        string $not_found_code,
        string $not_found_message
    ): \WP_Error|string|null {
        $out = null;
        try {
            @\set_time_limit(0);
            if (\function_exists('wp_raise_memory_limit')) {
                \wp_raise_memory_limit('admin');
            }
            if (!\is_dir($absolute_dir)) {
                $out = new \WP_Error($not_found_code, $not_found_message);
            } else {
                $backup_dir = WP_CONTENT_DIR . '/fleekdash-backups/' . $backup_segment . '/' . $slug;
                if (!\is_dir($backup_dir)) {
                    \wp_mkdir_p($backup_dir);
                }

                $backup_filename = $slug . '-' . $version . '-backup-' . \time() . '.zip';
                $backup_path = $backup_dir . '/' . $backup_filename;

                if (!\class_exists('ZipArchive')) {
                    $out = new \WP_Error('zip_not_available', 'ZipArchive extension not available');
                } else {
                    $zip = new \ZipArchive();
                    if ($zip->open($backup_path, \ZipArchive::CREATE) !== true) {
                        $out = new \WP_Error('zip_error', 'Could not create ZIP file');
                    } else {
                        self::addDirectoryToZip($zip, $absolute_dir, $slug);
                        $zip->close();
                        $out = $backup_path;
                    }
                }
            }
        } catch (\Exception $e) {
            $out = new \WP_Error('backup_error', $e->getMessage());
        }

        return $out;
    }

    /**
     * Create backup of plugin
     *
     * @return \WP_Error|string|null
     */
    private static function backupPlugin(string $plugin_file, string $version): \WP_Error|string|null
    {
        $plugin_slug = \dirname($plugin_file) === '.' ? \basename($plugin_file, '.php') : \dirname($plugin_file);
        $plugin_dir = WP_PLUGIN_DIR . '/' . $plugin_slug;

        return self::zipDirectoryToFleekdashBackup(
            $plugin_dir,
            'plugins',
            $plugin_slug,
            $version,
            'plugin_not_found',
            'Plugin directory not found',
        );
    }

    /**
     * Create backup of theme
     *
     * @return \WP_Error|string
     */
    private static function backupTheme(string $theme_slug, string $version): \WP_Error|string
    {
        $theme_dir = \get_theme_root() . '/' . $theme_slug;

        return self::zipDirectoryToFleekdashBackup(
            $theme_dir,
            'themes',
            $theme_slug,
            $version,
            'theme_not_found',
            'Theme directory not found',
        );
    }

    /**
     * Add directory to ZIP recursively
     */
    private static function addDirectoryToZip(\ZipArchive $zip, string $dir, string $base_name): void
    {
        $files = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS),
            \RecursiveIteratorIterator::LEAVES_ONLY
        );

        foreach ($files as $file) {
            if (!$file->isDir()) {
                $file_path = $file->getRealPath();
                $relative_file = \str_replace('\\', '/', \substr($file_path, \strlen($dir) + 1));
                if (
                    $base_name === FleekDashRollbackRegistry::PLUGIN_SLUG
                    && !FleekDashRollbackRegistry::shouldIncludeBackupRelativePath($relative_file)
                ) {
                    continue;
                }
                $relative_path = $base_name . '/' . $relative_file;
                $zip->addFile($file_path, $relative_path);
            }
        }
    }

    /**
     * @return string|null
     */
    private static function getPluginVersionDownloadUrl(string $slug, string $version): ?string
    {
        $url = null;
        if (!\function_exists('plugins_api')) {
            self::requirePathUnderAbspath(self::WP_ADMIN_PLUGIN_INSTALL);
        }

        $api = \plugins_api(
            'plugin_information',
            array(
                'slug' => $slug,
                'fields' => array('versions' => true),
            ),
        );

        if (!\is_wp_error($api)) {
            if (\is_array($api)) {
                $api = (object) $api;
            }
            if (\is_object($api) && isset($api->versions)) {
                $versions = (array) $api->versions;
                $url = $versions[$version] ?? null;
            }
        }

        return $url;
    }

    /**
     * Get download URL for specific version
     *
     * @return string|null
     */
    private static function getDownloadUrl(string $type, string $slug, string $version): ?string
    {
        if ($type === 'plugin') {
            return self::getPluginVersionDownloadUrl($slug, $version);
        }

        return "https://downloads.wordpress.org/theme/{$slug}.{$version}.zip";
    }

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

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

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

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

        \set_transient('fleekdash_rollback_job_' . $job_id, $job_status, 3600);

        if (\defined('WP_DEBUG') && WP_DEBUG) {
            fleekdash_log_debug("FleekDash Rollback [{$job_id}]: {$state} - {$progress}% - {$step}");
        }
    }

    /**
     * Process restore from backup job (wp-cron hook: fleekdash_process_restore)
     */
    public static function processRestoreJob(string $job_id, string $backup_path): void
    {
        try {
            self::updateJobStatus($job_id, 'extracting', 30, 'Extracting backup...');
            $supported_backup = FleekDashRollbackRegistry::getBackupPath(\basename($backup_path)) !== null;
            if (!$supported_backup) {
                self::updateJobStatus($job_id, 'failed', 0, self::STEP_RESTORE_FAILED, 'Restore currently supports only FleekDash backups');
            } else {
                self::updateJobStatus($job_id, 'restoring', 70, 'Restoring FleekDash package files...');
                $restore_result = FleekDashPackageSynchronizer::synchronizePluginFromLocalZip(
                    $backup_path,
                    FleekDashRollbackRegistry::PLUGIN_SLUG
                );
                if (\is_wp_error($restore_result)) {
                    self::updateJobStatus($job_id, 'failed', 0, self::STEP_RESTORE_FAILED, $restore_result->get_error_message());
                } else {
                    self::updateJobStatus($job_id, 'complete', 100, 'Backup restored successfully!');
                }
            }
        } catch (\Exception $e) {
            fleekdash_log_error('FleekDash: Restore job exception: ' . $e->getMessage());
            self::updateJobStatus($job_id, 'failed', 0, self::STEP_RESTORE_FAILED, $e->getMessage());
        }
    }
}

// Initialize
RollbackJobManager::init();
