<?php

namespace CleantalkSP\SpbctWP\SpbcRateLimit;

use CleantalkSP\Common\RateLimit\RateLimiter;
use CleantalkSP\Common\RateLimit\RateLimitDTO;
use CleantalkSP\Common\TextPlate;
use CleantalkSP\Common\TT;
use CleantalkSP\SpbctWP\Helpers\IP;
use CleantalkSP\Variables\Server;

/**
 * WordPress-specific implementation of the rate limiter
 *
 * @package CleantalkSP\SpbctWP\SpbcRateLimit
 */
class SpbcRateLimiter extends RateLimiter
{
    use TextPlate;

    /**
     * Database table name for storing rate limit data
     *
     * @var string
     */
    private static $table_name = SPBC_TBL_RATE_LIMITS;

    /**
     * Cron rate to clean up global old entries
     * @var int
     */
    private static $clean_up_on_cron_rate = 86400;

    /**
     * @var bool
     */
    private static $debug = false;

    /**
     * Sets the IP address from WordPress environment
     *
     * @return void
     */
    protected function setIP(): void
    {
        $this->ip = TT::toString(IP::get());
    }

    protected function setUA(): void
    {
        $this->ua = Server::getString('HTTP_USER_AGENT', 'default_ua');
    }

    /**
     * Performs health check to ensure database table exists
     *
     * @return bool True if both parent health check passes and table exists, false otherwise
     */
    protected function healthCheck(): bool
    {
        global $wpdb;

        $sql = $wpdb->prepare('SHOW TABLES LIKE %s', static::$table_name);
        $table_ok = !empty($wpdb->get_var($sql));
        $parent_check = parent::healthCheck();
        return $table_ok && $parent_check;
    }

    /**
     * Handles errors by logging them with formatted message
     *
     * @param string $msg Error message to log
     * @return void
     */
    protected function handleErrors(string $msg): void
    {
        if (self::$debug) {
            $error = self::textPlateRender(
                'Security by CleanTalk: RateLimiter error: {{msg}}, type {{type}}, limit {{limit}}, period {{period}}',
                array(
                    'msg' => esc_html(TT::toString($msg)),
                    'type' => esc_html(TT::toString($this->config->type)),
                    'limit' => esc_html(TT::toString($this->config->limit)),
                    'period' => esc_html(TT::toString($this->config->period)),
                )
            );
            error_log($error);
        }
    }

    /**
     * Retrieves rate limit data for the current UID from database
     *
     * @return RateLimitDTO|false Rate limit data object or false if not found
     */
    public function selectUIDData()
    {
        global $wpdb;
        $sql = $wpdb->prepare(
            '
                SELECT uid, type, ip, ua, counter, last_call, created_at FROM ' . static::$table_name . '
                WHERE uid = %s LIMIT 1;
            ',
            $this->uid
        );
        $result = $wpdb->get_row($sql, ARRAY_A);

        return !empty($result) ? new RateLimitDTO($result) : false;
    }

    /**
     * Inserts a new rate limit record for the current UID in database.
     * @param RateLimitDTO $uid_data
     * @return bool True on successful insert, false on failure
     */
    protected function insert($uid_data): bool
    {
        global $wpdb;

        $sql = $wpdb->prepare(
            '
            INSERT INTO ' . static::$table_name . '
                (uid, type, ip, ua, counter, last_call, created_at)
            VALUES (%s, %s, %s, %s, 1, %d, %d)
            ON DUPLICATE KEY UPDATE last_call = %s, counter = counter + 1;
            ',
            $uid_data->uid,
            $uid_data->type,
            $uid_data->ip,
            $uid_data->ua,
            $uid_data->last_call,
            $uid_data->created_at,
            $uid_data->last_call
        );

        $result = $wpdb->query($sql);

        return false !== $result;
    }

    /**
     * Increments the counter for an existing rate limit record
     * Resets counter if the period has expired
     * @param RateLimitDTO $uid_data
     * @return bool True on successful update, false on failure
     */
    protected function increment($uid_data): bool
    {
        global $wpdb;

        $is_expired = ($this->current_ts - $uid_data->created_at) > $this->config->period;

        $uid_data->counter = $is_expired ? 1 : $uid_data->counter + 1;
        $uid_data->created_at = $is_expired ? $this->current_ts : $uid_data->created_at;
        $uid_data->last_call = $this->current_ts;

        $sql = $wpdb->prepare(
            '
            UPDATE ' . static::$table_name . ' SET
                counter = %d,
                last_call = %d,
                created_at = %d
            WHERE uid = %s
        ',
            $uid_data->counter,
            $uid_data->last_call,
            $uid_data->created_at,
            $uid_data->uid
        );

        return false !== $wpdb->query($sql);
    }

    /**
     * Removes expired rate limit records from the database
     * Records older than (period + 10 seconds) are deleted
     *
     * @return bool True on successful cleanup, false on failure
     */
    protected function cleanUp(): bool
    {
        global $wpdb;

        $threshold = $this->current_ts - ($this->config->period + 10);

        $sql = $wpdb->prepare(
            'DELETE FROM ' . static::$table_name . ' WHERE created_at < %d AND type = %s;',
            $threshold,
            $this->config->type
        );

        $result = $wpdb->query($sql);

        return false !== $result;
    }

    /**
     * Global cleanup on cron run.
     * @return void
     */
    public static function cleanUpOnCron()
    {
        global $wpdb;
        $sql = $wpdb->prepare(
            'DELETE FROM ' . static::$table_name . ' WHERE created_at < %d',
            static::$clean_up_on_cron_rate
        );
        $wpdb->query($sql);
    }
}
