<?php

use CleantalkSP\SpbctWP\CleantalkSettingsTemplates;
use CleantalkSP\SpbctWP\Cron;
use CleantalkSP\SpbctWP\Transaction;

if (!defined('WP_CLI')) {
    return;
}

require_once(SPBC_PLUGIN_DIR . 'inc/spbc-settings.php');

/**
 * @psalm-suppress UndefinedClass
 */
WP_CLI::add_command('spbc', SpbctCli::class, []);

/**
 * Performs custom operations on my plugin items. Read the doc on https://cleantalk.org/help/security-wp-cli to know more. Also you can request more supported commands here https://wordpress.org/support/plugin/security-malware-firewall/.
 *
 * @psalm-suppress UnusedClass
 * @psalm-suppress UndefinedClass
 */
class SpbctCli extends WP_CLI_Command // phpcs:ignore PSR1.Classes.ClassDeclaration.MissingNamespace
{
    public $method = 'POST';
    public $url = 'https://api.cleantalk.org';

    /**
     * Add service. The command will run automatic service creation, then run synchronization with the cloud data, including account details, Firewall rules, scanner signatures e.t.c. Provide [--token] and [--domain] args.
     * https://cleantalk.org/help/security-wp-cli
     *
     * @param mixed $args legacy support
     * @param array $params CLI params
     *
     * @psalm-suppress PossiblyUnusedMethod
     * @throws WP_CLI\ExitException
     */
    public function create($args, $params)
    {
        global $spbc;

        self::prompt(__('Service creation start..', 'security_malware_firewall'));

        delete_option('spbc_activation__redirect');
        self::prompt(__('Redirect after activation disabled..', 'security_malware_firewall'));

        $data = [];

        if (!isset($params['token'])) {
            self::prompt(__('User token is required! <--token=your_user_token> Exit..', 'security_malware_firewall'));
            return;
        }
        $data['user_token'] = $params['token'];

        if (!isset($params['email'])) {
            $admin_email = spbc_get_admin_email();
            /**
             * Filters the email to get Access key
             *
             * @param string $admin_email email to get Access key
             */
            $data['email'] = apply_filters('apbct_get_api_key_email', $admin_email);
            self::prompt(__('The email is not specified, the administrator\'s email will be used: ', 'security_malware_firewall') . $admin_email);
        } else {
            $data['email'] = $params['email'];
        }

        if (!isset($params['domain'])) {
            $data['website'] = parse_url(get_option('home'), PHP_URL_HOST) . parse_url(get_option('home'), PHP_URL_PATH);
            self::prompt(__('The domain is not specified, the current domain will be used: ', 'security_malware_firewall') . $data['website']);
        } else {
            $data['website'] = $params['domain'];
        }

        $data['platform'] = 'wordpress';
        $data['product_name'] = 'security';
        $data['method_name'] = 'get_api_key';
        $data['timezone'] = (string)get_option('gmt_offset');

        self::prompt(__('Trying get api key via WP_CLI utils..', 'security_malware_firewall'));

        $result = WP_CLI\Utils\http_request($this->method, $this->url, $data, [], ['insecure' => true]);
        if (!isset($result->body)) {
            self::prompt(__("HTTP error occurred, exit..", 'security_malware_firewall'));
            return;
        }

        $result = json_decode($result->body, true);
        if (!empty($result['error']) || !empty($result['error_message'])) {
            self::prompt(__("API error:", 'security_malware_firewall'));
            $error = isset($result['error_message']) ? esc_html($result['error_message']) : esc_html($result['error']);
            self::prompt($error, true);
            return;
        } elseif (!isset($result['data'])) {
            self::prompt(__("Error. Probably, automatic key getting is disabled in the CleanTalk dashboard settings. Please, get the Access Key from CleanTalk Control Panel. Exit..", 'security_malware_firewall'));
            return;
        }

        if ( isset($result['data']['user_token']) ) {
            $spbc->data['user_token'] = $result['data']['user_token'];
            self::prompt(__('User token installed.', 'security_malware_firewall'));
        }

        if ( !empty($result['data']['auth_key']) && spbc_api_key__is_correct($result['data']['auth_key'])) {
            $new_key = trim($result['data']['auth_key']);
            $spbc->data['key_changed'] = $new_key !== $spbc->settings['spbc_key'];
            $spbc->settings['spbc_key'] = $new_key;
            $spbc->api_key = $new_key;
            self::prompt(__('Api key installed: ', 'security_malware_firewall') . $spbc->settings['spbc_key']);
        }

        $spbc->save('settings');
        $spbc->save('data');

        self::prompt(__('Running synchronization process and SFW update init..', 'security_malware_firewall'));

        spbc_sync(true);
        if ( $spbc->isHaveErrors() ) {
            self::prompt(__("Error occurred while syncing: ", 'security_malware_firewall'));
            self::prompt($spbc->errors, true);
        } else {
            self::prompt(__("Synchronization success.\n", 'security_malware_firewall'));
        }
        self::prompt(__('Service created successful.', 'security_malware_firewall'));
    }

    /**
     * Manage malware scanner. Provide an arg [start|clear_results|update_signatures] to proceed.
     *
     * @param array $args
     * @param array $_params
     *
     * @return void
     * @throws WP_CLI\ExitException
     */
    public function malware_scanner($args, $_params) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
    {
        $commands_map = array(
            'start' => function () {
                $this->scannerRunBackgroundScan();
            },
            'clear_results' => function () {
                $this->scannerClearResults();
            },
            'update_signatures' => function () {
                $this->scannerUpdateSignatures();
            },
        );

        $allowed_commands = array_keys($commands_map);

        self::prompt(__('Scanner background service start..', 'security_malware_firewall'));

        if (count($args) !== 1) {
            self::prompt(__('Error. Commands count invalid. Use exactly one command.', 'security_malware_firewall'));
            self::prompt($allowed_commands, 1);
            return;
        }

        $command = isset($args[0]) ? $args[0] : null;
        if (!in_array($command, $allowed_commands)) {
            self::prompt(__('Error. Unknown command. Use one of the following:', 'security_malware_firewall'));
            self::prompt($allowed_commands, 1);
            return;
        }

        //run
        call_user_func($commands_map[$command]);
    }

    /**
     * Set existing settings template. Provide an arg [list|set|reset] to proceed.
     *
     * @param array $args [list|set|reset]
     * @param array $params
     *
     * @psalm-suppress PossiblyUnusedMethod
     *
     * @throws WP_CLI\ExitException
     */
    public function template($args, $params)
    {
        global $spbc;

        self::prompt(__('Template service start..', 'security_malware_firewall'));
        self::prompt(__('Trying to get templates list..', 'security_malware_firewall'));

        $data = [];
        $key = $spbc->settings['spbc_key'];

        if (!$key) {
            self::prompt(__('Error. No api key found. Set up api_key first. Exit..', 'security_malware_firewall'));
            return;
        }

        self::prompt(__('API Key found..', 'security_malware_firewall'));

        $data['auth_key'] = $key;
        $data['method_name'] = 'services_templates_get';
        $data['search[product_id]'] = 4;

        $result = WP_CLI\Utils\http_request($this->method, $this->url, $data, [], ['insecure' => true]);
        if (!isset($result->body)) {
            self::prompt($result, true);
            self::prompt(__('HTTP error occurred. Exit..', 'security_malware_firewall'));
            return;
        }

        self::prompt(__('Templates request success..', 'security_malware_firewall'));

        $result = json_decode($result->body, true);
        if (!isset($result['data'])) {
            self::prompt(json_last_error(), true);
            self::prompt(json_last_error_msg(), true);
            self::prompt(__('JSON parse error occurred. Exit..', 'security_malware_firewall'));
            return;
        }

        self::prompt(__('Results decoded..', 'security_malware_firewall'));

        if (isset($result['error'])) {
            self::prompt(__('API error:', 'security_malware_firewall'));
            $error = isset($result['error_message']) ? esc_html($result['error_message']) : esc_html($result['error']);
            self::prompt($error, true);
            return;
        }

        self::prompt(__('Response is correct..', 'security_malware_firewall'));

        if (in_array('list', $args)) {
            self::prompt(__('Listing mode..', 'security_malware_firewall'));
            if (empty($result['data'])) {
                self::prompt(__('Error. No templates found. Exit..', 'security_malware_firewall'));
                return;
            }
            self::prompt(__('Success! Available templates, format is ID -> NAME:', 'security_malware_firewall'));
            foreach ($result['data'] as $template) {
                $id = isset($template['template_id']) ? $template['template_id'] : 'N/A';
                $name = isset($template['name']) ? $template['name'] : 'N/A';
                self::prompt($id . ' -> ' . $name);
            }
            return;
        }

        if (in_array('set', $args)) {
            if (in_array('reset', $args)) {
                self::prompt(__('Reset mode..', 'security_malware_firewall'));
                $settings_template_service = new CleantalkSettingsTemplates($key);
                $res = $settings_template_service->resetOptions();
                if (!$res) {
                    self::prompt(__('Can\'t reset settings to default. Exit..', 'security_malware_firewall'));
                }
                self::prompt(__('Success! Template was reset to default.', 'security_malware_firewall'));
                return;
            }
            self::prompt(__('Set up mode..', 'security_malware_firewall'));

            if (!isset($params['id'])) {
                self::prompt(__('Error. Please add \<id\> param to choose template. Exit..', 'security_malware_firewall'));
                return;
            }

            $id = null;
            $name = '';
            $set = [];
            foreach ($result['data'] as $key => $template) {
                if (
                    isset($template['template_id'], $template['name'], $template['options_site']) &&
                    $template['template_id'] == $params['id']
                ) {
                    $id = $template['template_id'];
                    $name = $template['name'];
                    $set = json_decode($template['options_site'], true);
                }
            }
            if (is_null($id)) {
                self::prompt(__('Error. Selected ID does not exist. Exit..', 'security_malware_firewall'));
                return;
            }

            require_once('spbc-settings.php');
            $settings_template_service = new CleantalkSettingsTemplates($key);
            $res = $settings_template_service->setOptions($id, $name, $set);
            if (!$res) {
                self::prompt(__('Error occurred during setting a template. Exit..', 'security_malware_firewall'));
            }
            self::prompt(__('Success! Template installed: ', 'security_malware_firewall') . $name);
            return;
        }

        self::prompt(__('No available params found. Use <spbc templates list> or <spbc templates set>', 'security_malware_firewall'));
    }

    /**
     * Set a new settings state. Provide a setting name on state On|Off.
     *
     * @param mixed $args legacy support
     * @param array $input_params CLI params
     *
     * @psalm-suppress PossiblyUnusedMethod
     */
    public function settings($args, $input_params)
    {
        global $spbc;

        self::prompt(__('Settings update start..', 'security_malware_firewall'));

        if ( empty($input_params)) {
            self::prompt(__('No available params found - nothing to do. Exit..', 'security_malware_firewall'));
        }

        $available_params = [
            // 2FA
            '2fa__enabled' => '2fa__enable',
            // Login Page
            'login_page__rename_enabled' => 'login_page_rename__enabled',
            // Scanner
            'scanner__auto_start_enabled' => 'scanner__auto_start',
            'scanner__heuristic_analysis_enabled' => 'scanner__heuristic_analysis',
            'scanner__signature_analysis_enabled' => 'scanner__signature_analysis',
            'scanner__automatic_cure_enabled' => 'scanner__auto_cure',
            'scanner__os_cron_analysis_enabled' => 'scanner__os_cron_analysis',
            'scanner__unknown_files_listing_enabled' => 'scanner__list_unknown',
            // Filesystem watcher
            'scanner__fs_watcher' => 'scanner__fs_watcher',
            //firewall
            'firewall__traffic_control_enabled' => 'traffic_control__enabled',
            'firewall__firewall_enabled' => 'secfw__enabled',
            'firewall__waf_enabled' => 'waf__enabled',
            'firewall__waf_blocker_enabled' => 'waf_blocker__enabled',
            //
            'complete_deactivation_enabled' => 'misc__complete_deactivation',
            //
            'vulnerability_check__cron_enabled' => 'vulnerability_check__enable_cron',
            'vulnerability_check__reports_enabled' => 'vulnerability_check__show_reports',
            'vulnerability_check__testing_before_install_enabled' => 'vulnerability_check__test_before_install',
            'vulnerability_check__warn_on_modules_pages_enabled' => 'vulnerability_check__warn_on_modules_pages',
            //
            'wpsec__xmlrpc_disabled' => 'wp__disable_xmlrpc',
            'wpsec__rest_api_for_non_authenticated_disabled' => 'wp__disable_rest_api_for_non_authenticated',
            //
            'admin_bar__enabled' => 'admin_bar__show',
            'dashboard_widget__enabled' => 'wp__dashboard_widget__show',
        ];

        if (isset($input_params['list'])) {
            self::prompt(__('Current settings:', 'security_malware_firewall'));
            self::prompt(static::listSettings($spbc, $available_params), true);
            return;
        }

        foreach ( $input_params as $key => $value) {
            if ('2fa__enabled' === $key && 'on' === $value) {
                self::prompt(__('Error. Two factor auth can only be disabled via WP CLI. <--2fa__enabled=off>', 'security_malware_firewall'));
                unset($input_params[$key]);
                continue;
            }
            if ('login_page__rename_enabled' === $key && 'on' === $value) {
                self::prompt(__('Error. Renaming login page can only be disabled via WP CLI. <--login_page__rename_enabled=off>', 'security_malware_firewall'));
                unset($input_params[$key]);
                continue;
            }
            if (!in_array($key, array_keys($available_params))) {
                self::prompt(__('Error. Unknown param: ', 'security_malware_firewall') . $key);
                unset($input_params[$key]);
                continue;
            }
            $input_params[$key] = trim($value, ' \'\"');
        }

        if (!empty($input_params)) {
            self::prompt(__('Found valid params:', 'security_malware_firewall'));
            self::prompt($input_params, true);
        } else {
            self::prompt(__('No valid params found. Nothing to do. Exit..', 'security_malware_firewall'));
            return;
        }

        foreach ($available_params as $avail_param => $setting_key) {
            if ( isset($input_params[$avail_param]) ) {
                if ( $input_params[$avail_param] == 'on' ) {
                    self::prompt(__('Set ', 'security_malware_firewall') . $avail_param . __(' to ON', 'security_malware_firewall'));
                    $spbc->settings[$setting_key] = 1;
                } else if ($input_params[$avail_param] == 'off') {
                    self::prompt(__('Set ', 'security_malware_firewall') . $avail_param . __(' to OFF', 'security_malware_firewall'));
                    $spbc->settings[$setting_key] = 0;
                } else {
                    self::prompt(__('Error. Unknown value for setting: ', 'security_malware_firewall') . $avail_param . '->' . $input_params[$avail_param]);
                }
            }
        }

        $spbc->save('settings');
        self::prompt(__('Updated settings state:', 'security_malware_firewall'));
        self::prompt(static::listSettings($spbc, $available_params), true);
    }

    /**
     * @param $spbc
     * @param $available_params
     *
     * @return array
     */
    private static function listSettings($spbc, $available_params)
    {
        $out = [];
        $available_params_flip = array_flip($available_params);
        foreach ($spbc->settings as $key => $value) {
            if (in_array($key, array_keys($available_params_flip))) {
                $value = $value ? 'on' : 'off';
                $out[$available_params_flip[$key]] = $value;
            }
        }
        return $out;
    }

    /**
     * Echo a message. If the message is not string or $pretty flag is set to true, it will be printed as a print_r.
     * @param mixed $msg Value to print
     * @param bool $pretty Flag to force print as a print_r
     *
     * @return void
     */
    private static function prompt($msg, $pretty = false)
    {
        if ($pretty || !is_string($msg)) {
            WP_CLI::print_value($msg);
            return;
        }
        WP_CLI::line($msg);
    }

    /**
     * Clear all the scan results.
     * @return void
     */
    private function scannerClearResults()
    {
        require_once SPBC_PLUGIN_DIR . 'inc/spbc-scanner.php';
        if (!isset($params['force'])) {
            WP_CLI::confirm(
                __(
                    'This action will delete last scan results, the action is not reversible. Are you sure to implement?',
                    'security_malware_firewall'
                )
            );
        }
        self::prompt(__('Clearing scan results..', 'security_malware_firewall'));
        try {
            spbc_scanner_clear(true);
        } catch (\Exception $e) {
            self::prompt(__('Error. ', 'security_malware_firewall') . $e->getMessage(), true);
        }
        WP_CLI::success(__('Scan results cleared.', 'security_malware_firewall'));
    }

    /**
     * Update signatures for Signature Analysis module.
     * @return void
     */
    private function scannerUpdateSignatures()
    {
        global $spbc;
        self::prompt('Updating signatures..');
        $result = spbc_scanner__signatures_update(true);
        if (isset($result['error'])) {
            $error = is_string($result['error']) ? $result['error'] : __('unknown error', 'security_malware_firewall');
            self::prompt(__('Error. Update failed. Reason: ', 'security_malware_firewall') . $error);
            return;
        }
        $final_count = $spbc->data['scanner']['signature_count'];
        if (empty($final_count)) {
            self::prompt(__('Error. Update failed. Zero count of signatures.', 'security_malware_firewall'));
            return;
        }
        WP_CLI::success(__('Signatures updated. Final count: ', 'security_malware_firewall') . $final_count);
    }

    /**
     * Run background scan within 30 seconds.
     * @return void
     */
    private function scannerRunBackgroundScan()
    {
        self::prompt('Starting schedule background scan..');

        Transaction::get('background_scanner')->clearTransactionTimer();
        Cron::removeTask('background_scan');
        \CleantalkSP\SpbctWP\Scanner\ScannerQueue::launchBackground();

        WP_CLI::success(__('Background scan will start within 30 seconds. Make note, this could take up to 1 hour to complete the scan process in the background.', 'security_malware_firewall'));
    }
}
