<?php

namespace CleantalkSP\SpbctWP\VulnerabilityAlarm;

use CleantalkSP\SpbctWP\VulnerabilityAlarm\Dto\ApiResults;
use CleantalkSP\SpbctWP\VulnerabilityAlarm\Dto\PluginReport;
use CleantalkSP\SpbctWP\VulnerabilityAlarm\Dto\ThemeReport;
use CleantalkSP\SpbctWP\VulnerabilityAlarm\Exceptions\VulnerabilityAlarmServiceException;

class VulnerabilityAlarm
{
    const VULNERABILITY_LIST = 'spbc_vulnerabilities';

    /**
     * @var array
     */
    public static $plugins = [];

    /**
     * @var array
     */
    public static $themes = [];

    /**
     * * * * Common * * *
     */

    /**
     * Get vulnerabilities from cloud and write to storage
     * @throws VulnerabilityAlarmServiceException
     */
    public static function updateWPModulesVulnerabilities()
    {
        VulnerabilityAlarmService::updateVulnerabilitiesLibrary();
    }

    /**
     * Delete all the data for vulnerabilities storage
     */
    public static function dropWPModulesVulnerabilities()
    {
        VulnerabilityAlarmService::dropVulnerabilitiesLibrary();
    }

    /**
     * @param string $module_type
     * @param string $plugin_slug
     * @param string $plugin_id
     * @param string $psc_cert_name
     * @param string $research_url
     * @return string|null
     */
    public static function showSafeBadge($module_type = 'plugin', $plugin_slug = '', $plugin_id = '', $psc_cert_name = '', $research_url = '', $placed_on_installed_list = false)
    {
        if ( ! empty($psc_cert_name) ) {
            return VulnerabilityAlarmView::showSafeBadgePSC($module_type, $plugin_slug, $plugin_id, $psc_cert_name, $research_url, $placed_on_installed_list);
        }
        return null;
    }

    /**
     * * * * Plugins * * *
     */

    /**
     * @param $plugin_slug
     * @param $plugin_version
     * @return Dto\PluginReport|ThemeReport|false
     */
    public static function checkPluginVulnerabilityStatic($plugin_slug, $plugin_version)
    {
        /** @var ApiResults|null $list */
        $list = get_option(self::VULNERABILITY_LIST);
        if ( $list === false ) {
            return false;
        }
        static::$plugins = $list->plugins;
        return VulnerabilityAlarmService::getPluginReportStatic($plugin_slug, $plugin_version);
    }

    public static function getPluginAlarmHTML($plugin_file, $plugin_data)
    {
        return VulnerabilityAlarmView::showPluginAlarm($plugin_file, $plugin_data);
    }

    /**
     * Retrieves a list of plugins considered safe based on vulnerability reports.
     *
     * This method evaluates each plugin provided in the $plugins parameter against vulnerability reports
     * obtained from a cloud service. It first checks if there are any static reports available locally.
     * If not, or if the $refresh_static_data_before flag is set, it makes a call to the cloud service to
     * obtain the latest vulnerability reports for the plugins.
     *
     * Each plugin is then checked against these reports to determine if it is considered safe. A plugin is
     * deemed safe if it does not appear in the vulnerability reports or if it meets certain criteria specified
     * in the reports (e.g., being of a version that is not affected by known vulnerabilities).
     *
     * @param array $plugins An array of plugins to check. Each plugin should be an associative array containing
     *                       at least the plugin slug.
     * @param bool $refresh_static_data_before Whether to refresh the static data with the latest reports from
     *                                         the cloud service before performing the check. Defaults to true.
     * @return array An array of plugins that are considered safe. Each element in the array is an associative
     *               array containing the plugin's slug, its ID, any PSC certification
     *               it may have, and the full report object from the vulnerability check.
     */
    public static function getSafePlugins($plugins, $refresh_static_data_before = true)
    {
        $safe_plugins = array();
        $plugins_reports_to_use = array();

        //check if static reports available
        $static_reports = get_option(self::VULNERABILITY_LIST);
        if ( $static_reports instanceof ApiResults ) {
            $plugins_reports_to_use = $static_reports->plugins;
        }

        // if no static data provided or the flag is set, run api call before check
        if ( $refresh_static_data_before ) {
            $plugins_to_cloud = [];
            // Prepare data to the cloud call
            foreach ( $plugins as $plugin_file => $plugin ) {
                $verified_slug = static::getPluginSlug($plugin, is_string($plugin_file) ? $plugin_file : '');
                if ( !empty($verified_slug) ) {
                    $plugin_name = is_array($plugin)
                        ? (isset($plugin['name']) ? $plugin['name'] : (isset($plugin['Name']) ? $plugin['Name'] : $verified_slug))
                        : (isset($plugin->name) ? $plugin->name : $verified_slug);
                    $plugin_uri = is_array($plugin)
                        ? (isset($plugin['homepage']) ? $plugin['homepage'] : (isset($plugin['PluginURI']) ? $plugin['PluginURI'] : ''))
                        : (isset($plugin->homepage) ? $plugin->homepage : '');
                    $plugin_version = static::getPluginVersion($plugin);

                    // Cloud matches modules by slug; Name/URI alone is not enough
                    // (e.g. Strong Testimonials has custom homepage, not wordpress.org/plugins/...).
                    $plugins_to_cloud[$plugin_name] = [
                        'slug'      => $verified_slug,
                        'PluginURI' => $plugin_uri,
                        'Version'   => $plugin_version,
                        'Name'      => $plugin_name,
                    ];
                }
            }

            // Get cloud report about plugins
            try {
                $api_report = VulnerabilityAlarmService::getCloudReport(['plugins' => $plugins_to_cloud]);
                $plugins_reports_to_use = $api_report->plugins;
            } catch (\Exception $_exception) {
                return $safe_plugins;
            }
        }

        if ( ! count($plugins_reports_to_use) ) {
            return $safe_plugins;
        }

        static::$plugins = $plugins_reports_to_use;

        foreach ( $plugins as $plugin_file => $plugin ) {
            $verified_slug = static::getPluginSlug($plugin, is_string($plugin_file) ? $plugin_file : '');
            $verified_version = static::getPluginVersion($plugin);

            if ( empty($verified_slug) ) {
                continue;
            }

            $safe_plugin_report = VulnerabilityAlarmService::getPluginReportStatic($verified_slug, $verified_version, true);

            if ( ! $safe_plugin_report ) {
                continue;
            }

            $psc = static::getPluginReportPSC($safe_plugin_report);
            if ( empty($psc) ) {
                continue;
            }

            $safe_plugins[] = [
                'slug' => $verified_slug,
                'id' => static::getPluginReportId($safe_plugin_report),
                'psc' => $psc,
                'report_object' => $safe_plugin_report,
                'current_version' => $verified_version
            ];
        }

        return $safe_plugins;
    }

    /**
     * Retrieves a list of plugins with a valid PSC (Plugin Security Certificate) only.
     *
     * This method first gathers a list of all installed plugins using the get_plugins() function. It then filters
     * these plugins through the getSafePlugins() method to identify which of them are considered safe based on
     * vulnerability reports. Among these safe plugins, it further filters to return only those with a valid PSC.
     *
     * Each plugin in the returned list includes its ID, slug, PSC, and the minimum safe version as determined by
     * the vulnerability reports. This method is useful for scenarios where you need to display or process only
     * plugins that have been certified for security.
     *
     * @return array An array of plugins that are considered safe and have a valid PSC. Each element in the array
     *               is an associative array containing the plugin's ID, slug, PSC, and version.
     */
    public static function getPSCSafePluginsOnly()
    {
        $installed_plugins = get_plugins();
        $safe_plugins = VulnerabilityAlarm::getSafePlugins($installed_plugins, false);

        $psc_safe_plugins = array();
        // Iterate over each plugin considered safe
        foreach ($safe_plugins as $safe_plugin) {
            // extract array item to report object
            $report_object = isset($safe_plugin['report_object']) && $safe_plugin['report_object'] instanceof PluginReport
                ? $safe_plugin['report_object']
                : false;
            $psc = $report_object
                ? $report_object->psc
                : '';
            $psc_safe_plugins[] = array(
                'id' => $safe_plugin['id'],
                'slug' => $safe_plugin['slug'],
                'psc' => $psc,
                'version' => $safe_plugin['current_version']
            );
        }
        return $psc_safe_plugins;
    }

    /**
     * Safely get plugin report ID that provided form API response
     * @param PluginReport $plugin_report
     * @return string
     */
    public static function getPluginReportId($plugin_report)
    {
        $id = '';

        if ( empty($plugin_report) || ! $plugin_report instanceof PluginReport) {
            return $id;
        }

        if ( property_exists($plugin_report, 'id')) {
            $id = $plugin_report->id;
        }
        return sanitize_title($id);
    }

    /**
     * Safely get plugin report PSC that provided form API response
     * @param PluginReport $plugin_report
     * @return string
     */
    public static function getPluginReportPSC($plugin_report)
    {
        $psc = '';

        //@ToDo remove this hack
        $plugin_report->psc = 'PSC';

        if (!empty($plugin_report->psc) && $plugin_report->app_status === 'safe_psc') {
            $psc = $plugin_report->psc;
        }
        return esc_html($psc);
    }

    /**
     * Safely get plugin slug from provided dataset
     *
     * @param array|object $plugin_dataset Array if provided WP plugin dataset as array, object otherwise
     * @param string $plugin_relative_dir Optional, relative path to wp-content/plugins
     *
     * @return string
     */
    public static function getPluginSlug($plugin_dataset, $plugin_relative_dir = '')
    {
        $slug = false;

        if (is_array($plugin_dataset)) {
            $slug = self::getPluginSlugFromArray($plugin_dataset);
        }

        if (!$slug && is_object($plugin_dataset)) {
            $slug = self::getPluginSlugFromObject($plugin_dataset);
        }

        if (!$slug && !empty($plugin_relative_dir) && is_string($plugin_relative_dir)) {
            $slug = self::getPluginSlugFromRelativePath($plugin_relative_dir);
        }

        if (!$slug && is_array($plugin_dataset)) {
            $slug = self::getPluginSlugFromTextDomain($plugin_dataset);
        }

        return false !== $slug ? esc_html($slug) : '';
    }

    /**
     * @param object $plugin_data_object
     *
     * @return false|string
     */
    public static function getPluginSlugFromObject($plugin_data_object)
    {
        $slug = false;
        if ( property_exists($plugin_data_object, 'slug') ) {
            $slug = !empty($plugin_data_object->slug) ? $plugin_data_object->slug : false;
        }
        return $slug;
    }

    /**
     * @param array $plugin_data_array
     *
     * @return false|string
     */
    public static function getPluginSlugFromArray($plugin_data_array)
    {
        $slug = false;
        if ( !empty($plugin_data_array['slug']) ) {
            $slug = $plugin_data_array['slug'];
        } elseif (!empty($plugin_data_array['Slug'])) {
            $slug = $plugin_data_array['Slug'];
        }
        return $slug;
    }

    public static function getPluginSlugFromTextDomain($plugin_data_array)
    {
        $slug = false;
        if (!empty($plugin_data_array['TextDomain'])) {
            $slug = $plugin_data_array['TextDomain'];
        }
        return $slug;
    }

    /**
     * @param string $path
     *
     * @return false|string
     */
    public static function getPluginSlugFromRelativePath($path)
    {
        $slug = false;
        if (strpos($path, '/') !== false) {
            $explode = explode('/', $path);
            $slug = !empty($explode[0]) ? $explode[0] : false;
        }
        return $slug;
    }

    /**
     * @param array|object $plugin Array if provided WP plugin dataset as array, object otherwise
     * @return string
     */
    public static function getPluginVersion($plugin)
    {
        $version = '';
        if ( !is_array($plugin) ) {
            if ( gettype($plugin) === 'object' ) {
                if (property_exists($plugin, 'version')) {
                    $version = $plugin->version;
                }
            }
        } else {
            $version = isset($plugin['version']) ? (string)$plugin['version'] : '';
            $version = empty($version) && isset($plugin['Version']) ? (string)$plugin['Version'] : $version;
        }
        return esc_html($version);
    }

    /**
     * Checks a single plugin's version against a cloud-based vulnerability report.
     *
     * This method attempts to retrieve a vulnerability report for a specific plugin module from a cloud service.
     * It sends the plugin module information to the cloud service and expects to receive a report detailing
     * any known vulnerabilities for that module. If the report is successfully retrieved, the method then checks
     * if the provided version of the plugin is listed as vulnerable within the report. This is done by delegating
     * the version check to the VulnerabilityAlarmService::isModuleVersionIsVulnerable method, which evaluates
     * the plugin version against the vulnerabilities listed in the report.
     *
     * @param string $module The slug or identifier of the plugin module to check.
     * @param string $version The version of the plugin module to evaluate for vulnerabilities.
     * @return bool Returns true if the plugin version is found to be vulnerable based on the cloud report,
     *              false otherwise. Also returns false if the report could not be retrieved or if the plugin
     *              is not found within the report.
     */
    public static function checkSinglePluginViaAPI($module, $version)
    {
        $modules = [
            'plugins' => [[$module, $version]],
            'themes' => [[]]
        ];
        // Get cloud report about plugin
        try {
            $report = VulnerabilityAlarmService::getCloudReport($modules);
        } catch (\Exception $_exception) {
            return false;
        }

        if (!isset($report->plugins[0])) {
            return false;
        }

        return VulnerabilityAlarmService::isModuleVersionIsVulnerable($report->plugins[0], $version);
    }

    /**
     * * * * Themes * * *
     */

    /**
     * @param string $theme_slug
     * @param string $theme_version
     *
     * @return Dto\PluginReport|ThemeReport|false|mixed
     */
    public static function checkThemeVulnerabilityStatic($theme_slug, $theme_version)
    {
        /** @var ApiResults|null $list */
        $list = get_option(self::VULNERABILITY_LIST);
        if ( $list === false ) {
            return false;
        }
        static::$themes = $list->themes;
        return VulnerabilityAlarmService::getThemeReportStatic($theme_slug, $theme_version);
    }

    /**
     * Displays an alarm for a theme identified as vulnerable.
     *
     * This method is a wrapper around the VulnerabilityAlarmView::showThemeAlarm() method. It is responsible for
     * displaying an alarm for a theme that has been identified as vulnerable. The method takes a theme report object
     * as its parameter, which contains details about the theme's vulnerability. The actual display logic and UI rendering
     * are handled by the VulnerabilityAlarmView::showThemeAlarm() method, to which this method delegates by passing
     * the theme report object.
     *
     * @param ThemeReport $theme_report An object containing the theme's details, including its vulnerability information.
     * @return string The string result of the VulnerabilityAlarmView::showThemeAlarm() method call
     */
    public static function getThemeAlarmHTML($theme_report)
    {
        return VulnerabilityAlarmView::showThemeAlarm($theme_report);
    }

    /**
     * @param array $themes
     *
     * @return array
     * @psalm-suppress PossiblyUnusedParam
     */
    public static function getSafeThemesViaApi($themes)
    {
        $theme_names = array();
        for ($i = 0; $i < count($themes); $i++) {
            $arr_slug = [$themes[$i]];
            array_push($theme_names, $arr_slug);
        }
        return VulnerabilityAlarmService::getSafeThemesViaAPI($theme_names);
    }
}
