<?php

namespace CleantalkSP\SpbctWP\VulnerabilityAlarm;

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

class VulnerabilityAlarmService
{
    /**
     * @throws VulnerabilityAlarmServiceException
     */
    public static function updateVulnerabilitiesLibrary()
    {
        $modules = self::getModules();
        $report = self::getReport($modules);
        self::writeReport($report);
        self::setSummaryLogPluginsAndThemes($modules);
    }

    /**
     * @param array $modules
     */
    public static function setSummaryLogPluginsAndThemes($modules)
    {
        global $spbc;

        if ($modules['themes'] && is_array($modules['themes'])) {
            $spbc->scan_themes_info['total_site_themes_count'] = count($modules['themes']);

            $number_of_vulnerable_themes = 0;
            $themes_info_requested = 0;
            $names_vulnerable_themes = array();

            foreach ($modules['themes'] as $theme) {
                $theme_slug = $theme[0];
                $theme_version = $theme[1];
                $theme_is_vulnerable = VulnerabilityAlarm::checkthemeVulnerabilityStatic($theme_slug, $theme_version);

                $themes_info_requested++;
                if ($theme_is_vulnerable) {
                    $names_vulnerable_themes[] = $theme_is_vulnerable;
                    $number_of_vulnerable_themes++;
                }
            }

            $spbc->scan_themes_info['names_vulnerable_themes'] = $names_vulnerable_themes;
            $spbc->scan_themes_info['themes_info_requested'] = $themes_info_requested;
            $spbc->scan_themes_info['themes_found_with_known_vulnerabilities'] = $number_of_vulnerable_themes;

            $spbc->save('scan_themes_info');
        }

        if ($modules['plugins'] && is_array($modules['plugins'])) {
            $spbc->scan_plugins_info['total_site_plugins_count'] = count($modules['plugins']);

            $number_of_vulnerable_plugins = 0;
            $plugins_info_requested = 0;
            $names_vulnerable_plugins = array();

            foreach ($modules['plugins'] as $plugin) {
                $plugin_slug = $plugin[0];
                $plugin_version = $plugin[1];
                $plugin_is_vulnerable = VulnerabilityAlarm::checkPluginVulnerabilityStatic($plugin_slug, $plugin_version);
                $plugins_info_requested++;
                if ($plugin_is_vulnerable) {
                    $names_vulnerable_plugins[] = $plugin_is_vulnerable;
                    $number_of_vulnerable_plugins++;
                }
            }

            $spbc->scan_plugins_info['names_vulnerable_plugins'] = $names_vulnerable_plugins;
            $spbc->scan_plugins_info['plugins_info_requested'] = $plugins_info_requested;
            $spbc->scan_plugins_info['plugins_found_with_known_vulnerabilities'] = $number_of_vulnerable_plugins;

            $spbc->save('scan_plugins_info');
        }
    }

    /**
     * @param ItemReport $module_report
     * @param $version_to_check
     * @param $safety_check
     * @return false|ItemReport
     */
    private static function checkModule($module_report, $version_to_check, $safety_check = false)
    {
        if ( $safety_check ) {
            if ( static::isModuleVersionIsSafe($module_report, $version_to_check) ) {
                return $module_report;
            }
        } elseif ( static::isModuleVersionIsVulnerable($module_report, $version_to_check) ) {
            return $module_report;
        }
        return false;
    }

    /**
     * Checks if a plugin is considered safe or vulnerable based on its version.
     *
     * This method evaluates a plugin's safety by comparing its version against a list of known vulnerabilities
     * or safe versions obtained from an API. It iterates over a static list of plugins, matching the provided
     * slug with those in the list. For each match, it checks the plugin version against the minimum and maximum
     * safe versions. If the plugin version falls within the safe range, the plugin is considered safe, and its
     * details are returned. Otherwise, the method returns false, indicating the plugin may be vulnerable or its
     * safety cannot be determined.
     *
     * @param string $wp_plugin_slug The slug of the wp system plugin to check.
     * @param string $wp_plugin_version The version of the wp system plugin to check.
     * @param bool $check_is_safe Optional. If true, specifically checks if the plugin version is marked as safe.
     *                           Defaults to false, which checks if the plugin is vulnerable.
     * @return ItemReport|false False if the plugin version is not provided, the plugin is not found.
     * Otherwise, returns the plugin report object indicating the plugin is safe or vulnerable.
     */
    public static function getPluginReportStatic($wp_plugin_slug, $wp_plugin_version, $check_is_safe = false)
    {
        return self::getItemReportStatic($wp_plugin_slug, $wp_plugin_version, $check_is_safe);
    }

    /**
     * @param string $item_slug
     * @param string $item_version
     * @param bool $check_is_safe
     * @param bool $theme
     *
     * @return PluginReport|ThemeReport|false|mixed
     */
    public static function getItemReportStatic($item_slug, $item_version, $check_is_safe = false, $theme = false)
    {
        $results = array();

        if ( empty($item_version) ) {
            return false;
        }

        /**
         * This check every module from API result. We should collect result for each module listed on API result and make decision depends on version after.
         */
        $list = $theme ? VulnerabilityAlarm::$themes : VulnerabilityAlarm::$plugins;
        foreach ( $list as $item ) {
            if ( $item->slug === $item_slug ) {
                $results[] = self::checkModule($item, $item_version, $check_is_safe);
            }
        }

        if ( ! empty($results) ) {
            // Get only one result - sorting is here
            $filtered_results = array_filter($results, function ($element) {
                return $element instanceof ItemReport;
            });
            if ( ! empty($filtered_results) ) {
                usort($filtered_results, function ($a, $b) {
                    return $a->id - $b->id;
                });

                return $filtered_results[0];
            }
        }

        return false;
    }

    /**
     * Check if a plugin is vulnerable based on its version comparing with plugins report.
     * @param ItemReport $module_report
     * @param string $plugin_version Installed plugin version to check
     *
     * @return bool
     */
    public static function isModuleVersionIsVulnerable($module_report, $plugin_version)
    {
        // If the plugin record is not vulnerability record - skip
        if ( $module_report->app_status !== 'vulnerable' ) {
            return false;
        }

        // If min rs_app_version_max not provided
        if ( empty($module_report->rs_app_version_max) ) {
            return true;
        }

        // if no rs_app_version_min provided, makes module
        // think that all the version below rs_app_version_max is vulnerable
        if ( empty($module_report->rs_app_version_min) ) {
            $module_report->rs_app_version_min = '0.0';
        }

        // If min rs_app_version_max provided - check this
        if (
            version_compare($module_report->rs_app_version_min, $plugin_version, '<=') &&
            version_compare($module_report->rs_app_version_max, $plugin_version, '>')
        ) {
            return true;
        }

        return false;
    }

    /**
     * @param $plugin
     * @param $plugin_version
     * @return bool
     */
    private static function isModuleVersionIsSafe($plugin, $plugin_version)
    {
        // If the plugin record is not safe record - skip
        if ( !in_array($plugin->app_status, array('safe', 'safe_psc'))) {
            return false;
        }

        if ( empty($plugin->rs_app_version_min) || empty($plugin->rs_app_version_max) ) {
            return false;
        }

        // If min rs_app_version_min provided - check this
        if ( version_compare($plugin_version, $plugin->rs_app_version_min, '>=')
            &&
            version_compare($plugin_version, $plugin->rs_app_version_max, '<=')
        ) {
            return true;
        }

        return false;
    }

    /**
     * @param $theme_slug
     * @param $theme_version
     * @return ItemReport|false
     */
    public static function getThemeReportStatic($theme_slug, $theme_version)
    {
        return static::getItemReportStatic($theme_slug, $theme_version, false, true);
    }

    /**
     * @param array $theme_slug
     *
     * @return array
     */
    public static function getSafeThemesViaAPI($theme_slug)
    {
        try {
            $report = self::getReport(['themes' => $theme_slug]);
        } catch (\Exception $_exception) {
            return $theme_slug;
        }

        $list_safe_themes = array();
        if (count($report->themes) > 0) {
            foreach ($report->themes as $theme_name) {
                if ($theme_name->app_status == 'safe') {
                    array_push($list_vlnerability_themes, $theme_name->slug);
                }
            }
            return $list_safe_themes;
        }
        return [];
    }

    /**
     * Gathering info about installed modules
     *
     * @return array
     */
    public static function getModules()
    {
        $modules = [
            'themes' => [],
            'plugins' => []
        ];

        require_once(ABSPATH . 'wp-admin/includes/plugin.php');
        require_once(ABSPATH . 'wp-admin/includes/theme.php');

        $plugins = get_plugins();
        $themes = wp_get_themes();

        foreach ($themes as $theme) {
            $modules['themes'][] = [
                $theme->get_stylesheet(),
                $theme->get('Version')
            ];
        }

        foreach ($plugins as $plugin_file => $plugin) {
            $plugin_dirname = dirname(plugin_basename($plugin_file));
            if ( '.' !== $plugin_dirname && strpos($plugin_dirname, '/') === false ) {
                $plugin_slug = sanitize_title($plugin_dirname);
            } else {
                $plugin_slug = sanitize_title($plugin['Name']);
            }
            $modules['plugins'][] = [
                $plugin_slug,
                $plugin['Version']
            ];
        }

        return $modules;
    }

    /**
     * Get info about vulnerabilities from cloud
     *
     * @param $modules array
     *
     * @return ApiResults
     *
     * @throws VulnerabilityAlarmServiceException
     */
    public static function getReport($modules)
    {
        if ( ! is_array($modules) ) {
            throw new VulnerabilityAlarmServiceException('Modules not provided.');
        }

        $modules_names = [];
        foreach ( $modules as $module_types ) {
            if ( is_array($module_types) ) {
                foreach ( $module_types as $module ) {
                    if ( isset($module[0]) ) {
                        $modules_names[] = $module[0];
                    }
                }
            }
        }

        if ( empty($modules_names) ) {
            throw new VulnerabilityAlarmServiceException('Modules names not found.');
        }
        $api_res = ResearchApi::method__research_list(json_encode($modules_names));
        if ( isset($api_res['error']) ) {
            throw new VulnerabilityAlarmServiceException('API error: ' . $api_res['error']);
        }

        // Convert API request to the usable format
        $results = static::mapApiResults($api_res);

        return $results;
    }

    /**
     * Save vulnerability info
     */
    private static function writeReport($report)
    {
        update_option(VulnerabilityAlarm::VULNERABILITY_LIST, $report);
    }

    /**
     * @param $api_res
     *
     * @return ApiResults
     */
    private static function mapApiResults($api_res)
    {
        $results = new ApiResults();
        foreach ( $api_res as $res ) {
            // Skip apps without versions
            if ( is_null($res['rs_app_version_min']) && is_null($res['rs_app_version_max']) ) {
                continue;
            }
            if ( isset($res['app_type'], $res['app_name'], $res['app_status']) ) {
                switch ( $res['app_type'] ) {
                    case 'plugin':
                    default:
                        $report = new PluginReport();
                        break;
                    case 'theme':
                        $report = new ThemeReport();
                        break;
                }

                $report->slug = $res['slug'];
                $report->app_status = $res['app_status'];
                if ( isset($res['id']) ) {
                    $report->id = $res['id'];
                }
                if ( isset($res['CVE']) ) {
                    $report->CVE = $res['CVE'];
                }
                if ( isset($res['date']) ) {
                    $report->date = $res['date'];
                }
                if ( isset($res['rs_app_version_min']) ) {
                    $report->rs_app_version_min = $res['rs_app_version_min'];
                }
                if ( isset($res['rs_app_version_max']) ) {
                    $report->rs_app_version_max = $res['rs_app_version_max'];
                }
                if ( isset($res['psc']) ) {
                    $report->psc = $res['psc'];
                }
                if ( isset($res['research_url']) ) {
                    $report->research_url = $res['research_url'];
                }

                // generates `$results->plugins` or `$results->themes`
                $res_type = $res['app_type'] . 's';
                $results->{$res_type}[] = $report;
            }
        }
        return $results;
    }

    public static function sendReport()
    {
        global $spbc;

        if ( $spbc->settings['spbc_key'] === '' ) {
            throw new \Exception(__('Access key is empty.', 'security-malware-firewall'));
        }

        if ( $spbc->data['service_id'] === '' ) {
            throw new \Exception(__('Service ID is empty. Please, do cloud synchronization.', 'security-malware-firewall'));
        }

        $vulnerabilities = [];

        // 1) Collect vulnerable plugins research IDs
        if (
            isset($spbc->scan_plugins_info['names_vulnerable_plugins']) &&
            is_array($spbc->scan_plugins_info['names_vulnerable_plugins']) &&
            count($spbc->scan_plugins_info['names_vulnerable_plugins']) > 0
        ) {
            /** @var \CleantalkSP\SpbctWP\VulnerabilityAlarm\Dto\PluginReport $plugin_report */
            foreach ( $spbc->scan_plugins_info['names_vulnerable_plugins'] as $plugin_report ) {
                $vulnerabilities[] = $plugin_report->id;
            }
        }

        // 2) Collect vulnerable themes research IDs
        if (
            isset($spbc->scan_themes_info['names_vulnerable_themes']) &&
            is_array($spbc->scan_themes_info['names_vulnerable_themes']) &&
            count($spbc->scan_themes_info['names_vulnerable_themes']) > 0
        ) {
            /** @var \CleantalkSP\SpbctWP\VulnerabilityAlarm\Dto\ThemeReport $theme_report */
            foreach ( $spbc->scan_themes_info['names_vulnerable_themes'] as $theme_report ) {
                $vulnerabilities[] = $theme_report->id;
            }
        }

        // 3) Send found vulnerable IDs
        if ( count($vulnerabilities) > 0 ) {
            $_send_result = API::methodReportVulnerabilityInUse($spbc->settings['spbc_key'], $spbc->data['service_id'], $vulnerabilities);
        }
    }
}
