<?php
/**
 * Rate Limiter Model
 *
 * Handles global rate limiting by tracking request counts per IP
 * in minute-based windows. Simplified version with only global limits.
 *
 * @package VMPFence
 * @since 2.0.0
 */

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

/**
 * VMPFence Rate Limiter Model
 *
 * @since 2.0.0
 */
class VMPFence_Rate_Limiter {

    /**
     * Track a request in the traffic rates table
     *
     * @since 2.0.0
     * @param string $ip The IP address
     * @param string $hitType The type of hit ('hit' or '404')
     * @return bool True on success, false on failure
     */
    public function track_request($ip, $hitType = 'hit') {
        global $wpdb;

        if (empty($ip)) {
            return false;
        }
        
        $table_name = VMPFence_Constants::get_table_name(VMPFence_Constants::TABLE_TRAFFIC_RATES);
        $current_window = floor(time() / 60); // Per-minute window
        $ip_binary = inet_pton($ip);
        
        if ($ip_binary === false) {
            return false;
        }

        // Insert or update hit count for current minute window
        $result = $wpdb->query(
            $wpdb->prepare(
                "INSERT INTO {$table_name} (IP, hitCount, hitType, window_start) 
                 VALUES (%s, 1, %s, %d) 
                 ON DUPLICATE KEY UPDATE hitCount = hitCount + 1",
                $ip_binary,
                $hitType,
                $current_window
            )
        );

        return $result !== false;
    }

    /**
     * Check if IP is currently blocked for rate limiting
     *
     * @since 2.0.0
     * @param string $ip The IP address
     * @return bool True if blocked, false if not
     */
    public function is_blocked($ip) {
        global $wpdb;

        if (empty($ip)) {
            return false;
        }

        $blocks_table = VMPFence_Constants::get_table_name(VMPFence_Constants::TABLE_BLOCKS);
        $ip_binary = inet_pton($ip);

        if ($ip_binary === false) {
            return false;
        }

        $current_time = time();
        
        $blocked = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT id FROM {$blocks_table} 
                 WHERE IP = %s AND type = 1 
                 AND (expiration = 0 OR expiration > %d)
                 LIMIT 1",
                $ip_binary,
                $current_time
            )
        );
        return $blocked !== null;
    }

    /**
     * Get block information for an IP if it's currently blocked
     *
     * @since 2.0.0
     * @param string $ip The IP address
     * @return array|false Block details if blocked, false if not blocked
     */
    public function get_block_info($ip) {
        global $wpdb;

        if (empty($ip)) {
            return false;
        }

        $blocks_table = VMPFence_Constants::get_table_name(VMPFence_Constants::TABLE_BLOCKS);
        $ip_binary = inet_pton($ip);

        if ($ip_binary === false) {
            return false;
        }

        $current_time = time();
        
        $block_info = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT id, type, blockedTime, reason, expiration, lastAttempt, blockedHits 
                 FROM {$blocks_table} 
                 WHERE IP = %s AND type = 1 
                 AND (expiration = 0 OR expiration > %d)
                 LIMIT 1",
                $ip_binary,
                $current_time
            ),
            ARRAY_A
        );
        return $block_info !== null ? $block_info : false;
    }

    /**
     * Check if IP exceeds rate limits with visitor type awareness
     *
     * @since 2.0.0
     * @param string $ip The IP address
     * @param string $hitType The type of hit ('hit' or '404')
     * @param string $visitor_type The visitor type ('human' or 'crawler')
     * @return array|false Array with violation details if limit exceeded, false otherwise
     */
    public function is_rate_limited($ip, $hitType = 'hit', $visitor_type = 'human') {
        // Check if rate limiting is enabled
        if (!VMPFence_Config::get('rate_limiting_enabled', 0)) {
            return false;
        }

        // Check if IP is already blocked
        if ($this->is_blocked($ip)) {
            return array(
                'type' => 'blocked',
                'action' => 'block',
                'limit' => 'N/A',
                'count' => 'N/A',
                'message' => 'IP is currently blocked due to previous rate limit violation'
            );
        }

        // Track this request first
        $this->track_request($ip, $hitType);

        // Get current hit count for this IP in current minute
        $current_window = floor(time() / 60);
        
        // Get specific hit type count or total count depending on what we're checking
        $specific_hit_count = $this->get_hit_count($ip, $current_window, $hitType);
        $total_hit_count = $this->get_hit_count($ip, $current_window);

        // Priority 1: Check visitor-specific limits (crawler or human) based on hit type
        if ($visitor_type === 'crawler') {
            // Check crawler-specific limits
            if ($hitType === '404') {
                $limit = VMPFence_Config::get('crawler_404_limit');
                $action = VMPFence_Config::get('crawler_404_action');
                $limit_name = 'crawler 404';
                $check_count = $specific_hit_count;
            } else {
                $limit = VMPFence_Config::get('crawler_rate_limit');
                $action = VMPFence_Config::get('crawler_rate_action');
                $limit_name = 'crawler page views';
                $check_count = $specific_hit_count;
            }
        } else {
            // Check human-specific limits
            if ($hitType === '404') {
                $limit = VMPFence_Config::get('human_404_limit');
                $action = VMPFence_Config::get('human_404_action');
                $limit_name = 'human 404';
                $check_count = $specific_hit_count;
            } else {
                $limit = VMPFence_Config::get('human_rate_limit');
                $action = VMPFence_Config::get('human_rate_action');
                $limit_name = 'human page views';
                $check_count = $specific_hit_count;
            }
        }

        // Check visitor-specific limit
        if ($limit !== 'unlimited' && is_numeric($limit)) {
            $limit_value = (int) $limit;
            
            if ($check_count > $limit_value) {
                return array(
                    'type' => $limit_name,
                    'action' => $action,
                    'limit' => $limit_value,
                    'count' => $check_count,
                    'message' => ucfirst($limit_name) . " limit exceeded: {$check_count} requests in current minute (limit: {$limit_value})"
                );
            }
        }

        // Priority 2: Check global rate limit (applies to all visitors, uses total count)
        $global_limit = VMPFence_Config::get('global_rate_limit');
        
        if ($global_limit !== 'unlimited' && is_numeric($global_limit)) {
            $limit_value = (int) $global_limit;
            
            if ($total_hit_count > $limit_value) {
                $global_action = VMPFence_Config::get('global_rate_action');
                
                return array(
                    'type' => 'global',
                    'action' => $global_action,
                    'limit' => $limit_value,
                    'count' => $total_hit_count,
                    'message' => "Global rate limit exceeded: {$total_hit_count} requests in current minute (limit: {$limit_value})"
                );
            }
        }

        return false;
    }

    /**
     * Get hit count for IP in specific minute window
     *
     * @since 2.0.0
     * @param string $ip The IP address
     * @param int $window_start The minute window timestamp
     * @param string|null $hitType Optional hit type ('hit' or '404'). If null, returns sum of all types.
     * @return int Hit count
     */
    public function get_hit_count($ip, $window_start, $hitType = null) {
        global $wpdb;

        if (empty($ip)) {
            return 0;
        }

        $table_name = VMPFence_Constants::get_table_name(VMPFence_Constants::TABLE_TRAFFIC_RATES);
        $ip_binary = inet_pton($ip);

        if ($ip_binary === false) {
            return 0;
        }

        if ($hitType !== null) {
            // Get specific hit type count
            $hit_count = $wpdb->get_var(
                $wpdb->prepare(
                    "SELECT hitCount FROM {$table_name} 
                     WHERE IP = %s AND window_start = %d AND hitType = %s",
                    $ip_binary,
                    $window_start,
                    $hitType
                )
            );
        } else {
            // Get total count (sum of all hit types)
            $hit_count = $wpdb->get_var(
                $wpdb->prepare(
                    "SELECT SUM(hitCount) FROM {$table_name} 
                     WHERE IP = %s AND window_start = %d",
                    $ip_binary,
                    $window_start
                )
            );
        }
        return $hit_count ? (int) $hit_count : 0;
    }

    /**
     * Block an IP for rate limit violations
     *
     * @since 2.0.0
     * @param string $ip The IP address
     * @param string $reason The reason for blocking
     * @return bool True on success, false on failure
     */
    public function block_ip($ip, $reason = 'Rate limit exceeded') {
        global $wpdb;

        if (empty($ip)) {
            return false;
        }

        $blocks_table = VMPFence_Constants::get_table_name(VMPFence_Constants::TABLE_BLOCKS);
        $ip_binary = inet_pton($ip);

        if ($ip_binary === false) {
            return false;
        }

        $current_time = time();
        $block_duration_minutes = VMPFence_Config::get('rate_block_duration');
        $block_duration_seconds = $block_duration_minutes * 60;
        $expiration = $current_time + $block_duration_seconds;

        // Insert or update block in blocks table (type = 1 for rate limiting)
        $result = $wpdb->query(
            $wpdb->prepare(
                "INSERT INTO {$blocks_table} 
                 (type, IP, blockedTime, reason, expiration, lastAttempt, blockedHits) 
                 VALUES (1, %s, %d, %s, %d, %d, 0)
                 ON DUPLICATE KEY UPDATE 
                 blockedTime = %d, reason = %s, expiration = %d, lastAttempt = %d, blockedHits = blockedHits + 1",
                $ip_binary, $current_time, $reason, $expiration, $current_time,
                $current_time, $reason, $expiration, $current_time
            )
        );
        
        // Log to WAF log for historical tracking ONLY on new blocks (INSERT)
        // ON DUPLICATE KEY UPDATE returns: 1 for INSERT, 2 for UPDATE, 0 for no change
        // This prevents duplicate log entries for the same blocking event
        if ($result === 1 && class_exists('VMPFence_WAF_Log')) {
            VMPFence_WAF_Log::log_attack(array(
                'action' => 'block',
                'actionDescription' => 'Rate limit block: ' . $reason,
                'severity' => 1, // low-medium severity for rate limiting
                'logMsg' => sprintf('Blocked IP %s for rate limiting: %s', $ip, $reason),
                'IP' => $ip_binary,
                'actionData' => wp_json_encode(array(
                    'block_type' => 'rate_limit',
                    'duration_minutes' => $block_duration_minutes,
                    'expiration' => $expiration
                ))
            ));
        }
        
        return $result !== false;
    }

    /**
     * Unblock an IP (remove rate limit block)
     *
     * @since 2.0.0
     * @param string $ip The IP address
     * @return bool True on success, false on failure
     */
    public function unblock_ip($ip) {
        global $wpdb;

        if (empty($ip)) {
            return false;
        }

        $blocks_table = VMPFence_Constants::get_table_name(VMPFence_Constants::TABLE_BLOCKS);
        $ip_binary = inet_pton($ip);

        if ($ip_binary === false) {
            return false;
        }

        // Remove block from blocks table (type = 1 for rate limiting)
        $result = $wpdb->query(
            $wpdb->prepare(
                "DELETE FROM {$blocks_table} WHERE IP = %s AND type = 1",
                $ip_binary
            )
        );
        return $result !== false;
    }

    /**
     * Clean up old traffic data
     *
     * @since 2.0.0
     * @return int Number of records deleted
     */
    public function trim_old_data() {
        global $wpdb;

        $table_name = VMPFence_Constants::get_table_name(VMPFence_Constants::TABLE_TRAFFIC_RATES);
        
        // Keep data for last 2 hours (120 minutes) to handle blocks properly
        $cutoff_time = floor(time() / 60) - 120;

        $deleted = $wpdb->query(
            $wpdb->prepare(
                "DELETE FROM {$table_name} WHERE window_start < %d",
                $cutoff_time
            )
        );

        return $deleted ? $deleted : 0;
    }

    /**
     * Get rate limiting statistics for admin
     *
     * @since 2.0.0
     * @return array Statistics data
     */
    public function get_stats() {
        global $wpdb;

        $traffic_table = VMPFence_Constants::get_table_name(VMPFence_Constants::TABLE_TRAFFIC_RATES);
        $blocks_table = VMPFence_Constants::get_table_name(VMPFence_Constants::TABLE_BLOCKS);
        $current_window = floor(time() / 60);
        $current_time = time();

        // Get current minute stats (unique IPs)
        $current_requests = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(DISTINCT IP) FROM {$traffic_table} WHERE window_start = %d",
                $current_window
            )
        );

        // Get blocked IPs count (active blocks only)
        $blocked_ips = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM {$blocks_table} 
                 WHERE type = 1 AND (expiration = 0 OR expiration > %d)",
                $current_time
            )
        );

        // Get total requests in last hour
        $hour_ago = $current_window - 60;
        $hourly_requests = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT SUM(hitCount) FROM {$traffic_table} 
                 WHERE window_start > %d",
                $hour_ago
            )
        );

        return array(
            'current_minute_requests' => (int) $current_requests,
            'blocked_ips_count' => (int) $blocked_ips,
            'hourly_requests' => (int) $hourly_requests,
            'current_window' => $current_window
        );
    }

    /**
     * Get list of currently blocked IPs
     *
     * @since 2.0.0
     * @return array List of blocked IP addresses
     */
    public function get_blocked_ips() {
        global $wpdb;

        $blocks_table = VMPFence_Constants::get_table_name(VMPFence_Constants::TABLE_BLOCKS);
        $current_time = time();

        $results = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT IP, blockedTime, blockedHits, reason, expiration FROM {$blocks_table} 
                 WHERE type = 1 AND (expiration = 0 OR expiration > %d) 
                 ORDER BY blockedTime DESC",
                $current_time
            ),
            ARRAY_A
        );

        $blocked_ips = array();
        
        if ($results) {
            foreach ($results as $row) {
                $ip = inet_ntop($row['IP']);
                if ($ip) {
                    $blocked_ips[] = array(
                        'ip' => $ip,
                        'blocked_at' => $row['blockedTime'],
                        'blocked_hits' => $row['blockedHits'],
                        'reason' => $row['reason'],
                        'expiration' => $row['expiration']
                    );
                }
            }
        }

        return $blocked_ips;
    }
}