<?php

namespace Cleantalk\ApbctWP\Firewall;

use Cleantalk\Common\Helper;
use Cleantalk\ApbctWP\Variables\Cookie;
use Cleantalk\ApbctWP\Variables\Server;
use Cleantalk\Common\TT;
use Cleantalk\ApbctWP\ApbctJsBundleResolver;

/**
 * Class AntiFlood
 * @package Cleantalk\ApbctWP\Firewall
 *
 * @psalm-suppress PossiblyUnusedProperty
 */
class AntiFlood extends \Cleantalk\Common\Firewall\FirewallModule
{
    public $module_name = 'ANTIFLOOD';

    private $db__table__ac_logs;
    private $db__table__ac_ua_bl;

    private $api_key = '';
    private $view_limit = 20;
    private $apbct = array();
    private $store_interval = 60;
    private $chance_to_clean = 20;

    public $isExcluded = false;

    /**
     * @var string Content of the die page
     */
    private $sfw_die_page;

    /**
     * @var string
     */
    private $server__http_user_agent;

    /**
     * AntiCrawler constructor.
     *
     * @param $log_table
     * @param $ac_logs_table
     * @param array $params
     */
    public function __construct($log_table, $ac_logs_table, $params = array())
    {
        parent::__construct($log_table, $ac_logs_table, $params);

        $this->server__http_user_agent = TT::toString(Server::get('HTTP_USER_AGENT'));

        $this->db__table__logs     = $log_table ?: null;
        $this->db__table__ac_logs  = $ac_logs_table ?: null;
        $this->db__table__ac_ua_bl = defined('APBCT_TBL_AC_UA_BL') ? APBCT_TBL_AC_UA_BL : null;

        foreach ($params as $param_name => $param) {
            $this->$param_name = isset($this->$param_name) ? $param : false;
        }

        $this->isExcluded = $this->checkExclusions();
    }

    /**
     * Use this method to execute main logic of the module.
     * @return array
     */
    public function check()
    {
        global $apbct;

        $results = array();

        if (empty($apbct->key_is_ok) || empty($apbct->api_key)) {
            return $results;
        }

        $this->clearTable();

        $time = time() - $this->store_interval;

        foreach ($this->ip_array as $_ip_origin => $current_ip) {
            // UA check
            $ua_bl_results = $this->db->fetchAll(
                "SELECT * FROM " . $this->db__table__ac_ua_bl . " ORDER BY `ua_status` DESC;"
            );

            if ( ! empty($ua_bl_results)) {
                foreach ($ua_bl_results as $ua_bl_result) {
                    if (
                        ! empty($ua_bl_result['ua_template'])
                        && preg_match(
                            '%' . str_replace(array('"', '%'), array('', '\%'), $ua_bl_result['ua_template']) . '%i',
                            $this->server__http_user_agent
                        )
                        && ! in_array(preg_last_error(), array(PREG_BACKTRACK_LIMIT_ERROR, PREG_RECURSION_LIMIT_ERROR), true)
                    ) {
                        if (TT::getArrayValueAsString($ua_bl_result, 'ua_status')  === '1') {
                            // Whitelisted
                            $results[] = array(
                                'ip'          => $current_ip,
                                'is_personal' => false,
                                'status'      => 'PASS_ANTIFLOOD_UA',
                            );

                            return $results;
                        }
                    }
                }
            }

            // Passed
            if (Cookie::get('apbct_antiflood_passed') === md5($current_ip . $this->api_key)) {
                if ( ! headers_sent()) {
                    Cookie::set('apbct_antiflood_passed', '0', time() - 86400, '/', '', null, true);
                }

                // Do logging an one passed request
                $this->updateLog($current_ip, 'PASS_ANTIFLOOD');

                $results[] = array('ip' => $current_ip, 'is_personal' => false, 'status' => 'PASS_ANTIFLOOD',);

                return $results;
            }


            // @todo Rename ip column to sign. Use IP + UserAgent for it.

            $result = $this->db->fetchAll(
                "SELECT SUM(entries) as total_count"
                . ' FROM `' . $this->db__table__ac_logs . '`'
                . " WHERE ip = '$current_ip' AND interval_start > '$time' AND " . rand(1, 100000) . ";"
            );

            if ( ! empty($result) && isset($result[0]['total_count']) && $result[0]['total_count'] >= $this->view_limit) {
                $results[] = array('ip' => $current_ip, 'is_personal' => false, 'status' => 'DENY_ANTIFLOOD',);
            }
        }

        if ( ! empty($results)) {
            // Do block page
            return $results;
        } else {
            // Do logging entries
            add_action('template_redirect', array(& $this, 'updateAcLog'), 999);
        }

        return $results;
    }

    public function updateAcLog()
    {
        $interval_time = Helper::timeGetIntervalStart($this->store_interval);

        // @todo Rename ip column to sign. Use IP + UserAgent for it.

        foreach ($this->ip_array as $_ip_origin => $current_ip) {
            $id = md5($current_ip . $interval_time);
            $this->db->execute(
                "INSERT INTO " . $this->db__table__ac_logs . " SET
					id = '$id',
					ip = '$current_ip',
					entries = 1,
					interval_start = $interval_time
				ON DUPLICATE KEY UPDATE
					ip = ip,
					entries = entries + 1,
					interval_start = $interval_time;"
            );
        }
    }

    public function clearTable()
    {
        if (rand(0, 100) < $this->chance_to_clean) {
            $interval_start = \Cleantalk\ApbctWP\Helper::timeGetIntervalStart($this->store_interval);
            $this->db->execute(
                'DELETE
				FROM ' . $this->db__table__ac_logs . '
				WHERE interval_start < ' . $interval_start . ' 
				AND ua = "" 
				LIMIT 100000;'
            );
        }
    }

    /**
     * Add entry to SFW log.
     * Writes to database.
     *
     * @param string $ip
     * @param $status
     */
    public function updateLog($ip, $status)
    {
        $id   = md5($ip . $this->module_name);
        $time = time();

        $query = "INSERT INTO " . $this->db__table__logs . "
		SET
			id = '$id',
			ip = '$ip',
			status = '$status',
			all_entries = 1,
			blocked_entries = " . (strpos($status, 'DENY') !== false ? 1 : 0) . ",
			entries_timestamp = '" . $time . "',
			ua_name = %s
		ON DUPLICATE KEY
		UPDATE
			status = '$status',
			all_entries = all_entries + 1,
			blocked_entries = blocked_entries" . (strpos($status, 'DENY') !== false ? ' + 1' : '') . ",
			entries_timestamp = '" . $time . "',
			ua_name = %s";

        $this->db->prepare($query, array(Server::get('HTTP_USER_AGENT'), Server::get('HTTP_USER_AGENT')));
        $this->db->execute($this->db->getQuery());
    }

    public function diePage($result)
    {
        global $apbct;

        // File exists?
        if (file_exists(CLEANTALK_PLUGIN_DIR . 'lib/Cleantalk/ApbctWP/Firewall/die_page_antiflood.html')) {
            $this->sfw_die_page = file_get_contents(
                CLEANTALK_PLUGIN_DIR . 'lib/Cleantalk/ApbctWP/Firewall/die_page_antiflood.html'
            );

            $js_url = ApbctJsBundleResolver::getFullScriptURI($apbct->settings);

            $net_count = $apbct->stats['sfw']['entries'];

            // Custom Logo
            $custom_logo_img = '';
            $custom_logo_id = isset($apbct->settings['cleantalk_custom_logo']) ? $apbct->settings['cleantalk_custom_logo'] : false;

            if ($custom_logo_id && ($image_attributes = wp_get_attachment_image_src($custom_logo_id, array(150, 150)))) {
                $custom_logo_img = '<img src="' . esc_url(TT::getArrayValueAsString($image_attributes, 0)) . '" width="150" alt="" />';
            }

            // Translation
            $replaces = array(
                '{SFW_DIE_NOTICE_IP}'              => __(
                    'Anti-Flood is activated for your IP',
                    'cleantalk-spam-protect'
                ),
                '{SFW_DIE_MAKE_SURE_JS_ENABLED}'   => __(
                    'To continue working with the web site, please make sure that you have enabled JavaScript.',
                    'cleantalk-spam-protect'
                ),
                '{SFW_DIE_YOU_WILL_BE_REDIRECTED}' => sprintf(
                    __(
                        'You will be automatically redirected to the requested page after %d seconds.',
                        'cleantalk-spam-protect'
                    ),
                    30
                ),
                '{CLEANTALK_TITLE}'                => esc_html($apbct->data['wl_brandname']),
                '{CLEANTALK_URL}'                  => esc_url($apbct->data['wl_url']),
                '{REMOTE_ADDRESS}'                 => esc_html($result['ip']),
                '{REQUEST_URI}'                    => esc_html(Server::getString('REQUEST_URI')),
                '{SERVICE_ID}'                     => esc_html($this->apbct->data['service_id']) . ', ' . esc_html($net_count),
                '{HOST}'                           => get_home_url() . ', ' . APBCT_VERSION,
                '{GENERATED}'                      => '<p>The page was generated at&nbsp;' . date('D, d M Y H:i:s') . "</p>",
                '{COOKIE_ANTIFLOOD_PASSED}'        => md5($result['ip'] . $this->api_key),
                '{SCRIPT_URL}'                     => esc_url($js_url),

                // Custom Logo
                '{CUSTOM_LOGO}'                    => $custom_logo_img
            );

            foreach ($replaces as $place_holder => $replace) {
                $this->sfw_die_page = str_replace($place_holder, TT::toString($replace), $this->sfw_die_page);
            }

            add_action('init', array($this, 'printDiePage'));
        }
    }

    public function printDiePage()
    {
        global $apbct;

        parent::diePage('');

        $localize_js = array(
            '_ajax_nonce'                          => $apbct->ajax_service->getPublicNonce(),
            '_rest_nonce'                          => wp_create_nonce('wp_rest'),
            '_ajax_url'                            => admin_url('admin-ajax.php', 'relative'),
            '_rest_url'                            => esc_url(get_rest_url()),
            'data__cookies_type'                   => $apbct->data['cookies_type'],
            'data__ajax_type'                      => $apbct->data['ajax_type'],
            'sfw__random_get'                      => $apbct->settings['sfw__random_get'] === '1' ||
                                                      ($apbct->settings['sfw__random_get'] === '-1' && apbct_is_cache_plugins_exists()),
            'cookiePrefix'                         => apbct__get_cookie_prefix(),
        );

        $localize_js_public = array(
            'pixel__setting'                => $apbct->settings['data__pixel'],
            'pixel__enabled'                => $apbct->settings['data__pixel'] === '2' ||
                                               ($apbct->settings['data__pixel'] === '3' && apbct_is_cache_plugins_exists()),
            'pixel__url'                    => $apbct->pixel_url,
            'data__email_check_before_post' => $apbct->settings['data__email_check_before_post'],
            'data__cookies_type'            => $apbct->data['cookies_type'],
            'data__visible_fields_required' => ! apbct_is_user_logged_in() || $apbct->settings['data__protect_logged_in'] == 1,
            'bot_detector_enabled' => apbct__is_bot_detector_enabled(),
        );

        $replaces = array(
            '{LOCALIZE_SCRIPT}'   => 'var ctPublicFunctions = ' . json_encode($localize_js) . ';' .
                                     'var ctPublic = ' . json_encode($localize_js_public) . ';',
        );

        foreach ($replaces as $place_holder => $replace) {
            $this->sfw_die_page = str_replace($place_holder, $replace, $this->sfw_die_page);
        }

        if ( ! headers_sent() ) {
            http_response_code(403);
        }

        // File exists?
        if (file_exists(CLEANTALK_PLUGIN_DIR . "lib/Cleantalk/ApbctWP/Firewall/die_page_sfw.html")) {
            die($this->sfw_die_page);
        }

        die("IP BLACKLISTED. Blocked by AntiFlood " . $this->apbct->stats['last_sfw_block']['ip']);
    }

    private function checkExclusions()
    {
        $allowed_roles = array('administrator', 'editor');
        $user          = apbct_wp_get_current_user();

        if ( ! $user) {
            return false;
        }

        foreach ($allowed_roles as $role) {
            if (in_array($role, (array)$user->roles)) {
                return true;
            }
        }

        return false;
    }
}
