<?php

namespace FleekDash\API;

use FleekDash\API\Plugins\PluginHelpers;
use FleekDash\Core\FilesystemHelper;
use FleekDash\Core\PluginHelper;

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

/**
 * Plugin Directory Installer
 *
 * Handles plugin directory installation with progress tracking (sync or cron)
 *
 * @package FleekDash\API
 */
class PluginDirectoryInstaller
{
    private const STEP_INSTALLATION_FAILED = 'Installation failed';

    /**
     * Initialize installer hooks.
     *
     * Since v2.5.9 installations run synchronously inside the REST request
     * (PluginDirectoryAPI::install_plugin).  The legacy cron hook is kept only
     * to drain stale events that were scheduled before the migration.  On each
     * fire the handler silently cleans up the transient and exits without
     * running the heavy install pipeline a second time.
     */
    public static function init(): void
    {
        \add_action('fleekdash_install_plugin', array(__CLASS__, 'handleLegacyCronEvent'));
    }

    /**
     * Drain stale wp-cron events left from pre-v2.5.9 async installation.
     *
     * @param string $job_id
     */
    public static function handleLegacyCronEvent($job_id)
    {
        $job = \get_transient('fleekdash_install_job_' . $job_id);
        $state = $job['state'] ?? 'unknown';

        if ($state === 'complete' || $state === 'failed') {
            fleekdash_log_debug("FleekDash: Legacy cron fired for already-finished install job {$job_id} ({$state}); skipping.");
            return;
        }

        fleekdash_log_debug("FleekDash: Legacy cron fired for install job {$job_id} in state '{$state}'; marking as failed (migration to sync).");
        self::updateJobStatus($job_id, 'failed', 0, self::STEP_INSTALLATION_FAILED, 'Stale cron event from pre-v2.5.9; install should be retried via REST.');
    }

    /**
     * Process plugin installation (sync REST or wp-cron hook).
     */
    public static function processInstallation(string $job_id): void
    {
        try {
            if (\function_exists('set_time_limit')) {
                @\set_time_limit(600);
            }
            if (\function_exists('ignore_user_abort')) {
                @\ignore_user_abort(true);
            }
            if (\function_exists('ini_set')) {
                $memory = \defined('WP_MAX_MEMORY_LIMIT') ? WP_MAX_MEMORY_LIMIT : '512M';
                @\ini_set('memory_limit', $memory);
            }

            $job = self::readInstallJobFromTransient($job_id);
            if ($job === null) {
                fleekdash_log_debug('FleekDash: Install job not found: ' . $job_id);
                return;
            }
            self::runInstallPipeline($job_id, $job);
        } catch (\Exception $e) {
            fleekdash_log_error('FleekDash: Installation exception: ' . $e->getMessage());
            self::updateJobStatus($job_id, 'failed', 0, 'Installation error', $e->getMessage());
        }
    }

    /**
     * @return array{slug: string, activate: bool}|null
     */
    private static function readInstallJobFromTransient(string $job_id): ?array
    {
        $job_status = \get_transient('fleekdash_install_job_' . $job_id);
        if ($job_status === false) {
            return null;
        }
        $slug = isset($job_status['slug']) && \is_string($job_status['slug']) ? $job_status['slug'] : '';
        $activate = !empty($job_status['activate']);
        if ($slug === '') {
            return null;
        }
        return array(
            'slug' => $slug,
            'activate' => $activate,
        );
    }

    /**
     * Loads plugin.php helpers via PluginHelper when available.
     */
    private static function loadPluginPhpDependencies(): void
    {
        PluginHelper::ensurePluginApiLoaded();
    }

    /**
     * When the slug is already registered in get_plugins(), finish job (activate optional).
     *
     * @return bool true if the pipeline should stop (job finished or failed verification)
     */
    private static function completePipelineIfPluginAlreadyInstalled(string $job_id, string $slug, bool $activate): bool
    {
        $plugin_file = self::findInstalledPluginFileBySlug($slug, \get_plugins());
        if ($plugin_file === null) {
            return false;
        }

        self::finalizeExistingPluginInstallJob($job_id, $slug, $plugin_file, $activate);
        return true;
    }

    /**
     * @param string $plugin_file Relative path from get_plugins() keys
     */
    private static function finalizeExistingPluginInstallJob(string $job_id, string $slug, string $plugin_file, bool $activate): void
    {
        fleekdash_log_debug("FleekDash: Plugin {$slug} already installed as {$plugin_file}");
        self::updateJobStatus($job_id, 'verifying', 80, 'Plugin already installed, verifying...');

        $all_plugins = \get_plugins();
        if (!isset($all_plugins[$plugin_file])) {
            self::updateJobStatus($job_id, 'failed', 0, 'Verification failed', 'Plugin file not found');
            return;
        }

        if ($activate) {
            self::updateJobStatus($job_id, 'activating', 90, 'Activating plugin...');
            self::loadPluginPhpDependencies();
            $activate_result = \activate_plugin($plugin_file);
            if (\is_wp_error($activate_result)) {
                self::updateJobStatus(
                    $job_id,
                    'complete',
                    100,
                    'Plugin already installed but activation failed',
                    $activate_result->get_error_message(),
                );
                return;
            }
            self::updateJobStatus($job_id, 'complete', 100, 'Plugin already installed and activated successfully!');
        } else {
            self::updateJobStatus($job_id, 'complete', 100, 'Plugin already installed successfully!');
        }

        self::invalidateFleekdashPluginListCache();
    }

    /**
     * @param array{slug: string, activate: bool} $job
     */
    private static function runInstallPipeline(string $job_id, array $job): void
    {
        self::loadPluginPhpDependencies();
        if (self::completePipelineIfPluginAlreadyInstalled($job_id, $job['slug'], $job['activate'])) {
            return;
        }

        self::updateJobStatus($job_id, 'downloading', 10, 'Downloading plugin from WordPress.org...');
        require_once \ABSPATH . 'wp-admin/includes/file.php';
        require_once \ABSPATH . 'wp-admin/includes/misc.php';
        require_once \ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
        require_once \ABSPATH . 'wp-admin/includes/plugin-install.php';

        $api = \plugins_api(
            'plugin_information',
            array(
                'slug' => $job['slug'],
                'fields' => array(
                    'sections' => false,
                    'tags' => false,
                ),
            ),
        );
        if (\is_wp_error($api)) {
            self::updateJobStatus(
                $job_id,
                'failed',
                0,
                'Failed to get plugin information',
                $api->get_error_message(),
            );
            return;
        }

        self::executeFreshPluginInstall($job_id, $job, $api);
    }

    /**
     * @param array{slug: string, activate: bool} $job
     * @param \stdClass $api
     */
    private static function executeFreshPluginInstall(string $job_id, array $job, \stdClass $api): void
    {
        $version_label = isset($api->version) && \is_string($api->version) ? ('v' . $api->version) : 'latest';
        self::updateJobStatus(
            $job_id,
            'downloading',
            30,
            "Downloading {$api->name} ({$version_label})...",
        );
        self::updateJobStatus($job_id, 'installing', 50, 'Installing plugin files...');

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

        self::removeOrphanPluginDirectoryIfNeeded($job['slug']);

        $plugin_file = self::installPluginPackageAndResolveBootstrapFile($job_id, $job['slug'], $api);
        if ($plugin_file === null) {
            return;
        }

        if (!self::assertPluginRegisteredAfterInstallOrFail($job_id, $plugin_file)) {
            return;
        }

        self::finalizeInstallWithOptionalActivation($job_id, $plugin_file, $job['activate']);
        self::invalidateFleekdashPluginListCache();
    }

    private static function initFilesystemForInstallOrFail(string $job_id): bool
    {
        if (!\class_exists(FilesystemHelper::class)) {
            require_once FLEEKDASH_PLUGIN_DIR . 'includes/Core/FilesystemHelper.php';
        }
        if (!FilesystemHelper::safe_init()) {
            self::updateJobStatus($job_id, 'failed', 0, 'Filesystem initialization failed');
            return false;
        }
        return true;
    }

    /**
     * @param \stdClass $api
     * @return string|null Relative plugin bootstrap file
     */
    private static function installPluginPackageAndResolveBootstrapFile(string $job_id, string $slug, \stdClass $api): ?string
    {
        $skin = new \WP_Ajax_Upgrader_Skin();
        $upgrader = new \Plugin_Upgrader($skin);

        self::updateJobStatus(
            $job_id,
            'installing',
            55,
            'Unpacking and copying files (large plugins may take several minutes)...',
        );

        $result = $upgrader->install($api->download_link);
        if (\is_wp_error($result)) {
            return self::recoverBootstrapFileAfterInstallError(
                $job_id,
                $slug,
                $api->download_link,
                $upgrader,
                $result,
            );
        }
        if ($result === false) {
            self::updateJobStatus(
                $job_id,
                'failed',
                0,
                self::STEP_INSTALLATION_FAILED,
                'Unknown error during installation',
            );
            return null;
        }

        return self::finalizeBootstrapPathOrMarkFailed($job_id, $slug, $upgrader->plugin_info());
    }

    /**
     * @param string|null $plugin_file
     * @return string|null
     */
    private static function finalizeBootstrapPathOrMarkFailed(string $job_id, string $slug, ?string $plugin_file): ?string
    {
        $resolved = $plugin_file;
        if (!$resolved) {
            $resolved = self::findInstalledPluginFileBySlug($slug, \get_plugins());
            if ($resolved) {
                fleekdash_log_debug(
                    "FleekDash: Resolved plugin file by slug after install (plugin_info empty): {$resolved}",
                );
            }
        }
        if ($resolved === null) {
            self::updateJobStatus(
                $job_id,
                'failed',
                0,
                self::STEP_INSTALLATION_FAILED,
                'Could not determine plugin file',
            );
        }
        return $resolved;
    }

    /**
     * @return string|null
     */
    private static function recoverBootstrapFileAfterInstallError(
        string $job_id,
        string $slug,
        string $download_link,
        \Plugin_Upgrader $upgrader,
        \WP_Error $result
    ): ?string {
        $error_message = $result->get_error_message();
        $folder_exists = stripos($error_message, 'Destination folder already exists') !== false
            || stripos($error_message, 'folder already exists') !== false;

        if (!$folder_exists) {
            self::updateJobStatus($job_id, 'failed', 0, self::STEP_INSTALLATION_FAILED, $error_message);
            return null;
        }

        fleekdash_log_debug("FleekDash: Installation returned 'folder exists' error, verifying if plugin is actually installed...");

        $plugin_file = $upgrader->plugin_info();
        $all_plugins_after = \get_plugins();
        if (!$plugin_file) {
            $plugin_file = self::findInstalledPluginFileBySlug($slug, $all_plugins_after);
            if ($plugin_file) {
                fleekdash_log_debug("FleekDash: Found plugin file manually: {$plugin_file}");
            }
        }

        if ($plugin_file && isset($all_plugins_after[$plugin_file])) {
            fleekdash_log_debug("FleekDash: Plugin {$slug} is installed and working, treating as success");
            return self::finalizeBootstrapPathOrMarkFailed($job_id, $slug, $plugin_file);
        }

        return self::retryInstallAfterRemovingOrphanFolder($job_id, $slug, $download_link, $upgrader);
    }

    /**
     * @return string|null
     */
    private static function retryInstallAfterRemovingOrphanFolder(
        string $job_id,
        string $slug,
        string $download_link,
        \Plugin_Upgrader $upgrader
    ): ?string {
        fleekdash_log_debug(
            'FleekDash: Folder exists but plugin not valid — removing partial folder and retrying install once',
        );
        self::removeOrphanPluginDirectoryIfNeeded($slug);
        $retry_result = $upgrader->install($download_link);
        if (\is_wp_error($retry_result) || $retry_result === false) {
            $failure_detail = \is_wp_error($retry_result)
                ? $retry_result->get_error_message()
                : 'Unknown error during installation';
            self::updateJobStatus(
                $job_id,
                'failed',
                0,
                self::STEP_INSTALLATION_FAILED,
                $failure_detail,
            );
            return null;
        }

        $plugin_file = $upgrader->plugin_info();
        if (!$plugin_file) {
            $plugin_file = self::findInstalledPluginFileBySlug($slug, \get_plugins());
        }
        if (!$plugin_file) {
            self::updateJobStatus(
                $job_id,
                'failed',
                0,
                self::STEP_INSTALLATION_FAILED,
                'Could not determine plugin file after removing partial folder',
            );
            return null;
        }

        fleekdash_log_debug('FleekDash: Install succeeded after removing partial plugin directory');
        return $plugin_file;
    }

    private static function assertPluginRegisteredAfterInstallOrFail(string $job_id, string $plugin_file): bool
    {
        self::updateJobStatus($job_id, 'verifying', 80, 'Verifying installation...');

        if (!\function_exists('get_plugins')) {
            self::loadPluginPhpDependencies();
        }

        $all_plugins = \get_plugins();
        if (!isset($all_plugins[$plugin_file])) {
            self::updateJobStatus(
                $job_id,
                'failed',
                0,
                'Verification failed',
                'Plugin not found after installation',
            );
            return false;
        }
        return true;
    }

    private static function finalizeInstallWithOptionalActivation(string $job_id, string $plugin_file, bool $activate): void
    {
        if ($activate) {
            self::updateJobStatus($job_id, 'activating', 90, 'Activating plugin...');
            self::loadPluginPhpDependencies();
            $activate_result = \activate_plugin($plugin_file);
            if (\is_wp_error($activate_result)) {
                self::updateJobStatus(
                    $job_id,
                    'complete',
                    100,
                    'Plugin installed but activation failed',
                    $activate_result->get_error_message(),
                );
            } else {
                self::updateJobStatus($job_id, 'complete', 100, 'Plugin installed and activated successfully!');
            }
            return;
        }
        self::updateJobStatus($job_id, 'complete', 100, 'Plugin installed successfully!');
    }

    private static function invalidateFleekdashPluginListCache(): void
    {
        if (\class_exists(PluginHelpers::class)) {
            PluginHelpers::invalidatePluginsCache();
        }
    }

    /**
     * Delete WP_PLUGIN_DIR/{slug} when the directory exists but WordPress does not list that slug
     * as an installed plugin (interrupted download / corrupt partial tree).
     */
    private static function removeOrphanPluginDirectoryIfNeeded(string $slug): void
    {
        $slug_ok = \is_string($slug) && $slug !== ''
            && (\preg_match('/^[a-z0-9][a-z0-9-]*$/', $slug) === 1 || \preg_match('/^\d+[a-z0-9-]+$/', $slug) === 1);
        $plugin_root = \defined('WP_PLUGIN_DIR') ? \WP_PLUGIN_DIR : \ABSPATH . 'wp-content/plugins';
        $dir = $plugin_root . '/' . $slug;

        if (!$slug_ok || !\is_dir($dir) || self::findInstalledPluginFileBySlug($slug, \get_plugins()) !== null) {
            return;
        }

        $fs = FilesystemHelper::get_instance();
        if ($fs === null || !\method_exists($fs, 'delete')) {
            fleekdash_log_debug("FleekDash: Orphan plugin folder cleanup skipped (filesystem unavailable): {$dir}");
            return;
        }

        $deleted = $fs->delete($dir, true);
        if ($deleted) {
            fleekdash_log_debug("FleekDash: Removed orphan/partial plugin directory: {$dir}");
            if (\function_exists('wp_clean_plugins_cache')) {
                \wp_clean_plugins_cache();
            }
            self::invalidateFleekdashPluginListCache();
        } else {
            fleekdash_log_debug("FleekDash: Could not remove orphan plugin directory (check permissions): {$dir}");
        }
    }

    /**
     * Find bootstrap file for a plugin slug from a get_plugins() map (folder name or single-file plugin).
     *
     * @param string $slug Directory slug from WordPress.org (e.g. woocommerce).
     * @param array<string, array<string, string>> $all_plugins Result of get_plugins().
     * @return string|null Relative path such as woocommerce/woocommerce.php
     */
    private static function findInstalledPluginFileBySlug(string $slug, array $all_plugins): ?string
    {
        $resolved = null;
        foreach ($all_plugins as $file => $_plugin_data) {
            $installed_slug = dirname($file);
            if ($installed_slug === '.' || $installed_slug === '') {
                $installed_slug = basename($file, '.php');
            }
            if ($installed_slug === $slug) {
                $resolved = $file;
                break;
            }
        }
        return $resolved;
    }

    private static function updateJobStatus(string $job_id, string $state, int $progress, string $step, ?string $error = null): void
    {
        $job_status = \get_transient('fleekdash_install_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_install_job_' . $job_id, $job_status, 3600);

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

PluginDirectoryInstaller::init();
