<?php
/**
 * Brute Force Protection Model
 *
 * @package VMPFence
 * @since 2.0.0
 * @since 2.2.5 -- Added enhanced observer-based audit logging
 */

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

/**
 * VMPFence Brute Force Model
 * 
 * Handles brute force attack detection, lockouts, and permanent bans
 * Uses existing tables: wp_vmpfence_blocked_ips, wp_vmpfence_security_attempts
 */
class VMPFence_Brute_Force_Model {

    /**
     * Check if an IP is currently locked out
     *
     * @param string $ip IP address to check
     * @return bool True if locked out, false otherwise
     */
    public function is_locked_out($ip) {
        global $wpdb;

        $table_name = VMPFence_Constants::get_table_name(VMPFence_Constants::TABLE_BLOCKED_IPS);
        
        // Check for active lockout
        $lockout = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM `{$table_name}` WHERE ip_address = %s AND blocked_until > NOW()",
            $ip
        ));

        return $lockout !== null;
    }

    /**
     * Track a failed login attempt
     *
     * @param string $ip IP address
     * @param string $username Username attempted
     * @param string $reason Reason for failure (invalid_user, wrong_password, etc.)
     * @return bool Success status
     */
    public function track_failed_login($ip, $username = '', $reason = 'failed_login') {
        global $wpdb;

        $table_name = VMPFence_Constants::get_table_name(VMPFence_Constants::TABLE_SECURITY_ATTEMPTS);
        
        $result = $wpdb->insert(
            $table_name,
            array(
                'ip_address' => $ip,
                'username' => $username,
                'event_type' => 'failed_login',
                'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '',
                'attempt_time' => current_time('mysql'),
                'geo_location' => $this->get_country_code($ip),
                'attempt_count' => 1
            ),
            array('%s', '%s', '%s', '%s', '%s', '%d')
        );

        if ($result) {
            // Check if we need to apply lockout
            $this->check_and_apply_lockout($ip);
        }

        return $result !== false;
    }

    /**
     * Get failed login count for an IP within the time window
     *
     * @param string $ip IP address
     * @param int $time_window Time window in minutes (default: from config)
     * @return int Number of failed attempts
     */
    public function get_failed_count($ip, $time_window = null) {
        global $wpdb;

        if ($time_window === null) {
            $time_window = VMPFence_Config::get('count_failures_window', 240); // 4 hours default
        }

        $table_name = VMPFence_Constants::get_table_name(VMPFence_Constants::TABLE_SECURITY_ATTEMPTS);
        $cutoff_time = date('Y-m-d H:i:s', time() - ($time_window * 60)); // Convert to MySQL datetime

        $count = $wpdb->get_var($wpdb->prepare(
            "SELECT SUM(attempt_count) FROM `{$table_name}` WHERE ip_address = %s AND attempt_time >= %s AND event_type = 'failed_login'",
            $ip,
            $cutoff_time
        ));

        return (int) $count;
    }

    /**
     * Apply lockout to an IP address
     *
     * @param string $ip IP address
     * @param int $duration Lockout duration in minutes (default: from config)
     * @param bool $permanent Whether this is a permanent ban
     * @return bool Success status
     */
    public function apply_lockout($ip, $duration = null, $permanent = false, $reason= null) {
        global $wpdb;

        if ($duration === null) {
            $duration = VMPFence_Config::get('lockout_duration', 240); // 4 hours default
        }
        $table_name = VMPFence_Constants::get_table_name(VMPFence_Constants::TABLE_BLOCKED_IPS);
        
        // Calculate blocked_until datetime using MySQL functions (consistent with brute force controller)
        if ($permanent) {
            $blocked_until = $wpdb->get_var($wpdb->prepare(
                "SELECT DATE_ADD(NOW(), INTERVAL %d YEAR)",
                1
            ));
        } else {
            $blocked_until = $wpdb->get_var($wpdb->prepare(
                "SELECT DATE_ADD(NOW(), INTERVAL %d MINUTE)",
                $duration
            ));
        }
        
        // Get current MySQL time
        $blocked_at = $wpdb->get_var("SELECT NOW()");

        // Check if lockout already exists
        $existing = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM `{$table_name}` WHERE ip_address = %s",
            $ip
        ));
        if($reason === null) {
            $reason = 'Brute force attack detected';
        }
        if ($existing) {
            // Update existing lockout
            $result = $wpdb->update(
                $table_name,
                array(
                    'blocked_until' => $blocked_until,
                    'reason' => $reason,
                    'last_attempt' => $blocked_at,
                    'block_count' => (int) $existing->block_count + 1
                ),
                array('ip_address' => $ip),
                array('%s', '%s', '%s', '%d'),
                array('%s')
            );
            
            // Log to WAF log for historical tracking (UPDATE path)
            if ($result !== false && class_exists('VMPFence_WAF_Log')) {
                $ip_binary = @inet_pton($ip);
                if ($ip_binary !== false) {
                    VMPFence_WAF_Log::log_attack(array(
                        'action' => 'block',
                        'actionDescription' => 'Brute force lockout extended: ' . $reason,
                        'severity' => 3, // high severity for brute force
                        'logMsg' => sprintf('Extended lockout for IP %s: %s (attempt #%d)', $ip, $reason, (int) $existing->block_count + 1),
                        'IP' => $ip_binary,
                        'actionData' => wp_json_encode(array(
                            'block_type' => 'brute_force',
                            'duration_minutes' => $duration,
                            'permanent' => $permanent,
                            'block_count' => (int) $existing->block_count + 1
                        ))
                    ));
                }
            }
        } else {
            // Insert new lockout
            $result = $wpdb->insert(
                $table_name,
                array(
                    'ip_address' => $ip,
                    'reason' => $reason,
                    'blocked_at' => $blocked_at,
                    'blocked_until' => $blocked_until,
                    'block_count' => 1,
                    'last_attempt' => $blocked_at
                ),
                array('%s', '%s', '%s', '%s', '%d', '%s')
            );
            
            // Log to WAF log for historical tracking (INSERT path)
            if ($result !== false && class_exists('VMPFence_WAF_Log')) {
                $ip_binary = @inet_pton($ip);
                if ($ip_binary !== false) {
                    VMPFence_WAF_Log::log_attack(array(
                        'action' => 'block',
                        'actionDescription' => 'Brute force lockout applied: ' . $reason,
                        'severity' => 3, // high severity for brute force
                        'logMsg' => sprintf('Locked out IP %s: %s (duration: %s)', $ip, $reason, $permanent ? 'permanent' : $duration . ' minutes'),
                        'IP' => $ip_binary,
                        'actionData' => wp_json_encode(array(
                            'block_type' => 'brute_force',
                            'duration_minutes' => $duration,
                            'permanent' => $permanent
                        ))
                    ));
                }
            }
        }

        // Fire hook for observer-based audit logging
        if ($result !== false) {
            do_action('vmpfence_brute_force_lockout', $ip, $duration, $permanent, $this->get_failed_count($ip));
        }

        return $result !== false;
    }

    /**
     * Apply permanent ban to an IP address
     *
     * @param string $ip IP address
     * @return bool Success status
     */
    public function apply_permanent_ban($ip) {
        return $this->apply_lockout($ip, 0, true);
    }

    /**
     * Clear lockout for an IP address
     *
     * @param string $ip IP address
     * @return bool Success status
     */
    public function clear_lockout($ip) {
        global $wpdb;

        $table_name = VMPFence_Constants::get_table_name(VMPFence_Constants::TABLE_BLOCKED_IPS);
        
        $result = $wpdb->delete(
            $table_name,
            array('ip_address' => $ip),
            array('%s')
        );

        // Also clear failed login records for this IP (optional cleanup)
        $failed_table = VMPFence_Constants::get_table_name(VMPFence_Constants::TABLE_SECURITY_ATTEMPTS);
        $wpdb->delete(
            $failed_table,
            array('ip_address' => $ip),
            array('%s')
        );

        return $result !== false;
    }

    /**
     * Get lockout count for an IP (how many times it has been locked out)
     *
     * @param string $ip IP address
     * @return int Number of times locked out
     */
    public function get_lockout_count($ip) {
        global $wpdb;

        // Count from audit log entries
        $audit_table = VMPFence_Constants::get_table_name(VMPFence_Constants::TABLE_AUDIT_LOG);
        
        $count = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM `{$audit_table}` WHERE action = 'brute_force_lockout' AND data LIKE %s",
            '%"ip":"' . $ip . '"%'
        ));

        return (int) $count;
    }

    /**
     * Check if a username is in the blocked usernames list
     *
     * @param string $username Username to check
     * @return bool True if username should be immediately blocked
     */
    public function is_invalid_username($username) {
        $blocked_usernames = VMPFence_Config::get('blocked_usernames', array());
        
        if (empty($blocked_usernames) || !is_array($blocked_usernames)) {
            return false;
        }

        // Check for exact match or pattern match
        foreach ($blocked_usernames as $blocked) {
            $blocked = trim($blocked);
            if (empty($blocked)) {
                continue;
            }

            // Exact match
            if (strcasecmp($username, $blocked) === 0) {
                return true;
            }

            // Pattern match (if contains wildcards)
            if (strpos($blocked, '*') !== false) {
                $pattern = '/^' . str_replace('*', '.*', preg_quote($blocked, '/')) . '$/i';
                if (preg_match($pattern, $username)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Check if lockout should be applied and apply it
     *
     * @param string $ip IP address
     * @return bool True if lockout was applied
     */
    private function check_and_apply_lockout($ip) {
        // Skip if already locked out
        if ($this->is_locked_out($ip)) {
            return false;
        }

        $max_attempts = VMPFence_Config::get('max_login_attempts', 20);
        $failed_count = $this->get_failed_count($ip);

        if ($failed_count >= $max_attempts) {
            // Check if this IP should get permanent ban
            $lockout_count = $this->get_lockout_count($ip);
            $permanent_ban_threshold = VMPFence_Config::get('permanent_ban_threshold', 10);

            if ($lockout_count >= $permanent_ban_threshold) {
                return $this->apply_permanent_ban($ip);
            } else {
                return $this->apply_lockout($ip);
            }
        }

        return false;
    }

    /**
     * Get country code for an IP address
     *
     * @param string $ip IP address
     * @return string Country code (2 letters) or 'XX' if unknown
     */
    private function get_country_code($ip) {
        // Basic implementation - can be enhanced with GeoIP later
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
            // For now, return unknown for public IPs
            return 'XX';
        }
        
        // Private/local IPs
        return 'LO';
    }

    /**
     * Log security event to wp_vmpfence_security_attempts table
     * 
     * @param string $ip IP address
     * @param string $event_type Event type
     * @param string $reason Reason for the event
     * @return bool Success status
     */
    public function log_security_event($ip, $event_type, $reason = '') {
        global $wpdb;
        
        $table_name = VMPFence_Constants::get_table_name(VMPFence_Constants::TABLE_SECURITY_ATTEMPTS);
        
        $result = $wpdb->insert(
            $table_name,
            array(
                'ip_address' => $ip,
                'username' => '',
                'event_type' => $event_type,
                'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '',
                'attempt_time' => current_time('mysql'),
                'geo_location' => $this->get_country_code($ip),
                'attempt_count' => 1
            ),
            array('%s', '%s', '%s', '%s', '%s', '%s', '%d')
        );

        return $result !== false;
    }

    /**
     * Show lockout page with custom text
     *
     * @param string $ip IP address that is locked out
     * @param string $reason Reason for lockout
     * @param int $duration Lockout duration in minutes
     */
    public function show_lockout_page($ip, $reason = '', $duration = 0) {
        status_header(503);
        
        // Get custom block text
        $custom_text = $this->get_custom_block_text();
        
        // Set variables for the template
        $lockout_duration = $duration;
        
        // Load the brute force lockout template
        include VMPFENCE_PLUGIN_DIR . '/firewall/views/brute-force-503-lockout.php';
        exit;
    }

    /**
     * Get custom block text with proper formatting
     * 
     * @return string Formatted custom text or empty string
     */
    private function get_custom_block_text() {
        $text = VMPFence_Config::get('custom_block_text', '');
        
        if (empty($text)) {
            return '';
        }
        
        // Apply processing: strip HTML and auto-paragraph
        return wpautop(wp_strip_all_tags($text));
    }
}