<?php

/**
 * LolaCore � ScanDataCollector
 *
 * @package LolaCore
 * @license GPL-2.0-or-later
 */


declare(strict_types=1);

namespace LolaCore\Scanner;

/**
 * Class ScanDataCollector
 *
 * Collects structured data from the WordPress installation.
 * Each method gathers a specific category of information using
 * native WordPress functions — no AI involved.
 *
 * COMPLIANCE NOTE (WordPress.org Guideline 7):
 * getVulnerabilities() is the ONLY method that makes an external HTTP call.
 * It MUST NOT be called during automatic activation scan. It is only invoked
 * when the user explicitly requests a vulnerability check via chat.
 */
class ScanDataCollector
{
    /**
     * Collect all local site data (excludes vulnerabilities).
     *
     * This is the method called during plugin activation.
     * It orchestrates all local collectors and returns a unified array.
     *
     * @return array Complete site scan data.
     */
    public function collectAll(): array
    {
        $plugins = $this->getPluginsInfo();

        return [
            'scan_timestamp' => \current_time('mysql'),
            'wordpress'      => $this->getWordPressInfo(),
            'plugins'        => $plugins,
            'php'            => $this->getPHPInfo(),
            'database'       => $this->getDatabaseInfo(),
            'content'        => $this->getContentInfo(),
            'theme'          => $this->getThemeInfo(),
            'security'       => $this->getSecurityInfo(),
            'backup'         => $this->getBackupInfo(),
            'abilities'      => $this->getAbilitiesInfo(),
            'elementor'      => $this->getElementorInfo(),
        ];
    }

    /**
     * Gather core WordPress information.
     *
     * @return array Keys: wp_version, is_multisite, site_url, home_url, is_updated.
     */
    public function getWordPressInfo(): array
    {
        global $wp_version;

        $updates = \get_core_updates();
        $isUpdated = true;

        if (\is_array($updates) && !empty($updates)) {
            $latest = $updates[0];
            if (isset($latest->response) && $latest->response === 'upgrade') {
                $isUpdated = false;
            }
        }

        return [
            'wp_version'   => $wp_version,
            'is_updated'   => $isUpdated,
            'site_url'     => \site_url(),
            'home_url'     => \home_url(),
            'is_multisite' => \is_multisite(),
        ];
    }

    /**
     * Gather installed plugins information.
     *
     * @return array List of plugins with slug, name, version, update status, and active status.
     */
    public function getPluginsInfo(): array
    {
        if (!\function_exists('get_plugins')) {
            require_once ABSPATH . 'wp-admin/includes/plugin.php';
        }

        if (!\function_exists('get_plugin_updates')) {
            require_once ABSPATH . 'wp-admin/includes/update.php';
        }

        $allPlugins = \get_plugins();
        $updates = \get_plugin_updates();
        $activePlugins = \get_option('active_plugins', []);

        $result = [];

        foreach ($allPlugins as $pluginFile => $pluginData) {
            $slug = \dirname($pluginFile);
            if ($slug === '.') {
                $slug = \basename($pluginFile, '.php');
            }

            $hasUpdate = isset($updates[$pluginFile]);
            $updateVersion = $hasUpdate
                ? ($updates[$pluginFile]->update->new_version ?? null)
                : null;

            $result[] = [
                'slug'             => $slug,
                'name'             => $pluginData['Name'] ?? $slug,
                'version'          => $pluginData['Version'] ?? 'unknown',
                'update_available' => $hasUpdate,
                'update_version'   => $updateVersion,
                'is_active'        => \in_array($pluginFile, $activePlugins, true),
            ];
        }

        return $result;
    }

    /**
     * Check known vulnerabilities for installed plugins via Wordfence API.
     *
     * ⚠️ COMPLIANCE: This method MUST NOT be called during automatic activation scan.
     * WordPress.org Guideline 7: "Plugins may not contact external servers without
     * explicit and authorized consent."
     *
     * This method is ONLY called:
     * 1. When user explicitly requests vulnerability check via chat (post API-key).
     * 2. When Lola proactively suggests it and user confirms.
     *
     * External service: Wordfence Vulnerability API
     * URL: https://www.wordfence.com/api/intelligence/v2/vulnerabilities/scanner
     * Privacy: https://www.wordfence.com/privacy-policy/
     *
     * @param array $plugins Plugin list from getPluginsInfo().
     * @return array List of vulnerability findings, empty array on failure.
     */
    public function getVulnerabilities(array $plugins): array
    {
        $apiUrl = 'https://www.wordfence.com/api/intelligence/v2/vulnerabilities/scanner';

        $response = \wp_remote_get($apiUrl, [
            'timeout'    => 10,
            'sslverify'  => true,
            'user-agent' => 'LolaCore WordPress Plugin',
        ]);

        if (\is_wp_error($response)) {
            return [];
        }

        $statusCode = \wp_remote_retrieve_response_code($response);
        if ($statusCode !== 200) {
            return [];
        }

        $body = \wp_remote_retrieve_body($response);
        $vulnerabilities = \json_decode($body, true);

        if (!\is_array($vulnerabilities)) {
            return [];
        }

        // Build a slug-to-version map from installed plugins
        $installedMap = [];
        foreach ($plugins as $plugin) {
            $installedMap[$plugin['slug']] = $plugin['version'];
        }

        // Filter vulnerabilities relevant to installed plugins
        $findings = [];
        foreach ($vulnerabilities as $vulnId => $vuln) {
            $software = $vuln['software'] ?? [];

            foreach ($software as $affected) {
                $slug = $affected['slug'] ?? '';
                $type = $affected['type'] ?? '';

                if ($type !== 'plugin' || !isset($installedMap[$slug])) {
                    continue;
                }

                $findings[] = [
                    'vuln_id'           => (string) $vulnId,
                    'plugin_slug'       => $slug,
                    'installed_version' => $installedMap[$slug],
                    'title'             => $vuln['title'] ?? 'Unknown vulnerability',
                    'cve'               => $vuln['cve'] ?? null,
                    'severity'          => $vuln['cvss']['rating'] ?? 'unknown',
                    'patched_version'   => $affected['patched_versions'] ?? null,
                ];
            }
        }

        return $findings;
    }

    /**
     * Gather PHP environment information.
     *
     * @return array Keys: php_version, extensions (list of relevant loaded extensions).
     */
    public function getPHPInfo(): array
    {
        $requiredExtensions = ['json', 'mbstring', 'openssl', 'curl', 'mysqli', 'dom', 'xml'];
        $loadedExtensions = [];

        foreach ($requiredExtensions as $ext) {
            $loadedExtensions[$ext] = \extension_loaded($ext);
        }

        return [
            'php_version' => PHP_VERSION,
            'extensions'  => $loadedExtensions,
        ];
    }

    /**
     * Gather database size and health metrics.
     *
     * @return array Keys: total_size_mb, tables_count, orphan_tables, expired_transients_count, post_revisions_count.
     */
    public function getDatabaseInfo(): array
    {
        global $wpdb;

        // Total database size in MB
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared -- Custom LolaCore table query.
        $sizeResult = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024, 2)
                 FROM information_schema.TABLES
                 WHERE table_schema = %s",
                DB_NAME,
            ),
        );

        // Total tables count
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared -- Custom LolaCore table query.
        $tablesCount = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*)
                 FROM information_schema.TABLES
                 WHERE table_schema = %s",
                DB_NAME,
            ),
        );

        // Orphan tables (tables without the WP prefix)
        $wpPrefix = $wpdb->prefix;
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared -- Custom LolaCore table query.
        $orphanCount = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*)
                 FROM information_schema.TABLES
                 WHERE table_schema = %s
                 AND table_name NOT LIKE %s",
                DB_NAME,
                $wpdb->esc_like($wpPrefix) . '%',
            ),
        );

        // Expired transients count
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared -- Custom LolaCore table query.
        $expiredTransients = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*)
                 FROM {$wpdb->options}
                 WHERE option_name LIKE %s
                 AND option_value < %d",
                $wpdb->esc_like('_transient_timeout_') . '%',
                \time(),
            ),
        );

        // Post revisions count
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared -- Custom LolaCore table query.
        $revisionsCount = (int) $wpdb->get_var(
            "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = 'revision'",
        );

        return [
            'total_size_mb'            => (float) ($sizeResult ?? 0),
            'tables_count'             => $tablesCount,
            'orphan_tables'            => $orphanCount,
            'expired_transients_count' => $expiredTransients,
            'post_revisions_count'     => $revisionsCount,
        ];
    }

    /**
     * Gather content statistics.
     *
     * @return array Keys: posts_count, pages_count, products_count, media_count, users_count_by_role.
     */
    public function getContentInfo(): array
    {
        $postCounts = \wp_count_posts('post');
        $pageCounts = \wp_count_posts('page');

        // WooCommerce products (only if CPT is registered)
        $productsCount = 0;
        if (\post_type_exists('product')) {
            $productCounts = \wp_count_posts('product');
            $productsCount = (int) ($productCounts->publish ?? 0);
        }

        // Media count
        $mediaCounts = \wp_count_attachments();
        $totalMedia = 0;
        if (\is_object($mediaCounts)) {
            foreach (\get_object_vars($mediaCounts) as $count) {
                $totalMedia += (int) $count;
            }
        }

        // Users by role
        $usersByRole = [];
        $countUsers = \count_users();
        if (isset($countUsers['avail_roles'])) {
            $usersByRole = $countUsers['avail_roles'];
        }

        return [
            'posts_count'         => (int) ($postCounts->publish ?? 0),
            'pages_count'         => (int) ($pageCounts->publish ?? 0),
            'products_count'      => $productsCount,
            'media_count'         => $totalMedia,
            'users_count_by_role' => $usersByRole,
        ];
    }

    /**
     * Gather active theme information.
     *
     * @return array Keys: active_theme_name, theme_version, is_child_theme, parent_theme, update_available.
     */
    public function getThemeInfo(): array
    {
        $theme = \wp_get_theme();
        $isChild = $theme->parent() !== false;
        $parentName = $isChild ? $theme->parent()->get('Name') : null;

        // Check for theme updates
        $themeUpdates = \get_site_transient('update_themes');
        $hasUpdate = false;

        if (\is_object($themeUpdates) && isset($themeUpdates->response)) {
            $hasUpdate = isset($themeUpdates->response[$theme->get_stylesheet()]);
        }

        return [
            'active_theme_name' => $theme->get('Name'),
            'theme_version'     => $theme->get('Version'),
            'is_child_theme'    => $isChild,
            'parent_theme'      => $parentName,
            'update_available'  => $hasUpdate,
        ];
    }

    /**
     * Gather security-related configuration.
     *
     * @return array Keys: ssl_active, file_editing_disabled, debug_mode, debug_log.
     */
    public function getSecurityInfo(): array
    {
        return [
            'ssl_active'            => \is_ssl(),
            'file_editing_disabled' => \defined('DISALLOW_FILE_EDIT') && DISALLOW_FILE_EDIT,
            'debug_mode'            => \defined('WP_DEBUG') && WP_DEBUG,
            'debug_log'             => \defined('WP_DEBUG_LOG') && WP_DEBUG_LOG,
        ];
    }

    /**
     * Detect known backup plugins and their status.
     *
     * @return array Keys: has_backup_plugin, detected_plugins (list of found backup solutions).
     */
    public function getBackupInfo(): array
    {
        $knownBackupPlugins = [
            'updraftplus/updraftplus.php'                       => 'UpdraftPlus',
            'backwpup/backwpup.php'                             => 'BackWPup',
            'developer-starter/developer-starter.php'           => 'BlogVault',
            'blogvault-real-time-backup/developer-starter.php'  => 'BlogVault',
            'jetpack/jetpack.php'                               => 'Jetpack Backup',
        ];

        $activePlugins = \get_option('active_plugins', []);
        $detectedPlugins = [];

        foreach ($knownBackupPlugins as $pluginFile => $label) {
            if (\in_array($pluginFile, $activePlugins, true)) {
                $detectedPlugins[] = $label;
            }
        }

        return [
            'has_backup_plugin' => !empty($detectedPlugins),
            'detected_plugins'  => $detectedPlugins,
        ];
    }

    /**
     * List abilities registered by other plugins via the WordPress Abilities API (WP 6.9+).
     *
     * @return array Keys: available (bool), abilities (list of registered abilities).
     */
    public function getAbilitiesInfo(): array
    {
        // Check if the Abilities API function exists (WP 6.9+)
        if (!\function_exists('wp_get_registered_abilities')) {
            return [
                'available' => false,
                'abilities' => [],
            ];
        }

        /** @var array $abilities */
        $abilities = \wp_get_registered_abilities();

        $result = [];
        foreach ($abilities as $ability) {
            $result[] = [
                'name'     => $ability['name'] ?? 'unknown',
                'label'    => $ability['label'] ?? '',
                'category' => $ability['category'] ?? '',
                'plugin'   => $ability['plugin'] ?? '',
            ];
        }

        return [
            'available' => true,
            'abilities' => $result,
        ];
    }

    /**
     * Gather Elementor-specific information (if Elementor is active).
     *
     * @return array|null Null if Elementor is not installed/active. Otherwise:
     *   Keys: version, is_pro, templates_count, elementor_pages_count, kit_colors, kit_fonts.
     */
    public function getElementorInfo(): ?array
    {
        // Check if Elementor is active
        if (!\defined('ELEMENTOR_VERSION')) {
            return null;
        }

        $isPro = \defined('ELEMENTOR_PRO_VERSION');
        $version = ELEMENTOR_VERSION;

        // Count Elementor templates
        global $wpdb;
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared -- Custom LolaCore table query.
        $templatesCount = (int) $wpdb->get_var(
            "SELECT COUNT(*) FROM {$wpdb->posts}
             WHERE post_type = 'elementor_library'
             AND post_status = 'publish'",
        );

        // Count pages built with Elementor
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared -- Custom LolaCore table query.
        $elementorPagesCount = (int) $wpdb->get_var(
            "SELECT COUNT(*) FROM {$wpdb->postmeta}
             WHERE meta_key = '_elementor_edit_mode'
             AND meta_value = 'builder'",
        );

        // Kit colors and fonts (from the active Elementor kit)
        $kitColors = [];
        $kitFonts = [];

        $activeKitId = \get_option('elementor_active_kit');
        if ($activeKitId) {
            $kitSettings = \get_post_meta((int) $activeKitId, '_elementor_page_settings', true);

            if (\is_array($kitSettings)) {
                // Extract system colors
                if (isset($kitSettings['system_colors']) && \is_array($kitSettings['system_colors'])) {
                    foreach ($kitSettings['system_colors'] as $color) {
                        $kitColors[] = [
                            'id'    => $color['_id'] ?? '',
                            'title' => $color['title'] ?? '',
                            'color' => $color['color'] ?? '',
                        ];
                    }
                }

                // Extract system typography
                if (isset($kitSettings['system_typography']) && \is_array($kitSettings['system_typography'])) {
                    foreach ($kitSettings['system_typography'] as $font) {
                        $kitFonts[] = [
                            'id'          => $font['_id'] ?? '',
                            'title'       => $font['title'] ?? '',
                            'font_family' => $font['typography_font_family'] ?? '',
                        ];
                    }
                }
            }
        }

        return [
            'version'               => $version,
            'is_pro'                => $isPro,
            'templates_count'       => $templatesCount,
            'elementor_pages_count' => $elementorPagesCount,
            'kit_colors'            => $kitColors,
            'kit_fonts'             => $kitFonts,
        ];
    }
}