<?php

namespace CleantalkSP\SpbctWP\Scanner;

use CleantalkSP\SpbctWP\Helpers\CSV;
use CleantalkSP\SpbctWP\Helpers\HTTP;
use CleantalkSP\SpbctWP\Scanner\ScannerActions\LinksActions;

class Helper
{
    /**
     * Getting real hashs of CMS core files
     *
     * @param string $cms CMS name
     * @param string $version CMS version
     *
     * @return array Array with all CMS files hashes or Error Array
     */
    public static function getHashesForCMS($cms, $version)
    {
        $file_path = 'https://cleantalk-security.s3.amazonaws.com/cms_checksums/' . $cms . '/' . $version . '/' . $cms . '_' . $version . '.json.gz';

        if ( HTTP::getResponseCode($file_path) === 200 ) {
            $gz_data = HTTP::getContentFromURL($file_path, false);

            if ( empty($gz_data['error']) ) {
                if ( function_exists('gzdecode') ) {
                    $data = gzdecode($gz_data);

                    if ( $data !== false ) {
                        $result = json_decode($data, true);
                        $result = $result['data'];

                        if ( count($result['checksums']) === (int)$result['checksums_count'] ) {
                            return $result;
                        }

                        return array('error' => 'FILE_DOESNT_MATHCES');
                    }

                    return array('error' => 'COULDNT_UNPACK');
                }

                return array('error' => 'Required function gzdecode() is not available. Please enable the zlib extension (or upgrade PHP).');
            }

            return (array) $gz_data;
        }

        return array('error' => 'Remote file not found or WordPress version is not supported. Yo could try again later (few hours). Contact tech support if it repeats.');
    }

    /**
     * Getting real hashs of plugin's or theme's files
     *
     * @param string $cms CMS name
     * @param string $type Plugin type (plugin|theme)
     * @param string $plugin Plugin name
     * @param string $version Plugin version
     *
     * @return array Array with all CMS files hashes or Error Array
     */
    public static function getHashesForModules($cms, $type, $plugin, $version)
    {
        $file_path = 'https://cleantalk-security.s3.amazonaws.com/extensions_checksums/' . $cms . '/' . $type . 's/' . $plugin . '/' . $version . '.csv.gz';

        if ( HTTP::getResponseCode($file_path) === 200 ) {
            $gz_data = HTTP::getContentFromURL($file_path, false);

            if ( empty($gz_data['error']) ) {
                if ( function_exists('gzdecode') ) {
                    $data = gzdecode($gz_data);

                    if ( $data !== false ) {
                        $lines = CSV::parseCSV($data);

                        if ( count($lines) > 0 ) {
                            $result = array();

                            $code_extensions   = array('php', 'html', 'js');
                            $binary_extensions = array('exe', 'dll', 'so', 'dylib');

                            foreach ( $lines as $hash_info ) {
                                if ( empty($hash_info) || empty($hash_info[0]) || empty($hash_info[1]) ) {
                                    continue;
                                }

                                preg_match('/.*\.(\S*)$/', $hash_info[0], $matches);
                                $ext = isset($matches[1]) ? $matches[1] : '';

                                // Code files + binaries (no extension or known binary ext).
                                // Skip assets (css, jpg, json, …) to avoid bloating the DB.
                                if (
                                    in_array($ext, $code_extensions, true) ||
                                    $ext === '' ||
                                    in_array($ext, $binary_extensions, true)
                                ) {
                                    $result[] = $hash_info;
                                }
                            }

                            if ( count($result) ) {
                                return $result;
                            }

                            return array('error' => 'BAD_HASHES_FILE__PLUG');
                        }

                        return array('error' => 'Empty hashes file');
                    }

                    return array('error' => 'COULDNT_UNPACK');
                }

                return array('error' => 'Function gzdecode not exists. Please update your PHP to version 5.4');
            }

            return (array) $gz_data;
        }

        return array('error' => 'REMOTE_FILE_NOT_FOUND_OR_VERSION_IS_NOT_SUPPORTED__PLUG');
    }

    /**
     * Getting real hashs of approved files
     *
     * @param string $cms CMS name
     * @param string $type Type - approved/rejected
     * @param string $version
     *
     * @return false|string[]|non-empty-array<array-key, string> Array with all files hashes or Error Array
     */
    public static function getHashesForFiles($cms, $type, $version)
    {
        $file_path = 'https://cleantalk-security.s3-us-west-2.amazonaws.com/extensions_checksums/' . $cms . '/' . $type . '/' . $version . '.csv.gz';
        if ( HTTP::getResponseCode($file_path) !== 200 ) {
            return array('error' => 'REMOTE_FILE_NOT_FOUND');
        }

        $gz_data = HTTP::getContentFromURL($file_path, false);
        if ( !empty($gz_data['error']) ) {
            return (array) $gz_data;
        }

        if ( !function_exists('gzdecode') ) {
            return array('error' => 'Function gzdecode not exists. Please update your PHP to version 5.4');
        }

        $data = gzdecode($gz_data);
        if ( $data === false ) {
            return array('error' => 'COULDNT_UNPACK');
        }

        /** @psalm-suppress UndefinedMethod */
        $lines = CSV::parseCSVLite($data);
        if ( count($lines) < 1 ) {
            return array('error' => 'Empty hashes file');
        }

        return $lines;
    }

    /**
     * Getting real hashes of CMS language (translation) files for all available translations
     *
     * @param string $cms CMS name
     *
     * @return array Array with language file names as keys and their hashes as values, or Error Array
     */
    public static function getHashesForLanguagesFiles($cms)
    {
        global $wp_version;

        $supported_locales = array(
            'es_ES', 'is_IS', 'fr_FR', 'it_IT', 'zh_CN', 'hi_IN', 'ar', 'de_DE',
            'bn_BD', 'ru_RU', 'pt_PT', 'id_ID', 'ja', 'en_GB', 'tr_TR', 'ko_KR',
            'vi', 'fi', 'pl_PL', 'nl_NL', 'cs_CZ', 'da_DK', 'sk_SK', 'sv_SE',
        );

        $installed_locales = get_available_languages();
        $locales = array_intersect($supported_locales, $installed_locales);
        if ( empty($locales) ) {
            return array('error' => 'NO_AVAILABLE_TRANSLATIONS');
        }

        $result = array();

        foreach ( $locales as $locale ) {
            $file_path = 'https://cleantalk-security.s3.us-west-2.amazonaws.com/extensions_checksums/' . $cms . '/languages/' . $locale . '/' . $wp_version . '.csv.gz';

            if ( HTTP::getResponseCode($file_path) !== 200 ) {
                continue;
            }

            $gz_data = HTTP::getContentFromURL($file_path, false);
            if ( !empty($gz_data['error']) ) {
                continue;
            }

            if ( !function_exists('gzdecode') ) {
                return array('error' => 'Function gzdecode not exists. Please update your PHP to version 5.4');
            }

            $data = gzdecode($gz_data);
            if ( $data === false ) {
                continue;
            }

            $lines = CSV::parseCSV($data);
            if ( count($lines) < 1 ) {
                continue;
            }

            foreach ( $lines as $hash_info ) {
                if ( empty($hash_info[0]) || empty($hash_info[1]) ) {
                    continue;
                }

                $result[$hash_info[0]] = $hash_info[1];
            }
        }

        if ( count($result) < 1 ) {
            return array('error' => 'Empty hashes file');
        }

        return $result;
    }

    /**
     * Get original file's content
     *
     * @param array $file_info Array with files data (path, real_full_hash, source_type, source), other is optional
     *
     * @return string|array
     */
    public static function getOriginalFile($file_info)
    {
        $file_info['path'] = str_replace(
            '\\',
            '/',
            $file_info['path']
        ); // Replacing win slashes to Orthodox slashes - in case of Windows

        switch ( $file_info['source_type'] ) {
            case 'PLUGIN':
                $file_info['path'] = preg_replace('@/wp-content/plugins/.*?/(.*)$@i', '$1', $file_info['path']);
                $url_path          = 'https://plugins.svn.wordpress.org/' . $file_info['source'] . '/tags/' . $file_info['version'] . '/' . $file_info['path'];
                break;
            case 'THEME':
                $file_info['path'] = preg_replace('@/wp-content/themes/.*?/(.*)$@i', '$1', $file_info['path']);
                $url_path          = 'https://themes.svn.wordpress.org/' . $file_info['source'] . '/' . $file_info['version'] . '/' . $file_info['path'];
                break;
            default:
                $url_path = 'http://cleantalk-security.s3.amazonaws.com/cms_sources/' . $file_info['source'] . '/' . $file_info['version'] . $file_info['path'];
                break;
        }

        $page_content = HTTP::getContentFromURL($url_path);

        /** @psalm-suppress TypeDoesNotContainType */
        return empty($page_content['error'])
            ? $page_content
            : array('error' => "Couldn't get an original file: " . $page_content['error']);
    }

    public static function clearScannerResults()
    {
        global $spbc;

        $spbc->plugins = array();
        $spbc->save('plugins');

        $spbc->themes = array();
        $spbc->save('themes');

        $spbc->data['scanner'] = array(
            'last_wp_version' => null,
            'cron'            => array(
                'state'         => 'get_hashes',
                'total_scanned' => 0,
                'offset'        => 0,
            ),
        );
        $spbc->save('data');

        $out = [
            'deleted_files_entries'    => Controller::resetCheckResult(),
            'deleted_frontend_entries' => Frontend::resetCheckResult(),
            'deleted_links'            => LinksActions::resetCheckResult(),
        ];
        return $out;
    }

    /**
     * tested
     * Get JSON string of accordion row actions that do not need to be confirmed.
     * @return string
     */
    public static function getRowActionsNeedsNoConfirm()
    {
        global $spbc;
        // by defaults
        $actions = array (
            'defaults' => array(
                'copy_file_info',
                'check_analysis_status',
            ),
            'restricted' => array(),
        );
        // if license is trial
        if ($spbc->data['license_trial']) {
            $actions['restricted'][] = 'delete';
            $actions['restricted'][] = 'delete_quarantine';
            $actions['restricted'][] = 'quarantine';
            $actions['restricted'][] = 'approve';
        }

        $actions['any'] = array_merge($actions['restricted'], $actions['defaults']);

        $actions = json_encode($actions);
        return is_string($actions) ? $actions : '{[]}';
    }

    /**
     * Whether the given path is the site-root WordPress wp-config.php (critical, no auto-cure).
     * Does not match files named wp-config.php in subdirectories (e.g. uploads).
     *
     * @param string $path Absolute or relative file path (as stored in scan table or filesystem)
     * @return bool
     */
    public static function isWpConfigPath($path)
    {
        $normalized = str_replace('\\', '/', (string) $path);

        if (defined('ABSPATH')) {
            $abspath = rtrim(str_replace('\\', '/', ABSPATH), '/');
            if ($abspath !== '' && stripos($normalized, $abspath . '/') === 0) {
                $normalized = substr($normalized, strlen($abspath));
            }
        }

        return ltrim($normalized, '/') === 'wp-config.php';
    }

    /**
     * Message shown when wp-config.php infection is detected (manual cleanup only).
     *
     * @return string
     */
    public static function getWpConfigManualCleanupMessage()
    {
        return __(
            'Infection detected in wp-config.php. Automatic cure is disabled for this critical file — manual cleanup is required. Please contact support or order a malware removal.',
            'security-malware-firewall'
        );
    }

    /**
     * Whether the current user may view wp-config.php contents in the scanner UI.
     * Restricted to Super Admins to reduce credential exposure.
     *
     * @return bool
     */
    public static function canViewWpConfigContents()
    {
        return function_exists('is_super_admin') && is_super_admin();
    }

    /**
     * @return string
     */
    public static function getWpConfigViewRestrictedMessage()
    {
        return __(
            'Only a Super Admin can view wp-config.php contents (credentials protection).',
            'security-malware-firewall'
        );
    }

    /**
     * @return string
     */
    public static function getWpConfigSendRestrictedMessage()
    {
        return __(
            'wp-config.php cannot be sent for analysis because it contains sensitive credentials.',
            'security-malware-firewall'
        );
    }

    /**
     * query
     * Get SQL *WHERE* suffix for SELECT query depends on files category.
     * @param string $category Category of files category which needs to be searched for
     * @return string SQL *WHERE* suffix.
     */
    public static function getSQLWhereAddictionForTableOfCategory($category)
    {
        global $spbc;

        switch ($category) {
            case 'critical':
                $res = ' WHERE status IN ("DENIED_BY_CLOUD", "DENIED_BY_CT")
            OR (
                severity = "CRITICAL"
                AND (
                    status <> "QUARANTINED" AND 
                    status <> "APPROVED_BY_USER" AND 
                    status <> "APPROVED_BY_CT" AND
                    status <> "OK"
                    )
                AND (
                    last_sent IS NULL OR 
                    pscan_status = "DANGEROUS"
                )
            )';
                break;
            case 'suspicious':
                $res = ' WHERE severity <> "CRITICAL" AND
                        last_sent IS NULL AND
                        (status = "MODIFIED" AND source_type IS NOT NULL) 
                        OR (status = "INFECTED" AND severity = "SUSPICIOUS" AND last_sent IS NULL)';
                break;
            case 'approved':
                $res = ' WHERE status = "APPROVED_BY_USER" AND source_type IS NULL';
                break;
            case 'approved_by_cloud':
                $res = ' WHERE ( status = "APPROVED_BY_CT" OR status = "APPROVED_BY_CLOUD") AND source_type IS NULL';
                break;
            case 'analysis_log':
                $res = ' WHERE last_sent IS NOT NULL';
                break;
            case 'unknown':
                $res = ' WHERE status NOT IN ("APPROVED_BY_USER","APPROVED_BY_CT","APPROVED_BY_CLOUD","DENIED_BY_CT","ERROR") AND
						    detected_at >= ' . (time() - $spbc->settings['scanner__list_unknown__older_than'] * 86400) . ' AND
						    (source IS NULL OR source = \'\') AND
						    source_type IS NULL AND
						    size > 0 AND
		                    path NOT LIKE "%wp-content%themes%" AND
                            path NOT LIKE "%wp-content%plugins%" AND
                            path NOT LIKE "%wp-content%cache%" AND
                            path NOT LIKE "%wp-config.php" AND
						    (severity IS NULL OR severity NOT IN ("CRITICAL", "SUSPICIOUS")) AND
						    last_sent IS NULL AND
						    fast_hash NOT IN (SELECT fast_hash FROM ' . SPBC_TBL_CURE_LOG . ' WHERE cure_status = 1)';
                break;
            case 'quarantined':
                $res = ' WHERE status = "QUARANTINED"';
                break;
            case 'frontend_malware':
                $res = ' WHERE approved IS NULL OR approved <> 1';
                break;
            case 'frontend_scan_results_approved':
                $res = ' WHERE approved = 1';
                break;
            case 'skipped':
                $res = ' WHERE status = "ERROR" AND error_msg IS NOT NULL AND error_msg NOT LIKE "%FILE_SIZE_ZERO%"';
                break;
            default:
                $res = '';
        }

        return $res;
    }
}
