<?php
/**
 * Live Traffic Model
 *
 * @package VMPFence
 * @subpackage Tools
 * @since 2.1.0
 */

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

/**
 * Live Traffic Model Class
 *
 * @since 2.1.0
 */
class VMPFence_Live_Traffic_Model {

    /**
     * Get live traffic logs
     *
     * @since 2.1.0
     * @param array $args Query arguments
     * @return array Traffic logs
     */
    public static function get_live_traffic($args = array()) {
        global $wpdb;

        $defaults = array(
            'limit' => 100,
            'offset' => 0,
            'filter_type' => 'all', // all, humans, registered_users, crawlers, google_crawlers, pages_not_found, logins_logouts, locked_out, blocked, blocked_by_firewall
            'order_by' => 'attackLogTime',
            'order' => 'DESC'
        );

        $args = wp_parse_args($args, $defaults);

        $table_name = VMPFence_Constants::get_table_name(VMPFence_Constants::TABLE_HITS);
        
        $where = '1=1';
        
        // Apply filters
        switch ($args['filter_type']) {
            case 'humans':
                $where .= ' AND jsRun = 1 AND userID = 0';
                break;
            
            case 'registered_users':
                $where .= ' AND userID > 0';
                break;
            
            case 'crawlers':
                $where .= ' AND (jsRun = 0 OR isGoogle = 1)';
                break;
            
            case 'google_crawlers':
                $where .= ' AND isGoogle = 1';
                break;
            
            case 'pages_not_found':
                $where .= ' AND statusCode = 404';
                break;
            
            case 'logins_logouts':
                $where .= " AND (action LIKE '%login%' OR action LIKE '%logout%')";
                break;
            
            case 'locked_out':
                $where .= " AND action LIKE '%lockout%'";
                break;
            
            case 'blocked':
                $where .= " AND (action LIKE '%blocked:waf%' OR action LIKE '%lockedOut%' OR action LIKE '%throttle%' OR action LIKE '%rate_limit%' OR (action LIKE '%blocked%' AND action NOT LIKE '%firewall%'))";
                break;
            
            case 'blocked_by_firewall':
                $where .= " AND (action LIKE '%firewall%' OR action LIKE '%waf%')";
                break;
        }

        // Get total count
        $total = $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE $where");

        // Build query with proper preparation - use placeholders only for limit/offset
        // Don't use prepare() when WHERE clause has LIKE wildcards (%)
        $query = "SELECT 
                id,
                attackLogTime,
                ctime,
                IP,
                jsRun,
                statusCode,
                isGoogle,
                userID,
                URL,
                referer,
                UA,
                action,
                actionDescription,
                actionData
            FROM $table_name 
            WHERE $where 
            ORDER BY {$args['order_by']} {$args['order']} 
            LIMIT " . intval($args['limit']) . " OFFSET " . intval($args['offset']);

        $results = $wpdb->get_results($query, ARRAY_A);

        // Handle query failure
        if ($results === null) {
            $results = array();
        }

        // Convert binary IP to readable format
        foreach ($results as &$row) {
            if (!empty($row['IP'])) {
                $row['IP'] = @inet_ntop($row['IP']);
            }
            // Format timestamp
            $row['formatted_time'] = date('m/d/Y g:i:s A', (int)$row['attackLogTime']);
            
            // Get hostname
            $row['hostname'] = self::get_hostname($row['IP']);
            
            // Determine type
            $row['type'] = self::get_traffic_type($row);
            
            // Get response code
            $row['response'] = !empty($row['statusCode']) ? $row['statusCode'] : 200;

            // Get country info from GeoIP lookup
            $country_info = self::get_country_info($row['IP']);
            $row['countryCode'] = $country_info['code'];
            $row['countryName'] = $country_info['name'];
            $row['countryFlag'] = $country_info['flag'];
        }

        return array(
            'total' => $total,
            'data' => $results
        );
    }

    /**
     * Get traffic type
     *
     * @since 2.1.0
     * @param array $row Traffic row data
     * @return string Traffic type
     */
    private static function get_traffic_type($row) {
        // Define actual blocking actions
        $blocking_actions = array(
            'blocked:waf',
            'lockedOut',
            'throttle',
            'blocked',
            'rate_limit_block',
            'rate_limit_throttle'
        );
        
        // Check if this is a blocked action
        if (!empty($row['action'])) {
            foreach ($blocking_actions as $blocking_action) {
                if (strpos($row['action'], $blocking_action) !== false) {
                    return 'blocked';
                }
            }
            
            // Authentication and security events that are not blocks
            // These are human-initiated actions, so show as "human" type
            if (strpos($row['action'], 'login') !== false || 
                strpos($row['action'], 'logout') !== false ||
                strpos($row['action'], 'lostPassword') !== false ||
                strpos($row['action'], 'user:') !== false ||
                strpos($row['action'], 'logged:waf') !== false) {
                return 'human';
            }
        }
        
        if ($row['isGoogle'] == 1) {
            return 'bot';
        }
        
        if ($row['jsRun'] == 1) {
            return 'human';
        }
        
        return 'unspecified';
    }

    /**
     * Get hostname for IP
     *
     * @since 2.1.0
     * @param string $ip IP address
     * @return string Hostname
     */
    public static function get_hostname($ip) {
        if (empty($ip)) {
            return '';
        }

        // Check reverse DNS cache first (7-day expiry)
        global $wpdb;
        $cache_table = VMPFence_Constants::get_table_name(VMPFence_Constants::TABLE_REVERSE_CACHE);
        $ip_binary = @inet_pton($ip);
        if ($ip_binary !== false) {
            $cache_expiry = time() - (7 * 86400);
            $cached = $wpdb->get_var(
                $wpdb->prepare(
                    "SELECT host FROM {$cache_table} WHERE IP = %s AND lastUpdate > %d",
                    $ip_binary,
                    $cache_expiry
                )
            );
            if ($cached !== null) {
                return $cached;
            }
        }

        // Original logic below - unchanged
        // Try to get hostname via reverse DNS lookup
        $hostname = @gethostbyaddr($ip);
        
        // If hostname was resolved and is different from IP, return it
        if ($hostname && $hostname !== $ip) {
            // Cache successful lookup
            if ($ip_binary !== false) {
                $wpdb->query(
                    $wpdb->prepare(
                        "INSERT INTO {$cache_table} (IP, host, lastUpdate) 
                         VALUES (%s, %s, %d)
                         ON DUPLICATE KEY UPDATE host = %s, lastUpdate = %d",
                        $ip_binary, $hostname, time(),
                        $hostname, time()
                    )
                );
            }
            return $hostname;
        }

        // If it's a local/private IP and hostname lookup failed, return 'localhost'
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
            // Cache localhost result
            if ($ip_binary !== false) {
                $wpdb->query(
                    $wpdb->prepare(
                        "INSERT INTO {$cache_table} (IP, host, lastUpdate) 
                         VALUES (%s, %s, %d)
                         ON DUPLICATE KEY UPDATE host = %s, lastUpdate = %d",
                        $ip_binary, 'localhost', time(),
                        'localhost', time()
                    )
                );
            }
            return 'localhost';
        }

        // Cache failed lookup as empty string
        if ($ip_binary !== false) {
            $wpdb->query(
                $wpdb->prepare(
                    "INSERT INTO {$cache_table} (IP, host, lastUpdate) 
                     VALUES (%s, %s, %d)
                     ON DUPLICATE KEY UPDATE host = %s, lastUpdate = %d",
                    $ip_binary, '', time(),
                    '', time()
                )
            );
        }

        return '';
    }

    /**
     * Get WAF logs (security events)
     *
     * @since 2.1.0
     * @param array $args Query arguments
     * @return array WAF logs
     */
    public static function get_waf_logs($args = array()) {
        global $wpdb;

        $defaults = array(
            'limit' => 100,
            'offset' => 0,
            'order_by' => 'attackLogTime',
            'order' => 'DESC'
        );

        $args = wp_parse_args($args, $defaults);

        $table_name = VMPFence_Constants::get_table_name(VMPFence_Constants::TABLE_WAF_LOG);

        // Get total count
        $total = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");

        // Get paginated results
        $query = $wpdb->prepare(
            "SELECT 
                id,
                attackLogTime,
                IP,
                countryCode,
                severity,
                action,
                actionDescription,
                actionData,
                logMsg,
                request_uri,
                http_referer,
                http_user_agent
            FROM $table_name 
            ORDER BY {$args['order_by']} {$args['order']} 
            LIMIT %d OFFSET %d",
            $args['limit'],
            $args['offset']
        );

        $results = $wpdb->get_results($query, ARRAY_A);

        // Handle query failure
        if ($results === null) {
            $results = array();
        }

        // Convert binary IP to readable format
        foreach ($results as &$row) {
            if (!empty($row['IP'])) {
                $row['IP'] = @inet_ntop($row['IP']);
            }
            // Format timestamp
            $row['formatted_time'] = date('m/d/Y g:i:s A', (int)$row['attackLogTime']);
        }

        return array(
            'total' => $total,
            'data' => $results
        );
    }

    /**
     * Get audit log entries
     *
     * @since 2.1.0
     * @param array $args Query arguments
     * @return array Audit log entries
     */
    public static function get_audit_logs($args = array()) {
        global $wpdb;

        $defaults = array(
            'limit' => 100,
            'offset' => 0,
            'event_type' => '',
            'order_by' => 'time',
            'order' => 'DESC'
        );

        $args = wp_parse_args($args, $defaults);

        $table_name = VMPFence_Constants::get_table_name(VMPFence_Constants::TABLE_AUDIT_LOG);

        $where = '1=1';
        
        if (!empty($args['event_type'])) {
            $where .= $wpdb->prepare(' AND event_type = %s', $args['event_type']);
        }

        // Get total count
        $total = $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE $where");

        // Get paginated results
        $query = $wpdb->prepare(
            "SELECT 
                id,
                time,
                event_type,
                user_id,
                username,
                user_role,
                source_ip,
                user_agent,
                object_type,
                object_id,
                object_name,
                action,
                message,
                old_value,
                new_value,
                additional_info
            FROM $table_name 
            WHERE $where
            ORDER BY {$args['order_by']} {$args['order']} 
            LIMIT %d OFFSET %d",
            $args['limit'],
            $args['offset']
        );

        $results = $wpdb->get_results($query, ARRAY_A);

        // Handle query failure
        if ($results === null) {
            $results = array();
        }

        // Convert binary IP to readable format and format data
        foreach ($results as &$row) {
            if (!empty($row['source_ip'])) {
                $row['source_ip'] = @inet_ntop($row['source_ip']);
            }
            // Format timestamp
            $row['formatted_time'] = date('m/d/Y g:i:s A', (int)$row['time']);
        }

        return array(
            'total' => $total,
            'data' => $results
        );
    }

    /**
     * Get traffic statistics
     *
     * @since 2.1.0
     * @return array Traffic statistics
     */
    public static function get_traffic_stats() {
        global $wpdb;

        $table_name = VMPFence_Constants::get_table_name(VMPFence_Constants::TABLE_HITS);
        
        $stats = array(
            'total_hits' => 0,
            'human_hits' => 0,
            'bot_hits' => 0,
            'blocked_hits' => 0
        );

        // Get counts for last 24 hours
        $cutoff = time() - 86400;
        
        $stats['total_hits'] = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM $table_name WHERE attackLogTime > %f",
            $cutoff
        ));

        $stats['human_hits'] = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM $table_name WHERE attackLogTime > %f AND jsRun = 1",
            $cutoff
        ));

        $stats['bot_hits'] = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM $table_name WHERE attackLogTime > %f AND (jsRun = 0 OR isGoogle = 1)",
            $cutoff
        ));

        $stats['blocked_hits'] = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM $table_name WHERE attackLogTime > %f AND action != ''",
            $cutoff
        ));

        return $stats;
    }

    /**
     * Get live traffic with advanced filtering and grouping
     *
     * @since 2.1.0
     * @since 2.2.2 Added 'since' parameter for realtime polling
     * @param array $args Query arguments
     * @return array Traffic results with metadata
     */
    public static function get_live_traffic_advanced($args = array()) {
        $defaults = array(
            'param' => array(),       // Filter parameters
            'operator' => array(),    // Filter operators
            'value' => array(),       // Filter values
            'groupby' => null,        // Group by field
            'startDate' => null,      // Start date timestamp
            'endDate' => null,        // End date timestamp
            'since' => null,          // Since timestamp (for realtime polling)
            'limit' => 50,
            'offset' => 0,
        );

        $args = wp_parse_args($args, $defaults);

        // If 'since' is provided (for realtime polling), use it as startDate
        if (!empty($args['since'])) {
            $args['startDate'] = $args['since'];
        }

        // Create query builder
        $query = new VMPFence_Live_Traffic_Query(array(
            'limit' => $args['limit'],
            'offset' => $args['offset'],
            'startDate' => $args['startDate'],
            'endDate' => $args['endDate'],
        ));

        // Build filter collection
        $filterCollection = new VMPFence_Live_Traffic_Filter_Collection();

        if (!empty($args['param']) && is_array($args['param'])) {
            $paramCount = count($args['param']);
            
            for ($i = 0; $i < $paramCount; $i++) {
                if (isset($args['param'][$i]) && isset($args['operator'][$i]) && isset($args['value'][$i])) {
                    $param = $args['param'][$i];
                    $operator = $args['operator'][$i];
                    $value = $args['value'][$i];

                    // Skip empty values
                    if (empty($param) || empty($operator)) {
                        continue;
                    }

                    // Handle special filter conversions
                    // Type filter: human/bot → jsRun
                    if ($param === 'type') {
                        if ($value === 'human') {
                            $param = 'jsrun';
                            $operator = '=';
                            $value = '1';
                        } elseif ($value === 'bot' || $value === 'crawler') {
                            $param = 'jsrun';
                            $operator = '=';
                            $value = '0';
                        }
                    }
                    
                    // Firewall Response filter → convert to action field
                    if ($param === 'firewall_response') {
                        $param = 'action';
                        if ($value === 'ok') {
                            // OK means no specific action (NULL or empty string)
                            // This requires special handling in the filter to check IS NULL OR = ''
                            $operator = 'isnullorempty';
                            $value = '';
                        } elseif ($value === 'throttle') {
                            $operator = 'contains';
                            $value = 'rate_limit';
                        } elseif ($value === 'lockedout') {
                            $operator = 'contains';
                            $value = 'lockedOut';
                        } elseif ($value === 'blocked') {
                            $operator = 'contains';
                            $value = 'blocked';
                        }
                        // blocked:waf is already the exact action value
                    }
                    
                    // Login Event filter → convert to action field
                    if ($param === 'login_event') {
                        $param = 'action';
                        // Special case: 'loginFailed' should match both loginFailInvalidUsername and loginFailValidUsername
                        if ($value === 'loginFailed') {
                            $operator = 'contains';
                            $value = 'loginFail';
                        } else {
                            $operator = '=';
                            // value is already the correct action name (loginOK, loginFailInvalidUsername, etc.)
                        }
                    }
                    
                    // Security Event filter → searches in action field
                    if ($param === 'security_event') {
                        $param = 'action';
                        if ($operator === '=') {
                            $operator = '=';
                        } else {
                            $operator = 'contains';
                        }
                    }

                    // Create and add filter
                    $filter = new VMPFence_Live_Traffic_Filter($query, $param, $operator, $value);
                    if ($filter->validate()) {
                        $filterCollection->addFilter($filter);
                    }
                }
            }
        }

        // Set filters on query
        $query->setFilters($filterCollection);

        // Set group by if provided
        if (!empty($args['groupby'])) {
            $groupBy = new VMPFence_Live_Traffic_GroupBy($query, $args['groupby']);
            if ($groupBy->validate()) {
                $query->setGroupBy($groupBy);
            }
        }

        // Execute query
        $results = $query->execute();
        $total = $query->getTotalCount();

        return array(
            'data' => $results,
            'total' => $total,
            'limit' => $args['limit'],
            'offset' => $args['offset'],
            'grouped' => !empty($args['groupby']),
        );
    }

    /**
     * Get available group by options
     *
     * @since 2.1.0
     * @return array Group by options
     */
    public static function get_group_by_options() {
        return VMPFence_Live_Traffic_GroupBy::getAvailableOptions();
    }

    /**
     * Get available filter parameters
     *
     * @since 2.1.0
     * @return array Filter parameters with metadata
     */
    public static function get_filter_params() {
        return array(
            array(
                'value' => 'type',
                'label' => 'Type',
                'type' => 'enum',
                'operators' => array('='),
                'values' => array(
                    array('value' => 'human', 'label' => 'Human'),
                    array('value' => 'bot', 'label' => 'Bot'),
                ),
            ),
            array(
                'value' => 'username',
                'label' => 'Username',
                'type' => 'text',
                'operators' => array('=', '!=', 'contains', 'notcontains'),
            ),
            array(
                'value' => 'userid',
                'label' => 'User ID',
                'type' => 'numeric',
                'operators' => array('=', '!=', '>', '<'),
            ),
            array(
                'value' => 'isgoogle',
                'label' => 'Google Bot',
                'type' => 'boolean',
                'operators' => array('='),
                'values' => array(
                    array('value' => 'yes', 'label' => 'Yes'),
                    array('value' => 'no', 'label' => 'No'),
                ),
            ),
            array(
                'value' => 'ip',
                'label' => 'IP',
                'type' => 'ip',
                'operators' => array('=', '!=', 'contains', 'notcontains', 'regexp'),
            ),
            array(
                'value' => 'ua',
                'label' => 'User Agent',
                'type' => 'text',
                'operators' => array('=', '!=', 'contains', 'notcontains', 'regexp'),
            ),
            array(
                'value' => 'referer',
                'label' => 'Referer',
                'type' => 'text',
                'operators' => array('=', '!=', 'contains', 'notcontains', 'regexp'),
            ),
            array(
                'value' => 'url',
                'label' => 'URL',
                'type' => 'text',
                'operators' => array('=', '!=', 'contains', 'notcontains', 'regexp'),
            ),
            array(
                'value' => 'statuscode',
                'label' => 'HTTP Response Code',
                'type' => 'numeric',
                'operators' => array('=', '!=', '>', '<', '>=', '<='),
            ),
            array(
                'value' => 'firewall_response',
                'label' => 'Firewall Response',
                'type' => 'enum',
                'operators' => array('='),
                'values' => array(
                    array('value' => 'ok', 'label' => 'OK'),
                    array('value' => 'throttle', 'label' => 'Throttled'),
                    array('value' => 'lockedout', 'label' => 'Locked Out'),
                    array('value' => 'blocked', 'label' => 'Blocked'),
                    array('value' => 'blocked:waf', 'label' => 'Blocked WAF'),
                ),
            ),
            array(
                'value' => 'login_event',
                'label' => 'Logins',
                'type' => 'enum',
                'operators' => array('='),
                'values' => array(
                    array('value' => 'loginOK', 'label' => 'Logged In'),
                    array('value' => 'loginFailed', 'label' => 'Failed Login'),
                    array('value' => 'loginFailInvalidUsername', 'label' => 'Failed Login: Invalid Username'),
                    array('value' => 'loginFailValidUsername', 'label' => 'Failed Login: Valid Username'),
                ),
            ),
            array(
                'value' => 'security_event',
                'label' => 'Security Event',
                'type' => 'text',
                'operators' => array('=', '!=', 'contains', 'notcontains'),
            ),
        );
    }

    /**
     * Get recent traffic by IP address
     *
     * @since 2.1.0
     * @param string $ip IP address
     * @param int $limit Number of entries to retrieve
     * @return array Traffic data
     */
    public static function get_recent_traffic_by_ip($ip, $limit = 10) {
        global $wpdb;
        $table_name = VMPFence_Constants::get_table_name(VMPFence_Constants::TABLE_HITS);

        // Convert IP to binary format
        $ip_binary = @inet_pton($ip);
        if ($ip_binary === false) {
            return array('total' => 0, 'data' => array(), 'ip' => $ip);
        }

        // Get total count for this IP
        $total = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$table_name} WHERE IP = %s",
            $ip_binary
        ));
        
        // Ensure total is an integer
        $total = (int)$total;

        // Get recent entries (ordered by ctime DESC)
        $results = $wpdb->get_results($wpdb->prepare(
            "SELECT 
                id,
                attackLogTime,
                ctime,
                IP,
                jsRun,
                statusCode,
                isGoogle,
                userID,
                newVisit,
                URL,
                referer,
                UA,
                action,
                actionDescription
            FROM {$table_name}
            WHERE IP = %s
            ORDER BY ctime DESC
            LIMIT %d",
            $ip_binary,
            $limit
        ), ARRAY_A);

        if (empty($results)) {
            return array('total' => 0, 'data' => array(), 'ip' => $ip);
        }

        // Process results
        $previous_time = null;
        foreach ($results as &$row) {
            // Convert binary IP to readable format
            if (!empty($row['IP'])) {
                $row['IP'] = @inet_ntop($row['IP']);
            }

            // Calculate "seconds since last hit"
            if ($previous_time !== null) {
                $row['seconds_since_last'] = round($previous_time - $row['ctime'], 4);
            } else {
                $row['seconds_since_last'] = 0; // First entry (newest) has no previous
            }
            $previous_time = $row['ctime'];

            // Format timestamp
            $row['formatted_time'] = date('m/d/Y g:i:s A', (int)$row['ctime']);
            $row['unix_timestamp'] = (int)$row['ctime'];

            // Calculate time ago
            $time_diff = time() - (int)$row['ctime'];
            if ($time_diff < 60) {
                $row['time_ago'] = $time_diff . ' seconds ago';
            } elseif ($time_diff < 3600) {
                $minutes = floor($time_diff / 60);
                $row['time_ago'] = $minutes . ' minute' . ($minutes > 1 ? 's' : '') . ' ago';
            } elseif ($time_diff < 86400) {
                $hours = floor($time_diff / 3600);
                $minutes = floor(($time_diff % 3600) / 60);
                $row['time_ago'] = $hours . ' hour' . ($hours > 1 ? 's' : '') . ' ' . 
                                  $minutes . ' min' . ($minutes > 1 ? 's' : '') . ' ago';
            } else {
                $days = floor($time_diff / 86400);
                $hours = floor(($time_diff % 86400) / 3600);
                $row['time_ago'] = $days . ' day' . ($days > 1 ? 's' : '') . ' ' . 
                                  $hours . ' hour' . ($hours > 1 ? 's' : '') . ' ago';
            }

            // Determine request type
            if (!empty($row['action'])) {
                if (strpos($row['action'], 'blocked') !== false || 
                    strpos($row['action'], 'lockedOut') !== false) {
                    $row['request_type'] = 'Blocked Request';
                } elseif (strpos($row['action'], 'login') !== false) {
                    $row['request_type'] = 'Login Attempt';
                } elseif (strpos($row['action'], 'logout') !== false) {
                    $row['request_type'] = 'Logout';
                } else {
                    $row['request_type'] = 'Normal Request';
                }
            } elseif (isset($row['statusCode']) && $row['statusCode'] == '404') {
                $row['request_type'] = '404 Not Found';
            } else {
                $row['request_type'] = 'Normal Request';
            }

            // Get hostname
            $row['hostname'] = self::get_hostname($row['IP']);
        }

        return array(
            'total' => $total,
            'data' => $results,
            'ip' => $ip,
            'hostname' => !empty($results[0]['hostname']) ? $results[0]['hostname'] : ''
        );
    }

    /**
     * Get country info (code, name, flag) for an IP address via GeoIP lookup.
     *
     * Uses VMPFence_GeoIP_Lookup for IP-to-country resolution and caches
     * the countries.json map in a static variable so it's loaded only once per request.
     *
     * @since 2.2.5
     * @param string $ip IP address to lookup.
     * @return array { code: string, name: string, flag: string }
     */
    public static function get_country_info($ip) {
        static $countries_map = null;

        $default = array('code' => '', 'name' => 'Unspecified', 'flag' => '');

        if (empty($ip) || !filter_var($ip, FILTER_VALIDATE_IP)) {
            return $default;
        }

        // Attempt GeoIP lookup
        $country_code = '';
        if (class_exists('VMPFence_GeoIP_Lookup')) {
            $result = VMPFence_GeoIP_Lookup::lookup($ip);
            if ($result && is_string($result)) {
                $country_code = strtoupper($result);
            }
        }

        if (empty($country_code)) {
            return $default;
        }

        // Build countries map once from countries.json
        if ($countries_map === null) {
            $countries_map = array();
            $json_path = VMPFENCE_PLUGIN_DIR . 'admin/data/countries.json';
            if (file_exists($json_path)) {
                $countries = json_decode(file_get_contents($json_path), true);
                if (is_array($countries)) {
                    foreach ($countries as $c) {
                        if (isset($c['code'])) {
                            $countries_map[strtoupper($c['code'])] = $c;
                        }
                    }
                }
            }
        }

        if (isset($countries_map[$country_code])) {
            return array(
                'code' => $country_code,
                'name' => $countries_map[$country_code]['name'] ?? $country_code,
                'flag' => 'https://flagcdn.com/20x15/' . strtolower($country_code) . '.png',
            );
        }

        return array('code' => $country_code, 'name' => $country_code, 'flag' => '');
    }
}
