<?php

namespace CleantalkSP\SpbctWP\Firewall;

use CleantalkSP\SpbctWP\Helpers\Helper;
use CleantalkSP\Security\Firewall\Result;

class TC extends FirewallModule
{
    public $module_name = 'TC';

    // Table names
    public $data_table = '';
    public $log_table = '';

    // Additional params
    /**
     * @psalm-suppress PossiblyUnusedProperty
     */
    protected $api_key = false;

    /**
     * @psalm-suppress PossiblyUnusedProperty
     */
    protected $set_cookies = false;

    // Default params
    /**
     * How long to keep the record in TC log before it will be randomly cleaned.
     * @var int
     */
    protected $store_interval = 300;
    /**
     * Chance to clean tc log on a hit.
     * @var int
     */
    protected $chance_to_clean = 100;
    /**
     * How many hits should be logged to get a TC block
     * @var int
     */
    protected $tc_limit = 1000;
    /**
     * For how long the visitor will be blocked
     * @var int
     */
    protected $block_period = 3600;

    /**
     * Is user is logged in
     * @var bool
     */
    protected $is_logged_in = false;

    /**
     * Is user is an admin
     * @var false|mixed
     */
    protected $user_is_admin;

    /**
     * Is request is skipped by role rules
     * @var bool
     */
    protected $tc_skipped_by_role = false;

    /**
     * FireWall_module constructor.
     * Use this method to prepare any data for the module working.
     *
     * @param array $params
     */
    public function __construct($params = array())
    {
        $params['block_period'] = $params['block_period'] ?: $this->block_period;

        parent::__construct($params);

        $this->user_is_admin = $params['user_is_admin'] ?: false;
    }

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

        $results = array();

        if ( $this->user_is_admin ) {
            // Role skipping rule. Do not check admins
            $this->tc_skipped_by_role = true;
            return $results;
        }

        if ( $spbc->settings['traffic_control__exclude_authorised_users'] && $this->is_logged_in ) {
            // Role skipping rule. Do not check logged-in users if the option is enabled
            $this->tc_skipped_by_role = true;
            return $results;
        }

        // Clear TC log if previous role rules have not been applied.
        $this->clearTable();

        $time = time();

        foreach ( $this->ip_array as $_ip_origin => $current_ip ) {
            $rand   = rand(1, 100000);
            //convert to long to prevent db mystery
            $md5_ip = md5($current_ip);
            $result = $this->db->fetchAll(
                "SELECT SUM(entries) as total_count"
                . ' FROM `' . $this->log_table . '`'
                . " WHERE
                    md5_ip = '$md5_ip' AND
                    interval_start < '$time' AND
                    $rand;",
                OBJECT
            );
            if ( ! empty($result) && $result[0]->total_count >= $this->tc_limit ) {
                $results[] = new Result(
                    array(
                        'module' => 'TC',
                        'ip'     => $current_ip,
                        'status' => 'DENY_BY_DOS',
                    )
                );
            }
        }

        return $results;
    }

    /**
     * TC module middle action. Update log if visitor not skipped by role rules.
     * @param $result
     * @return void
     * @psalm-suppress PossiblyUnusedMethod
     */
    public function middleAction($result = null)
    {
        //Update log if request is not skipped by role rules
        if ( ! $this->tc_skipped_by_role ) {
            $this->updateLog();
        }
    }

    /**
     * Write a new record to TC log.
     * @return void
     */
    public function updateLog()
    {
        $interval_time = Helper::getTimeIntervalStart($this->store_interval);

        foreach ( $this->ip_array as $_ip_origin => $current_ip ) {
            $id = md5($current_ip . $interval_time);
            //convert to long to prevent db mystery
            $md5_ip = md5($current_ip);
            $this->db->execute(
                "INSERT INTO " . $this->log_table . " SET
					id = '$id',
					log_type = 0,
					ip = '$current_ip',
					md5_ip = '$md5_ip',
					entries = 1,
					interval_start = $interval_time
				ON DUPLICATE KEY UPDATE
					ip = ip,
					md5_ip = md5_ip,
					entries = entries + 1,
					interval_start = $interval_time;"
            );
        }
    }

    /**
     * Clear TC log table. Chance to clean is 1 of 1000/$this->chance_to_clean. Limit is 100000 records.
     * @return void
     */
    private function clearTable()
    {
        if ( rand(0, 1000) < $this->chance_to_clean ) {
            $interval_start = Helper::getTimeIntervalStart($this->block_period + $this->store_interval);
            $this->db->execute(
                'DELETE
				FROM ' . $this->log_table . '
				WHERE interval_start < ' . $interval_start . '
				AND log_type  = 0
				LIMIT 100000;'
            );
        }
    }
}
