<?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::getModulesForCloud();
        $report = self::getCloudReport($modules);
        self::writeReport($report);
        self::setSummaryLogPluginsAndThemes($modules);
    }

    /**
     * @return void
     */
    public static function dropVulnerabilitiesLibrary()
    {
        $modules = self::getModulesForCloud();
        $report = new ApiResults();
        self::writeReport($report);
        self::setSummaryLogPluginsAndThemes($modules);
    }

    /**
     * @param array $modules
     * @ToDo is this locic needed?
     * @psalm-suppress PossiblyUnusedMethod
     */
    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;
            $vulnerable_themes_reports = array();

            foreach ($modules['themes'] as $theme) {
                if ( isset($theme['slug'], $theme['Version']) ) {
                    $theme_slug = $theme['slug'];
                    $theme_version = $theme['Version'];
                    $theme_is_vulnerable = VulnerabilityAlarm::checkthemeVulnerabilityStatic($theme_slug, $theme_version);
                    $themes_info_requested++;
                    if ($theme_is_vulnerable instanceof ThemeReport) {
                        $vulnerable_themes_reports[] = $theme_is_vulnerable;
                        $number_of_vulnerable_themes++;
                    }
                }
            }

            $spbc->scan_themes_info['vulnerable_themes_reports'] = $vulnerable_themes_reports;
            $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;
            $vulnerable_plugins_reports = array();

            foreach ($modules['plugins'] as $plugin) {
                if ( isset($plugin['slug'], $plugin['Version']) ) {
                    $plugin_slug = $plugin['slug'];
                    $plugin_version = $plugin['Version'];
                    $plugin_is_vulnerable = VulnerabilityAlarm::checkPluginVulnerabilityStatic($plugin_slug, $plugin_version);
                    $plugins_info_requested++;
                    if ($plugin_is_vulnerable instanceof PluginReport) {
                        $vulnerable_plugins_reports[] = $plugin_is_vulnerable;
                        $number_of_vulnerable_plugins++;
                    }
                }
            }

            $spbc->scan_plugins_info['vulnerable_plugins_reports'] = $vulnerable_plugins_reports;
            $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');
        }
    }

    private static function checkModule($module_report, $safety_check = false)
    {
        if ( $safety_check ) {
            if ( in_array($module_report->app_status, array('safe', 'safe_psc')) ) {
                return $module_report;
            }
        } elseif ( $module_report->app_status === 'vulnerable' ) {
            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. @deprecated
     * @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);
    }

    public static function getItemReportStatic($item_slug, $wp_plugin_version, $check_is_safe = false, $theme = false)
    {
        $results = array();
        /**
         * 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;
        /** @var ItemReport $item */
        foreach ( $list as $item ) {
            if (
                $item->slug === $item_slug &&
                self::isModuleVersionMatchesReport($item, $wp_plugin_version, $check_is_safe)
            ) {
                $results[] = self::checkModule($item, $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 end($filtered_results);
            }
        }

        return false;
    }

    /**
     * Compare plugin version against report bounds using dev-branch operators.
     *
     * @param ItemReport $module_report
     * @param string $plugin_version
     * @param bool $check_is_safe
     *
     * @return bool
     */
    public static function isModuleVersionMatchesReport($module_report, $plugin_version, $check_is_safe = false)
    {
        if (empty($module_report->rs_app_version_max)) {
            return false;
        }

        $default_operator = $check_is_safe ? '=' : '<';
        $operator = ! empty($module_report->rs_app_version_max_operation)
            ? $module_report->rs_app_version_max_operation
            : $default_operator;

        return (bool) version_compare($plugin_version, $module_report->rs_app_version_max, $operator);
    }

    /**
     * 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 $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::getCloudReport(['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
     *
     * @psalm-suppress PossiblyUnusedMethod
     */
    public static function getModulesForCloud()
    {
        $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_key => $theme) {
            $modules['themes'][$theme_key] = [
                'slug' => $theme_key,
                'Name' => $theme->get('Name'),
                'Version' => $theme->get('Version')
            ];
        }

        foreach ($plugins as $plugin_file => $plugin) {
            $modules['plugins'][$plugin_file] = [
                'slug' => VulnerabilityAlarm::getPluginSlug($plugin, $plugin_file),
                'PluginURI' => $plugin['PluginURI'],
                'Version' => $plugin['Version'],
                'Name' => $plugin['Name']
            ];
        }

        return $modules;
    }

    /**
     * Get info about vulnerabilities from cloud
     *
     * @param $modules array
     *
     * @return ApiResults
     *
     * @throws VulnerabilityAlarmServiceException
     *
     * @psalm-suppress PossiblyUnusedMethod
     */
    public static function getCloudReport($modules)
    {
        global $spbc;

        $api_key = $spbc->settings['spbc_key'];

        if ( ! is_array($modules) ) {
            throw new VulnerabilityAlarmServiceException('Modules not provided.');
        }

        if (empty($api_key)) {
            throw new VulnerabilityAlarmServiceException('Access key is empty.');
        }

        $plugins = isset($modules['plugins']) ? $modules['plugins'] : [];
        $themes = isset($modules['themes']) ? $modules['themes'] : [];

        $cloud_verdicts = ResearchApi::methodAnalyzeSiteWpVulnerabilities($api_key, $plugins, $themes);

        if ( ! empty($cloud_verdicts['error_message']) ) {
            throw new VulnerabilityAlarmServiceException('Modules checking API error: ' . $cloud_verdicts['error_message']);
        }

        if ( ! isset($cloud_verdicts['vulnerabilities']) ) {
            throw new VulnerabilityAlarmServiceException('Modules checking empty results.');
        }

        // Convert API request to the usable format
        $results = static::mapApiResults($cloud_verdicts['vulnerabilities']);

        return $results;
    }

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

    /**
     * @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['rs_app_version_max_operation']) ) {
                    $report->rs_app_version_max_operation = $res['rs_app_version_max_operation'];
                }
                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['vulnerable_plugins_reports']) &&
            is_array($spbc->scan_plugins_info['vulnerable_plugins_reports']) &&
            count($spbc->scan_plugins_info['vulnerable_plugins_reports']) > 0
        ) {
            /** @var \CleantalkSP\SpbctWP\VulnerabilityAlarm\Dto\PluginReport $plugin_report */
            foreach ( $spbc->scan_plugins_info['vulnerable_plugins_reports'] as $plugin_report ) {
                $vulnerabilities[] = $plugin_report->id;
            }
        }

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

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